@agentshouse/mdruntime 0.1.0-alpha.21 → 0.1.0-alpha.23
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 +9 -1
- package/dist/adapter.d.ts +20 -3
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/bootstrap.d.ts +1 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +2 -2
- package/dist/bootstrap.js.map +1 -1
- package/dist/change-set.d.ts +7 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +22 -2
- package/dist/change-set.js.map +1 -1
- package/dist/client.d.ts +7 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +19 -13
- package/dist/client.js.map +1 -1
- package/dist/collection.d.ts +2 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +11 -16
- package/dist/collection.js.map +1 -1
- package/dist/connection.d.ts +4 -4
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +78 -22
- package/dist/connection.js.map +1 -1
- package/dist/conversation.d.ts +28 -16
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +371 -269
- package/dist/conversation.js.map +1 -1
- package/dist/file.d.ts +2 -2
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js.map +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +6 -8
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts +3 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +24 -20
- package/dist/memory.js.map +1 -1
- package/dist/model.d.ts +10 -24
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/model.js.map +1 -1
- package/dist/refresh.d.ts +1 -2
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +2 -2
- package/dist/refresh.js.map +1 -1
- package/dist/replica.d.ts +2 -3
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +5 -5
- package/dist/replica.js.map +1 -1
- package/dist/save.d.ts +0 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +7 -30
- package/dist/save.js.map +1 -1
- package/dist/store.d.ts +19 -14
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +83 -20
- package/dist/store.js.map +1 -1
- package/dist/wire.d.ts +2 -2
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +4 -5
- package/dist/wire.js.map +1 -1
- package/package.json +3 -3
package/dist/conversation.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
+
import { conversationChangeSet } from './change-set.js';
|
|
1
2
|
import { declaredFile } from './file.js';
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
function ordered(left, right) {
|
|
4
|
+
if (left.position !== right.position) return left.position < right.position ? -1 : 1;
|
|
5
|
+
if (left.identity !== right.identity) return left.identity < right.identity ? -1 : 1;
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
function merged(held, arriving) {
|
|
9
|
+
const byIdentity = new Map(held.map((message)=>[
|
|
10
|
+
message.identity,
|
|
11
|
+
message
|
|
12
|
+
]));
|
|
13
|
+
for (const message of arriving)byIdentity.set(message.identity, message);
|
|
14
|
+
return [
|
|
15
|
+
...byIdentity.values()
|
|
16
|
+
].sort(ordered);
|
|
17
|
+
}
|
|
18
|
+
function acceptedMessage(value) {
|
|
4
19
|
if (typeof value !== 'object' || value === null) return null;
|
|
5
20
|
const record = value;
|
|
6
|
-
if (typeof record.turn !== 'string')
|
|
7
|
-
|
|
21
|
+
if (typeof record.identity !== 'string' || typeof record.turn !== 'string' || typeof record.position !== 'string' || typeof record.text !== 'string') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
identity: record.identity,
|
|
26
|
+
turn: record.turn,
|
|
27
|
+
position: record.position,
|
|
28
|
+
text: record.text,
|
|
29
|
+
message: structuredClone(record.message)
|
|
30
|
+
};
|
|
8
31
|
}
|
|
9
32
|
function historyPage(result) {
|
|
10
33
|
if (result.kind !== 'action') return null;
|
|
@@ -12,14 +35,18 @@ function historyPage(result) {
|
|
|
12
35
|
if (typeof answer !== 'object' || answer === null) return null;
|
|
13
36
|
const record = answer;
|
|
14
37
|
if (!Array.isArray(record.messages)) return null;
|
|
38
|
+
if (record.next !== undefined && typeof record.next !== 'string') return null;
|
|
15
39
|
const messages = [];
|
|
16
40
|
for (const entry of record.messages){
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
41
|
+
const message = acceptedMessage(entry);
|
|
42
|
+
if (message === null) return null;
|
|
43
|
+
messages.push(message);
|
|
19
44
|
}
|
|
20
45
|
return {
|
|
21
46
|
messages,
|
|
22
|
-
|
|
47
|
+
...record.next === undefined ? {} : {
|
|
48
|
+
next: record.next
|
|
49
|
+
}
|
|
23
50
|
};
|
|
24
51
|
}
|
|
25
52
|
function turnOf(result) {
|
|
@@ -29,182 +56,224 @@ function turnOf(result) {
|
|
|
29
56
|
const record = answer;
|
|
30
57
|
return typeof record.turn === 'string' ? record.turn : null;
|
|
31
58
|
}
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
].map((entry)=>entry.turn));
|
|
40
|
-
const live = held.messages.filter((entry)=>arrived.has(entry.turn) && !placed.has(entry.turn));
|
|
41
|
-
return {
|
|
42
|
-
...held,
|
|
43
|
-
messages: [
|
|
44
|
-
...carried,
|
|
45
|
-
...page.messages,
|
|
46
|
-
...live
|
|
47
|
-
],
|
|
48
|
-
cursor: overlap === -1 ? page.cursor : held.cursor
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function older(held, page) {
|
|
52
|
-
const present = new Set(held.messages.map((entry)=>entry.turn));
|
|
53
|
-
return {
|
|
54
|
-
...held,
|
|
55
|
-
messages: [
|
|
56
|
-
...page.messages.filter((entry)=>!present.has(entry.turn)),
|
|
57
|
-
...held.messages
|
|
58
|
-
],
|
|
59
|
-
cursor: page.cursor
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function sealed(held, arriving) {
|
|
63
|
-
const at = held.messages.findIndex((entry)=>entry.turn === arriving.turn);
|
|
64
|
-
if (at === -1) return {
|
|
65
|
-
...held,
|
|
66
|
-
messages: [
|
|
67
|
-
...held.messages,
|
|
68
|
-
arriving
|
|
69
|
-
]
|
|
70
|
-
};
|
|
71
|
-
const messages = [
|
|
72
|
-
...held.messages
|
|
73
|
-
];
|
|
74
|
-
messages[at] = arriving;
|
|
75
|
-
return {
|
|
76
|
-
...held,
|
|
77
|
-
messages
|
|
78
|
-
};
|
|
59
|
+
function limitOf(options) {
|
|
60
|
+
const { limit } = options;
|
|
61
|
+
if (limit === undefined) return undefined;
|
|
62
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
63
|
+
throw new Error(`conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
64
|
+
}
|
|
65
|
+
return limit;
|
|
79
66
|
}
|
|
80
67
|
export function createConversationStore(deps) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
68
|
+
const messages = new Map();
|
|
69
|
+
const reads = new Map();
|
|
70
|
+
const drafts = new Map();
|
|
71
|
+
const sending = new Map();
|
|
72
|
+
const behind = new Set();
|
|
73
|
+
const gaps = new Map();
|
|
83
74
|
const growing = new Map();
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
let
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function instructionOf(conversation) {
|
|
96
|
-
return composed.get(conversation) ?? {
|
|
97
|
-
text: '',
|
|
98
|
-
files: [],
|
|
99
|
-
turn: null
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function viewOf(conversation) {
|
|
103
|
-
const carried = heldOf(conversation);
|
|
104
|
-
const unsent = composed.get(conversation);
|
|
75
|
+
const summaries = new Map();
|
|
76
|
+
const views = new Map();
|
|
77
|
+
let replicated = new Set();
|
|
78
|
+
let handedReplicated;
|
|
79
|
+
let handedViewed;
|
|
80
|
+
function viewOf(conversation, limit) {
|
|
81
|
+
const held = messages.get(conversation) ?? [];
|
|
82
|
+
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
83
|
+
const draft = drafts.get(conversation);
|
|
105
84
|
return {
|
|
106
85
|
conversation,
|
|
107
|
-
messages:
|
|
108
|
-
|
|
86
|
+
messages: shown.map(({ identity, turn, text, message })=>({
|
|
87
|
+
identity,
|
|
88
|
+
turn,
|
|
89
|
+
text,
|
|
90
|
+
message: structuredClone(message)
|
|
91
|
+
})),
|
|
109
92
|
growingText: growing.get(conversation) ?? null,
|
|
110
|
-
summary: structuredClone(
|
|
111
|
-
|
|
112
|
-
text:
|
|
93
|
+
summary: structuredClone(summaries.get(conversation) ?? null),
|
|
94
|
+
chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft ? null : {
|
|
95
|
+
text: draft.text,
|
|
113
96
|
files: [
|
|
114
|
-
...
|
|
97
|
+
...draft.files
|
|
115
98
|
]
|
|
116
99
|
}
|
|
117
100
|
};
|
|
118
101
|
}
|
|
119
102
|
function emit(conversation) {
|
|
120
103
|
if (deps.closed()) return;
|
|
121
|
-
for (const
|
|
122
|
-
if (
|
|
104
|
+
for (const view of views.values()){
|
|
105
|
+
if (view.conversation !== conversation) continue;
|
|
106
|
+
const ready = [
|
|
107
|
+
...view.watchers
|
|
108
|
+
].filter((watcher)=>watcher.ready);
|
|
109
|
+
if (ready.length === 0) continue;
|
|
110
|
+
const value = viewOf(conversation, view.limit);
|
|
111
|
+
for (const watcher of ready)watcher.notify(value);
|
|
123
112
|
}
|
|
124
113
|
}
|
|
125
114
|
function viewed() {
|
|
126
115
|
return [
|
|
127
116
|
...new Set([
|
|
128
|
-
...
|
|
129
|
-
].map((
|
|
117
|
+
...views.values()
|
|
118
|
+
].map((view)=>view.conversation))
|
|
130
119
|
].sort();
|
|
131
120
|
}
|
|
132
|
-
function
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
121
|
+
function handViewed() {
|
|
122
|
+
const stream = deps.stream();
|
|
123
|
+
if (stream === undefined) return;
|
|
135
124
|
const open = viewed();
|
|
136
125
|
const named = JSON.stringify(open);
|
|
137
|
-
if (named ===
|
|
138
|
-
|
|
139
|
-
|
|
126
|
+
if (named === handedViewed) return;
|
|
127
|
+
handedViewed = named;
|
|
128
|
+
stream.view(open);
|
|
140
129
|
}
|
|
141
|
-
function
|
|
142
|
-
const
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
130
|
+
function handReplicated() {
|
|
131
|
+
const stream = deps.stream();
|
|
132
|
+
if (stream === undefined) return;
|
|
133
|
+
const held = [
|
|
134
|
+
...replicated
|
|
135
|
+
].sort();
|
|
136
|
+
const named = JSON.stringify(held);
|
|
137
|
+
if (named === handedReplicated) return;
|
|
138
|
+
handedReplicated = named;
|
|
139
|
+
stream.replicate(held);
|
|
149
140
|
}
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
141
|
+
function carded(documents) {
|
|
142
|
+
const declaration = deps.cards();
|
|
143
|
+
if (declaration === undefined) return [];
|
|
144
|
+
const cards = [];
|
|
145
|
+
for (const card of documents){
|
|
146
|
+
if (card.room !== declaration.locator || card.type !== declaration.type) continue;
|
|
147
|
+
const identity = card.fields[declaration.identity];
|
|
148
|
+
if (typeof identity === 'string') cards.push({
|
|
149
|
+
conversation: identity,
|
|
150
|
+
card
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return cards;
|
|
156
154
|
}
|
|
157
|
-
async function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
155
|
+
async function remove(leaving) {
|
|
156
|
+
await deps.durably(async (local)=>{
|
|
157
|
+
await local.dropConversations(leaving);
|
|
158
|
+
const removed = [];
|
|
159
|
+
const changed = [];
|
|
160
|
+
for (const conversation of leaving){
|
|
161
|
+
for (const message of messages.get(conversation) ?? []){
|
|
162
|
+
removed.push({
|
|
163
|
+
conversation,
|
|
164
|
+
identity: message.identity
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (messages.has(conversation) || drafts.has(conversation) || growing.has(conversation) || summaries.has(conversation)) {
|
|
168
|
+
changed.push(conversation);
|
|
169
|
+
}
|
|
170
|
+
messages.delete(conversation);
|
|
171
|
+
reads.delete(conversation);
|
|
172
|
+
drafts.delete(conversation);
|
|
173
|
+
behind.delete(conversation);
|
|
174
|
+
growing.delete(conversation);
|
|
175
|
+
summaries.delete(conversation);
|
|
176
|
+
}
|
|
177
|
+
if (changed.length > 0) deps.publish(conversationChangeSet(removed, changed));
|
|
165
178
|
});
|
|
166
|
-
if (kept) emit(conversation);
|
|
167
|
-
return kept;
|
|
168
179
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
function gap(conversation) {
|
|
181
|
+
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
182
|
+
behind.add(conversation);
|
|
183
|
+
}
|
|
184
|
+
function keep(conversation, message) {
|
|
185
|
+
return deps.durably(async (local)=>{
|
|
186
|
+
if (!replicated.has(conversation)) return;
|
|
187
|
+
const read = reads.get(conversation);
|
|
188
|
+
const advances = read !== undefined && !behind.has(conversation) && (read === null || message.position > read);
|
|
189
|
+
const sealed = drafts.get(conversation)?.turn === message.turn;
|
|
190
|
+
await local.keepMessages({
|
|
174
191
|
conversation,
|
|
175
|
-
|
|
192
|
+
messages: [
|
|
193
|
+
message
|
|
194
|
+
],
|
|
195
|
+
...advances ? {
|
|
196
|
+
read: message.position
|
|
197
|
+
} : {},
|
|
198
|
+
sealed
|
|
176
199
|
});
|
|
177
|
-
|
|
200
|
+
messages.set(conversation, merged(messages.get(conversation) ?? [], [
|
|
201
|
+
message
|
|
202
|
+
]));
|
|
203
|
+
if (advances) reads.set(conversation, message.position);
|
|
204
|
+
if (sealed) drafts.delete(conversation);
|
|
205
|
+
if (growing.get(conversation)?.turn === message.turn) growing.delete(conversation);
|
|
206
|
+
deps.publish(conversationChangeSet([
|
|
207
|
+
{
|
|
208
|
+
conversation,
|
|
209
|
+
identity: message.identity
|
|
210
|
+
}
|
|
211
|
+
], [
|
|
212
|
+
conversation
|
|
213
|
+
]));
|
|
178
214
|
});
|
|
179
|
-
emit(conversation);
|
|
180
215
|
}
|
|
181
|
-
async function
|
|
216
|
+
async function readHistory(conversation) {
|
|
217
|
+
const generation = gaps.get(conversation) ?? 0;
|
|
218
|
+
const from = reads.get(conversation);
|
|
219
|
+
const arriving = [];
|
|
220
|
+
let after = from ?? undefined;
|
|
221
|
+
for(;;){
|
|
222
|
+
const result = await deps.call('conversation.history', {
|
|
223
|
+
conversation,
|
|
224
|
+
...after === undefined ? {} : {
|
|
225
|
+
after
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const page = historyPage(result);
|
|
229
|
+
if (page === null) return result;
|
|
230
|
+
arriving.push(...page.messages);
|
|
231
|
+
if (page.next === undefined) break;
|
|
232
|
+
after = page.next;
|
|
233
|
+
}
|
|
182
234
|
await deps.durably(async (local)=>{
|
|
183
|
-
|
|
184
|
-
|
|
235
|
+
if (!replicated.has(conversation)) return;
|
|
236
|
+
const newest = arriving.reduce((top, message)=>top === null || message.position > top ? message.position : top, from ?? null);
|
|
237
|
+
const turn = drafts.get(conversation)?.turn;
|
|
238
|
+
const sealed = arriving.some((message)=>message.turn === turn);
|
|
239
|
+
await local.keepMessages({
|
|
240
|
+
conversation,
|
|
241
|
+
messages: arriving,
|
|
242
|
+
read: newest,
|
|
243
|
+
sealed
|
|
244
|
+
});
|
|
245
|
+
messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
|
|
246
|
+
reads.set(conversation, newest);
|
|
247
|
+
if (sealed) drafts.delete(conversation);
|
|
248
|
+
const growingTurn = growing.get(conversation)?.turn;
|
|
249
|
+
if (arriving.some((message)=>message.turn === growingTurn)) growing.delete(conversation);
|
|
250
|
+
if ((gaps.get(conversation) ?? 0) === generation) behind.delete(conversation);
|
|
251
|
+
if (arriving.length > 0 || sealed) {
|
|
252
|
+
deps.publish(conversationChangeSet(arriving.map((message)=>({
|
|
253
|
+
conversation,
|
|
254
|
+
identity: message.identity
|
|
255
|
+
})), [
|
|
256
|
+
conversation
|
|
257
|
+
]));
|
|
258
|
+
}
|
|
185
259
|
});
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
function supersede(conversation) {
|
|
189
|
-
issued.set(conversation, (issued.get(conversation) ?? 0) + 1);
|
|
260
|
+
return null;
|
|
190
261
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
262
|
+
function withDraft(conversation, next) {
|
|
263
|
+
return deps.durably(async (local)=>{
|
|
264
|
+
const following = next(drafts.get(conversation));
|
|
265
|
+
if (following === undefined) return;
|
|
266
|
+
if (following === null) {
|
|
267
|
+
await local.dropChatDraft(conversation);
|
|
268
|
+
drafts.delete(conversation);
|
|
269
|
+
} else {
|
|
270
|
+
await local.keepChatDraft(following);
|
|
271
|
+
drafts.set(conversation, following);
|
|
197
272
|
}
|
|
273
|
+
deps.publish(conversationChangeSet([], [
|
|
274
|
+
conversation
|
|
275
|
+
]));
|
|
198
276
|
});
|
|
199
|
-
const page = historyPage(result);
|
|
200
|
-
if (page === null) return result;
|
|
201
|
-
const landed = await keep(conversation, (carried)=>{
|
|
202
|
-
if (issued.get(conversation) !== generation) return null;
|
|
203
|
-
if (cursor !== null) return carried.cursor === cursor ? older(carried, page) : null;
|
|
204
|
-
return newest(carried, page, live.get(conversation) ?? NO_TURNS);
|
|
205
|
-
});
|
|
206
|
-
if (landed && cursor === null) live.delete(conversation);
|
|
207
|
-
return result;
|
|
208
277
|
}
|
|
209
278
|
function invoke(name, conversation, extra = {}) {
|
|
210
279
|
return deps.call(name, {
|
|
@@ -216,62 +285,63 @@ export function createConversationStore(deps) {
|
|
|
216
285
|
list () {
|
|
217
286
|
const declaration = deps.cards();
|
|
218
287
|
if (declaration === undefined) return Promise.resolve([]);
|
|
219
|
-
const documents = deps.documents().
|
|
220
|
-
const entries =
|
|
221
|
-
|
|
222
|
-
if (card.type !== declaration.type) continue;
|
|
223
|
-
const identity = card.fields[declaration.identity];
|
|
224
|
-
if (typeof identity !== 'string') continue;
|
|
225
|
-
const agent = documents.find((document)=>document.backlinks.some((backlink)=>backlink.path === card.path && backlink.field === declaration.agent));
|
|
226
|
-
entries.push({
|
|
227
|
-
conversation: identity,
|
|
288
|
+
const documents = deps.documents().filter((document)=>document.room === declaration.locator);
|
|
289
|
+
const entries = carded(documents).map(({ conversation, card })=>({
|
|
290
|
+
conversation,
|
|
228
291
|
card,
|
|
229
|
-
agent: agent ?? null
|
|
230
|
-
});
|
|
231
|
-
}
|
|
292
|
+
agent: documents.find((document)=>document.backlinks.some((backlink)=>backlink.path === card.path && backlink.field === declaration.agent)) ?? null
|
|
293
|
+
}));
|
|
232
294
|
entries.sort((left, right)=>left.conversation < right.conversation ? -1 : 1);
|
|
233
295
|
return Promise.resolve(entries);
|
|
234
296
|
},
|
|
235
|
-
read (conversation) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
older (conversation) {
|
|
239
|
-
const cursor = heldOf(conversation).cursor;
|
|
240
|
-
if (cursor === null) return Promise.resolve(null);
|
|
241
|
-
return fill(conversation, cursor);
|
|
242
|
-
},
|
|
243
|
-
compose (conversation, supplied) {
|
|
244
|
-
return keepInstruction(conversation, ()=>({
|
|
245
|
-
text: supplied.text,
|
|
246
|
-
files: [
|
|
247
|
-
...supplied.files
|
|
248
|
-
],
|
|
249
|
-
turn: null
|
|
250
|
-
}));
|
|
251
|
-
},
|
|
252
|
-
discard (conversation) {
|
|
253
|
-
return dropInstruction(conversation);
|
|
297
|
+
async read (conversation, options = {}) {
|
|
298
|
+
const limit = limitOf(options);
|
|
299
|
+
return deps.durably(()=>Promise.resolve(viewOf(conversation, limit)));
|
|
254
300
|
},
|
|
255
301
|
create (input) {
|
|
256
302
|
return deps.call('conversation.create', input);
|
|
257
303
|
},
|
|
258
304
|
async send (conversation) {
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
305
|
+
const draft = drafts.get(conversation);
|
|
306
|
+
const pending = draft === undefined ? undefined : {
|
|
307
|
+
draft
|
|
308
|
+
};
|
|
309
|
+
if (pending !== undefined) {
|
|
310
|
+
sending.set(conversation, pending);
|
|
311
|
+
deps.publish(conversationChangeSet([], [
|
|
312
|
+
conversation
|
|
313
|
+
]));
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const files = await Promise.all((draft?.files ?? []).map(async (file)=>({
|
|
317
|
+
...await declaredFile(file),
|
|
318
|
+
blob: file.blob
|
|
319
|
+
})));
|
|
320
|
+
const result = await invoke('conversation.send', conversation, {
|
|
321
|
+
text: draft?.text ?? '',
|
|
322
|
+
files
|
|
323
|
+
});
|
|
324
|
+
const turn = turnOf(result);
|
|
325
|
+
if (turn === null || draft === undefined) return result;
|
|
326
|
+
await withDraft(conversation, (current)=>{
|
|
327
|
+
if (current !== draft) return undefined;
|
|
328
|
+
const sealed = (messages.get(conversation) ?? []).some((message)=>message.turn === turn);
|
|
329
|
+
return sealed ? null : {
|
|
330
|
+
...draft,
|
|
331
|
+
turn
|
|
332
|
+
};
|
|
333
|
+
});
|
|
334
|
+
return result;
|
|
335
|
+
} finally{
|
|
336
|
+
if (pending !== undefined && sending.get(conversation) === pending) {
|
|
337
|
+
sending.delete(conversation);
|
|
338
|
+
if (drafts.get(conversation) === draft) {
|
|
339
|
+
deps.publish(conversationChangeSet([], [
|
|
340
|
+
conversation
|
|
341
|
+
]));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
275
345
|
},
|
|
276
346
|
answer (conversation, answer) {
|
|
277
347
|
return invoke('conversation.answer', conversation, {
|
|
@@ -289,114 +359,146 @@ export function createConversationStore(deps) {
|
|
|
289
359
|
turn
|
|
290
360
|
});
|
|
291
361
|
if (result.kind !== 'action') return result;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}));
|
|
297
|
-
}
|
|
362
|
+
await withDraft(conversation, (current)=>current === undefined || current.turn !== turn ? undefined : {
|
|
363
|
+
...current,
|
|
364
|
+
turn: null
|
|
365
|
+
});
|
|
298
366
|
return result;
|
|
299
367
|
},
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
368
|
+
delete (conversation) {
|
|
369
|
+
return invoke('conversation.delete', conversation);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
const chatDraft = {
|
|
373
|
+
write (conversation, draft) {
|
|
374
|
+
return withDraft(conversation, ()=>({
|
|
375
|
+
conversation,
|
|
376
|
+
text: draft.text,
|
|
377
|
+
files: [
|
|
378
|
+
...draft.files
|
|
379
|
+
],
|
|
380
|
+
turn: null
|
|
381
|
+
}));
|
|
382
|
+
},
|
|
383
|
+
discard (conversation) {
|
|
384
|
+
return withDraft(conversation, (current)=>current === undefined ? undefined : null);
|
|
314
385
|
}
|
|
315
386
|
};
|
|
316
387
|
return {
|
|
317
388
|
surface,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
389
|
+
chatDraft,
|
|
390
|
+
hydrate (held, stored) {
|
|
391
|
+
messages.clear();
|
|
392
|
+
reads.clear();
|
|
393
|
+
for (const entry of held){
|
|
394
|
+
messages.set(entry.conversation, [
|
|
395
|
+
...entry.messages
|
|
396
|
+
].sort(ordered));
|
|
397
|
+
if (entry.read !== undefined) reads.set(entry.conversation, entry.read);
|
|
326
398
|
}
|
|
327
|
-
|
|
328
|
-
for (const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
399
|
+
drafts.clear();
|
|
400
|
+
for (const record of stored)drafts.set(record.conversation, record);
|
|
401
|
+
},
|
|
402
|
+
async reconcile () {
|
|
403
|
+
const named = new Set(carded(deps.documents()).map((entry)=>entry.conversation));
|
|
404
|
+
const leaving = [
|
|
405
|
+
...new Set([
|
|
406
|
+
...replicated,
|
|
407
|
+
...messages.keys(),
|
|
408
|
+
...reads.keys(),
|
|
409
|
+
...drafts.keys()
|
|
410
|
+
])
|
|
411
|
+
].filter((conversation)=>!named.has(conversation));
|
|
412
|
+
for (const conversation of named){
|
|
413
|
+
if (!replicated.has(conversation)) behind.add(conversation);
|
|
334
414
|
}
|
|
415
|
+
replicated = named;
|
|
416
|
+
handReplicated();
|
|
417
|
+
if (leaving.length > 0) await remove(leaving);
|
|
335
418
|
},
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
419
|
+
async catchUp (progress) {
|
|
420
|
+
for (const conversation of [
|
|
421
|
+
...behind
|
|
422
|
+
].sort()){
|
|
423
|
+
if (deps.closed()) return;
|
|
424
|
+
if (!replicated.has(conversation)) continue;
|
|
425
|
+
const failed = await readHistory(conversation);
|
|
426
|
+
if (failed?.kind === 'transport-uncertainty') progress.reachable = false;
|
|
427
|
+
}
|
|
428
|
+
if (behind.size > 0) progress.complete = false;
|
|
429
|
+
},
|
|
430
|
+
current () {
|
|
431
|
+
return behind.size === 0;
|
|
432
|
+
},
|
|
433
|
+
reconnected () {
|
|
434
|
+
for (const conversation of replicated)gap(conversation);
|
|
435
|
+
},
|
|
436
|
+
observe (conversation, options, notify) {
|
|
437
|
+
const limit = limitOf(options);
|
|
438
|
+
const key = JSON.stringify([
|
|
339
439
|
conversation,
|
|
440
|
+
limit ?? null
|
|
441
|
+
]);
|
|
442
|
+
const view = views.get(key) ?? {
|
|
443
|
+
conversation,
|
|
444
|
+
limit,
|
|
445
|
+
watchers: new Set()
|
|
446
|
+
};
|
|
447
|
+
views.set(key, view);
|
|
448
|
+
const watcher = {
|
|
340
449
|
notify,
|
|
341
450
|
ready: false
|
|
342
451
|
};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
void deps.durably(()=>Promise.resolve()).then(async ()=>{
|
|
350
|
-
if (deps.closed() || !viewers.has(viewer)) return;
|
|
351
|
-
viewer.ready = true;
|
|
352
|
-
viewer.notify(viewOf(conversation));
|
|
353
|
-
if (first) await fill(conversation, null);
|
|
452
|
+
view.watchers.add(watcher);
|
|
453
|
+
handViewed();
|
|
454
|
+
void deps.durably(()=>Promise.resolve()).then(()=>{
|
|
455
|
+
if (deps.closed() || !view.watchers.has(watcher)) return;
|
|
456
|
+
watcher.ready = true;
|
|
457
|
+
watcher.notify(viewOf(conversation, limit));
|
|
354
458
|
}).catch(()=>{});
|
|
355
459
|
return ()=>{
|
|
356
|
-
if (!
|
|
460
|
+
if (!view.watchers.delete(watcher)) return;
|
|
461
|
+
if (view.watchers.size === 0) views.delete(key);
|
|
357
462
|
if (!viewed().includes(conversation)) {
|
|
358
463
|
growing.delete(conversation);
|
|
359
|
-
|
|
464
|
+
summaries.delete(conversation);
|
|
360
465
|
}
|
|
361
|
-
|
|
466
|
+
handViewed();
|
|
362
467
|
};
|
|
363
468
|
},
|
|
469
|
+
changed (set) {
|
|
470
|
+
for (const conversation of set.conversations)emit(conversation);
|
|
471
|
+
},
|
|
364
472
|
receive (event) {
|
|
365
|
-
if (deps.closed()
|
|
473
|
+
if (deps.closed()) return;
|
|
474
|
+
const { conversation } = event;
|
|
475
|
+
if (event.kind === 'gap') {
|
|
476
|
+
if (replicated.has(conversation)) gap(conversation);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
366
479
|
if (event.kind === 'growing-text') {
|
|
367
|
-
|
|
480
|
+
if (!viewed().includes(conversation)) return;
|
|
481
|
+
growing.set(conversation, {
|
|
368
482
|
turn: event.turn,
|
|
369
483
|
text: event.text
|
|
370
484
|
});
|
|
371
|
-
emit(
|
|
485
|
+
emit(conversation);
|
|
372
486
|
return;
|
|
373
487
|
}
|
|
374
488
|
if (event.kind === 'session-summary') {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
summary
|
|
379
|
-
})).catch(()=>{});
|
|
489
|
+
if (!viewed().includes(conversation)) return;
|
|
490
|
+
summaries.set(conversation, structuredClone(event.summary));
|
|
491
|
+
emit(conversation);
|
|
380
492
|
return;
|
|
381
493
|
}
|
|
382
|
-
const arriving =
|
|
383
|
-
if (arriving === null) return;
|
|
384
|
-
|
|
385
|
-
if (growing.get(event.conversation)?.turn === arriving.turn) {
|
|
386
|
-
growing.delete(event.conversation);
|
|
387
|
-
}
|
|
388
|
-
void deps.durably(async (local)=>{
|
|
389
|
-
held.set(event.conversation, sealed(heldOf(event.conversation), arriving));
|
|
390
|
-
await local.keepConversation(keptRecord(event.conversation));
|
|
391
|
-
await settle(local, event.conversation);
|
|
392
|
-
}).then(()=>{
|
|
393
|
-
emit(event.conversation);
|
|
394
|
-
}).catch(()=>{});
|
|
494
|
+
const arriving = acceptedMessage(event.message);
|
|
495
|
+
if (arriving === null || !replicated.has(conversation)) return;
|
|
496
|
+
void keep(conversation, arriving).catch(()=>{});
|
|
395
497
|
},
|
|
396
498
|
close () {
|
|
397
|
-
|
|
499
|
+
views.clear();
|
|
398
500
|
growing.clear();
|
|
399
|
-
|
|
501
|
+
summaries.clear();
|
|
400
502
|
}
|
|
401
503
|
};
|
|
402
504
|
}
|