@agentshouse/mdruntime 0.1.0-alpha.9 → 0.1.0-c30ed2b8e04d5
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 +217 -0
- package/README.md +43 -0
- package/dist/adapter.d.ts +184 -34
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -10
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +34 -0
- package/dist/attachment.d.ts +3 -0
- package/dist/attachment.d.ts.map +1 -0
- package/dist/attachment.js +8 -0
- package/dist/bootstrap.d.ts +7 -9
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +29 -28
- package/dist/change-set.d.ts +33 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/change-set.js +83 -0
- package/dist/change.d.ts +9 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +6 -5
- package/dist/client.d.ts +38 -32
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +114 -183
- package/dist/collection.d.ts +13 -16
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +45 -88
- package/dist/connection.d.ts +47 -44
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1542 -1158
- package/dist/conversation.d.ts +44 -22
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +540 -328
- package/dist/durability.js +6 -6
- package/dist/file.d.ts +2 -2
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +48 -71
- package/dist/host.d.ts +12 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +97 -119
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +10 -9
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/log.js +5 -6
- package/dist/memory.d.ts +15 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +255 -220
- package/dist/model.d.ts +10 -29
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +18 -21
- package/dist/observation.d.ts +27 -0
- package/dist/observation.d.ts.map +1 -0
- package/dist/observation.js +95 -0
- package/dist/outcome.d.ts +10 -29
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +1 -23
- package/dist/overlay.d.ts +9 -2
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +187 -43
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +12 -19
- package/dist/query.d.ts +215 -10
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +421 -15
- package/dist/reach.d.ts +40 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +277 -0
- package/dist/read-side.d.ts +18 -0
- package/dist/read-side.d.ts.map +1 -0
- package/dist/read-side.js +377 -0
- package/dist/refresh.d.ts +5 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +49 -30
- package/dist/replica.d.ts +25 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +227 -38
- package/dist/room-draft.d.ts +127 -0
- package/dist/room-draft.d.ts.map +1 -0
- package/dist/room-draft.js +329 -0
- package/dist/rows.d.ts +41 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +100 -0
- package/dist/save.d.ts +6 -4
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +262 -350
- package/dist/search.d.ts +61 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +125 -0
- package/dist/status.d.ts +4 -6
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +9 -13
- package/dist/store.d.ts +59 -32
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +338 -134
- package/dist/transfer.d.ts +5 -0
- package/dist/transfer.d.ts.map +1 -1
- package/dist/transfer.js +1 -5
- package/dist/wire.d.ts +51 -16
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +77 -108
- package/package.json +6 -12
- package/dist/adapter.js.map +0 -1
- package/dist/bootstrap.js.map +0 -1
- package/dist/change.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/collection.js.map +0 -1
- package/dist/connection.js.map +0 -1
- package/dist/conversation.js.map +0 -1
- package/dist/durability.js.map +0 -1
- package/dist/file.js.map +0 -1
- package/dist/host.js.map +0 -1
- package/dist/identity.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/memory.js.map +0 -1
- package/dist/model.js.map +0 -1
- package/dist/outcome.js.map +0 -1
- package/dist/overlay.js.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/query.js.map +0 -1
- package/dist/refresh.js.map +0 -1
- package/dist/replica.js.map +0 -1
- package/dist/save.js.map +0 -1
- package/dist/status.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/transfer.js.map +0 -1
- package/dist/wire.js.map +0 -1
package/dist/conversation.js
CHANGED
|
@@ -1,401 +1,613 @@
|
|
|
1
|
+
import { conversationChangeSet } from './change-set.js';
|
|
1
2
|
import { declaredFile } from './file.js';
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
function
|
|
10
|
-
if (result.kind !== 'action') return null;
|
|
11
|
-
const answer = result.result;
|
|
12
|
-
if (typeof answer !== 'object' || answer === null) return null;
|
|
13
|
-
const record = answer;
|
|
14
|
-
if (!Array.isArray(record.messages)) return null;
|
|
15
|
-
const messages = [];
|
|
16
|
-
for (const entry of record.messages){
|
|
17
|
-
const carried = carriedMessage(entry);
|
|
18
|
-
if (carried !== null) messages.push(carried);
|
|
19
|
-
}
|
|
3
|
+
const QUIET = {
|
|
4
|
+
turn: null,
|
|
5
|
+
questions: [],
|
|
6
|
+
process: null,
|
|
7
|
+
commands: [],
|
|
8
|
+
options: [],
|
|
9
|
+
};
|
|
10
|
+
export function unheldView(conversation) {
|
|
20
11
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
conversation,
|
|
13
|
+
messages: [],
|
|
14
|
+
turn: null,
|
|
15
|
+
questions: [],
|
|
16
|
+
process: null,
|
|
17
|
+
commands: [],
|
|
18
|
+
options: [],
|
|
19
|
+
chatDraft: null,
|
|
23
20
|
};
|
|
24
21
|
}
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
function startedTurn(turn, operation) {
|
|
23
|
+
if (operation.op === 'context') {
|
|
24
|
+
return { turn, parts: [], context: { used: operation.used, window: operation.window } };
|
|
25
|
+
}
|
|
26
|
+
return operation.op === 'start' && operation.index === 0
|
|
27
|
+
? { turn, parts: [structuredClone(operation.part)], context: null }
|
|
28
|
+
: null;
|
|
31
29
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
function changedFields(part, operation) {
|
|
31
|
+
if (operation.op === 'append') {
|
|
32
|
+
return { text: `${String(part.text ?? '')}${operation.text}` };
|
|
33
|
+
}
|
|
34
|
+
if (operation.op === 'status') {
|
|
35
|
+
return { status: operation.status, result: structuredClone(operation.result) };
|
|
36
|
+
}
|
|
37
|
+
if (operation.op === 'count')
|
|
38
|
+
return { count: operation.count };
|
|
39
|
+
return { entries: structuredClone(operation.entries) };
|
|
40
|
+
}
|
|
41
|
+
function appliedTurn(held, operation) {
|
|
42
|
+
if (operation.op === 'context') {
|
|
43
|
+
return { ...held, context: { used: operation.used, window: operation.window } };
|
|
44
|
+
}
|
|
45
|
+
if (operation.op === 'start') {
|
|
46
|
+
if (operation.index !== 0 && operation.index !== held.parts.length)
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
...held,
|
|
50
|
+
parts: [...held.parts.slice(0, operation.index), structuredClone(operation.part)],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const part = held.parts[operation.index];
|
|
54
|
+
if (typeof part !== 'object' || part === null)
|
|
55
|
+
return null;
|
|
56
|
+
const changed = { ...part, ...changedFields(part, operation) };
|
|
41
57
|
return {
|
|
42
58
|
...held,
|
|
43
|
-
|
|
44
|
-
...carried,
|
|
45
|
-
...page.messages,
|
|
46
|
-
...live
|
|
47
|
-
],
|
|
48
|
-
cursor: overlap === -1 ? page.cursor : held.cursor
|
|
59
|
+
parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
|
|
49
60
|
};
|
|
50
61
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
62
|
+
function ordered(left, right) {
|
|
63
|
+
if (left.position !== right.position)
|
|
64
|
+
return left.position < right.position ? -1 : 1;
|
|
65
|
+
if (left.identity !== right.identity)
|
|
66
|
+
return left.identity < right.identity ? -1 : 1;
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
function merged(held, arriving) {
|
|
70
|
+
const byIdentity = new Map(held.map((message) => [message.identity, message]));
|
|
71
|
+
for (const message of arriving)
|
|
72
|
+
byIdentity.set(message.identity, message);
|
|
73
|
+
return [...byIdentity.values()].sort(ordered);
|
|
74
|
+
}
|
|
75
|
+
function acceptedMessage(value) {
|
|
76
|
+
if (typeof value !== 'object' || value === null)
|
|
77
|
+
return null;
|
|
78
|
+
const record = value;
|
|
79
|
+
if (typeof record.identity !== 'string' ||
|
|
80
|
+
typeof record.turn !== 'string' ||
|
|
81
|
+
typeof record.position !== 'string' ||
|
|
82
|
+
typeof record.text !== 'string') {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
53
85
|
return {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
cursor: page.cursor
|
|
86
|
+
identity: record.identity,
|
|
87
|
+
turn: record.turn,
|
|
88
|
+
position: record.position,
|
|
89
|
+
text: record.text,
|
|
90
|
+
message: structuredClone(record.message),
|
|
60
91
|
};
|
|
61
92
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const messages
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
function historyPage(value) {
|
|
94
|
+
if (typeof value !== 'object' || value === null)
|
|
95
|
+
return null;
|
|
96
|
+
const record = value;
|
|
97
|
+
if (typeof record.conversation !== 'string' || !Array.isArray(record.messages))
|
|
98
|
+
return null;
|
|
99
|
+
if (record.next !== undefined && typeof record.next !== 'string')
|
|
100
|
+
return null;
|
|
101
|
+
const messages = [];
|
|
102
|
+
for (const entry of record.messages) {
|
|
103
|
+
const message = acceptedMessage(entry);
|
|
104
|
+
if (message === null)
|
|
105
|
+
return null;
|
|
106
|
+
messages.push(message);
|
|
107
|
+
}
|
|
75
108
|
return {
|
|
76
|
-
|
|
77
|
-
messages
|
|
109
|
+
conversation: record.conversation,
|
|
110
|
+
messages,
|
|
111
|
+
...(record.next === undefined ? {} : { next: record.next }),
|
|
78
112
|
};
|
|
79
113
|
}
|
|
114
|
+
function historyPages(result) {
|
|
115
|
+
if (result.kind !== 'action')
|
|
116
|
+
return null;
|
|
117
|
+
const answer = result.result;
|
|
118
|
+
if (typeof answer !== 'object' || answer === null)
|
|
119
|
+
return null;
|
|
120
|
+
const { histories } = answer;
|
|
121
|
+
if (!Array.isArray(histories))
|
|
122
|
+
return null;
|
|
123
|
+
const pages = [];
|
|
124
|
+
for (const entry of histories) {
|
|
125
|
+
const page = historyPage(entry);
|
|
126
|
+
if (page === null)
|
|
127
|
+
return null;
|
|
128
|
+
pages.push(page);
|
|
129
|
+
}
|
|
130
|
+
return pages;
|
|
131
|
+
}
|
|
132
|
+
function turnOf(result) {
|
|
133
|
+
if (result.kind !== 'action')
|
|
134
|
+
return null;
|
|
135
|
+
const answer = result.result;
|
|
136
|
+
if (typeof answer !== 'object' || answer === null)
|
|
137
|
+
return null;
|
|
138
|
+
const record = answer;
|
|
139
|
+
return typeof record.turn === 'string' ? record.turn : null;
|
|
140
|
+
}
|
|
141
|
+
function limitOf(options) {
|
|
142
|
+
const { limit } = options;
|
|
143
|
+
if (limit === undefined)
|
|
144
|
+
return undefined;
|
|
145
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
146
|
+
throw new Error(`conversation limit must be a non-negative integer, got ${String(limit)}`);
|
|
147
|
+
}
|
|
148
|
+
return limit;
|
|
149
|
+
}
|
|
80
150
|
export function createConversationStore(deps) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
151
|
+
const messages = new Map();
|
|
152
|
+
const reads = new Map();
|
|
153
|
+
const drafts = new Map();
|
|
154
|
+
const sending = new Map();
|
|
155
|
+
const behind = new Set();
|
|
156
|
+
const gaps = new Map();
|
|
157
|
+
const lives = new Map();
|
|
158
|
+
const views = new Map();
|
|
159
|
+
let replicated = new Set();
|
|
160
|
+
let handedReplicated;
|
|
161
|
+
function liveOf(conversation) {
|
|
162
|
+
return lives.get(conversation) ?? QUIET;
|
|
94
163
|
}
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
164
|
+
function shownMessages(conversation, limit) {
|
|
165
|
+
const held = messages.get(conversation) ?? [];
|
|
166
|
+
const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
|
|
167
|
+
return shown.map(({ identity, turn, text, message }) => ({
|
|
168
|
+
identity,
|
|
169
|
+
turn,
|
|
170
|
+
text,
|
|
171
|
+
message: structuredClone(message),
|
|
172
|
+
}));
|
|
101
173
|
}
|
|
102
|
-
function viewOf(conversation) {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
174
|
+
function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
|
|
175
|
+
const draft = drafts.get(conversation);
|
|
176
|
+
const facts = liveOf(conversation);
|
|
105
177
|
return {
|
|
106
178
|
conversation,
|
|
107
|
-
messages:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
]
|
|
116
|
-
}
|
|
179
|
+
messages: shown,
|
|
180
|
+
turn: structuredClone(facts.turn),
|
|
181
|
+
questions: structuredClone(facts.questions),
|
|
182
|
+
process: facts.process === null ? null : { ...facts.process },
|
|
183
|
+
commands: structuredClone(facts.commands),
|
|
184
|
+
options: structuredClone(facts.options),
|
|
185
|
+
chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft
|
|
186
|
+
? null
|
|
187
|
+
: { text: draft.text, files: [...draft.files] },
|
|
117
188
|
};
|
|
118
189
|
}
|
|
119
|
-
function emit(conversation) {
|
|
120
|
-
if (deps.closed())
|
|
121
|
-
|
|
122
|
-
|
|
190
|
+
function emit(conversation, messagesChanged = true) {
|
|
191
|
+
if (deps.closed())
|
|
192
|
+
return;
|
|
193
|
+
for (const view of views.values()) {
|
|
194
|
+
if (view.conversation !== conversation)
|
|
195
|
+
continue;
|
|
196
|
+
if (messagesChanged)
|
|
197
|
+
view.messages = null;
|
|
198
|
+
const ready = [...view.watchers].filter((watcher) => watcher.ready);
|
|
199
|
+
if (ready.length === 0)
|
|
200
|
+
continue;
|
|
201
|
+
view.messages ??= shownMessages(conversation, view.limit);
|
|
202
|
+
const value = viewOf(conversation, view.limit, view.messages);
|
|
203
|
+
for (const watcher of ready)
|
|
204
|
+
watcher.notify(value);
|
|
123
205
|
}
|
|
124
206
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...viewers
|
|
129
|
-
].map((viewer)=>viewer.conversation))
|
|
130
|
-
].sort();
|
|
207
|
+
function live(conversation, change) {
|
|
208
|
+
lives.set(conversation, { ...liveOf(conversation), ...change });
|
|
209
|
+
emit(conversation, false);
|
|
131
210
|
}
|
|
132
|
-
function
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (named === handed) return;
|
|
138
|
-
handed = named;
|
|
139
|
-
path.view(open);
|
|
211
|
+
function relayed(conversation, turn, operation) {
|
|
212
|
+
const held = liveOf(conversation).turn;
|
|
213
|
+
const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
|
|
214
|
+
if (next !== null)
|
|
215
|
+
live(conversation, { turn: next });
|
|
140
216
|
}
|
|
141
|
-
function
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
217
|
+
function sealTurns(conversation, turns) {
|
|
218
|
+
const held = lives.get(conversation);
|
|
219
|
+
if (held?.turn == null || !turns.has(held.turn.turn))
|
|
220
|
+
return false;
|
|
221
|
+
lives.set(conversation, { ...held, turn: null });
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
function handReplicated() {
|
|
225
|
+
const stream = deps.stream();
|
|
226
|
+
if (stream === undefined)
|
|
227
|
+
return;
|
|
228
|
+
const held = [...replicated].sort();
|
|
229
|
+
const named = JSON.stringify(held);
|
|
230
|
+
if (named === handedReplicated)
|
|
231
|
+
return;
|
|
232
|
+
handedReplicated = named;
|
|
233
|
+
stream.replicate(held);
|
|
149
234
|
}
|
|
150
|
-
async function
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
235
|
+
async function documented() {
|
|
236
|
+
const declaration = deps.cards();
|
|
237
|
+
if (declaration === undefined)
|
|
238
|
+
return [];
|
|
239
|
+
const { documents } = await deps.documents(declaration.locator);
|
|
240
|
+
return documents.filter((document) => typeof document.type === 'string');
|
|
241
|
+
}
|
|
242
|
+
function carded(documents) {
|
|
243
|
+
const declaration = deps.cards();
|
|
244
|
+
if (declaration === undefined)
|
|
245
|
+
return [];
|
|
246
|
+
const cards = [];
|
|
247
|
+
for (const card of documents) {
|
|
248
|
+
if (card.type !== declaration.type)
|
|
249
|
+
continue;
|
|
250
|
+
const identity = card.fields[declaration.identity];
|
|
251
|
+
if (typeof identity === 'string')
|
|
252
|
+
cards.push({ conversation: identity, card });
|
|
253
|
+
}
|
|
254
|
+
return cards;
|
|
156
255
|
}
|
|
157
|
-
async function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
256
|
+
async function remove(leaving) {
|
|
257
|
+
await deps.durably(async (local) => {
|
|
258
|
+
await local.dropConversations(leaving);
|
|
259
|
+
const removed = [];
|
|
260
|
+
const changed = [];
|
|
261
|
+
for (const conversation of leaving) {
|
|
262
|
+
for (const message of messages.get(conversation) ?? []) {
|
|
263
|
+
removed.push({ conversation, identity: message.identity });
|
|
264
|
+
}
|
|
265
|
+
if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
|
|
266
|
+
changed.push(conversation);
|
|
267
|
+
}
|
|
268
|
+
messages.delete(conversation);
|
|
269
|
+
reads.delete(conversation);
|
|
270
|
+
drafts.delete(conversation);
|
|
271
|
+
behind.delete(conversation);
|
|
272
|
+
lives.delete(conversation);
|
|
273
|
+
}
|
|
274
|
+
if (changed.length > 0)
|
|
275
|
+
deps.publish(conversationChangeSet(removed, changed));
|
|
165
276
|
});
|
|
166
|
-
if (kept) emit(conversation);
|
|
167
|
-
return kept;
|
|
168
277
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
278
|
+
function gap(conversation) {
|
|
279
|
+
gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
|
|
280
|
+
behind.add(conversation);
|
|
281
|
+
}
|
|
282
|
+
function keep(conversation, message) {
|
|
283
|
+
return deps.durably(async (local) => {
|
|
284
|
+
if (!replicated.has(conversation))
|
|
285
|
+
return;
|
|
286
|
+
const read = reads.get(conversation);
|
|
287
|
+
const advances = read !== undefined &&
|
|
288
|
+
!behind.has(conversation) &&
|
|
289
|
+
(read === null || message.position > read);
|
|
290
|
+
const sealed = drafts.get(conversation)?.turn === message.turn;
|
|
291
|
+
await local.keepMessages({
|
|
174
292
|
conversation,
|
|
175
|
-
|
|
293
|
+
messages: [message],
|
|
294
|
+
...(advances ? { read: message.position } : {}),
|
|
295
|
+
sealed,
|
|
176
296
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await local.dropInstruction(conversation);
|
|
297
|
+
messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
|
|
298
|
+
if (advances)
|
|
299
|
+
reads.set(conversation, message.position);
|
|
300
|
+
if (sealed)
|
|
301
|
+
drafts.delete(conversation);
|
|
302
|
+
sealTurns(conversation, new Set([message.turn]));
|
|
303
|
+
deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
|
|
185
304
|
});
|
|
186
|
-
emit(conversation);
|
|
187
|
-
}
|
|
188
|
-
function supersede(conversation) {
|
|
189
|
-
issued.set(conversation, (issued.get(conversation) ?? 0) + 1);
|
|
190
305
|
}
|
|
191
|
-
async function
|
|
192
|
-
const
|
|
193
|
-
const
|
|
306
|
+
async function readHistories(conversations) {
|
|
307
|
+
const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
|
|
308
|
+
const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
|
|
309
|
+
const arriving = new Map();
|
|
310
|
+
const complete = new Set();
|
|
311
|
+
let asking = conversations.map((conversation) => ({
|
|
194
312
|
conversation,
|
|
195
|
-
|
|
196
|
-
|
|
313
|
+
after: starts.get(conversation) ?? undefined,
|
|
314
|
+
}));
|
|
315
|
+
while (asking.length > 0) {
|
|
316
|
+
const result = await deps.call('conversation.history', {
|
|
317
|
+
conversations: asking.map(({ conversation, after }) => ({
|
|
318
|
+
conversation,
|
|
319
|
+
...(after === undefined ? {} : { after }),
|
|
320
|
+
})),
|
|
321
|
+
});
|
|
322
|
+
const pages = historyPages(result);
|
|
323
|
+
if (pages === null)
|
|
324
|
+
return result;
|
|
325
|
+
const asked = new Set(asking.map(({ conversation }) => conversation));
|
|
326
|
+
const following = [];
|
|
327
|
+
for (const page of pages) {
|
|
328
|
+
if (!asked.has(page.conversation))
|
|
329
|
+
continue;
|
|
330
|
+
arriving.set(page.conversation, [
|
|
331
|
+
...(arriving.get(page.conversation) ?? []),
|
|
332
|
+
...page.messages,
|
|
333
|
+
]);
|
|
334
|
+
if (page.next === undefined)
|
|
335
|
+
complete.add(page.conversation);
|
|
336
|
+
else
|
|
337
|
+
following.push({ conversation: page.conversation, after: page.next });
|
|
338
|
+
}
|
|
339
|
+
asking = following;
|
|
340
|
+
}
|
|
341
|
+
for (const [conversation, held] of arriving) {
|
|
342
|
+
if (!complete.has(conversation))
|
|
343
|
+
continue;
|
|
344
|
+
await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
function keepHistory(conversation, arriving, from, generation) {
|
|
349
|
+
return deps.durably(async (local) => {
|
|
350
|
+
if (!replicated.has(conversation))
|
|
351
|
+
return;
|
|
352
|
+
const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
|
|
353
|
+
const turn = drafts.get(conversation)?.turn;
|
|
354
|
+
const sealed = arriving.some((message) => message.turn === turn);
|
|
355
|
+
await local.keepMessages({ conversation, messages: arriving, read: newest, sealed });
|
|
356
|
+
messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
|
|
357
|
+
reads.set(conversation, newest);
|
|
358
|
+
if (sealed)
|
|
359
|
+
drafts.delete(conversation);
|
|
360
|
+
sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
|
|
361
|
+
if ((gaps.get(conversation) ?? 0) === generation)
|
|
362
|
+
behind.delete(conversation);
|
|
363
|
+
if (arriving.length > 0 || sealed) {
|
|
364
|
+
deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
|
|
197
365
|
}
|
|
198
366
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
367
|
+
}
|
|
368
|
+
function withDraft(conversation, next) {
|
|
369
|
+
return deps.durably(async (local) => {
|
|
370
|
+
const following = next(drafts.get(conversation));
|
|
371
|
+
if (following === undefined)
|
|
372
|
+
return;
|
|
373
|
+
if (following === null) {
|
|
374
|
+
await local.dropChatDraft(conversation);
|
|
375
|
+
drafts.delete(conversation);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
await local.keepChatDraft(following);
|
|
379
|
+
drafts.set(conversation, following);
|
|
380
|
+
}
|
|
381
|
+
deps.publish(conversationChangeSet([], [conversation]));
|
|
205
382
|
});
|
|
206
|
-
if (landed && cursor === null) live.delete(conversation);
|
|
207
|
-
return result;
|
|
208
383
|
}
|
|
209
384
|
function invoke(name, conversation, extra = {}) {
|
|
210
|
-
return deps.call(name, {
|
|
211
|
-
conversation,
|
|
212
|
-
...extra
|
|
213
|
-
});
|
|
385
|
+
return deps.call(name, { conversation, ...extra });
|
|
214
386
|
}
|
|
215
387
|
const surface = {
|
|
216
|
-
list
|
|
388
|
+
async list() {
|
|
217
389
|
const declaration = deps.cards();
|
|
218
|
-
if (declaration === undefined)
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
card,
|
|
229
|
-
agent: agent ?? null
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
entries.sort((left, right)=>left.conversation < right.conversation ? -1 : 1);
|
|
233
|
-
return Promise.resolve(entries);
|
|
234
|
-
},
|
|
235
|
-
read (conversation) {
|
|
236
|
-
return deps.durably(()=>Promise.resolve(viewOf(conversation)));
|
|
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
|
-
}));
|
|
390
|
+
if (declaration === undefined)
|
|
391
|
+
return [];
|
|
392
|
+
const documents = await documented();
|
|
393
|
+
const entries = carded(documents).map(({ conversation, card }) => ({
|
|
394
|
+
conversation,
|
|
395
|
+
card,
|
|
396
|
+
agent: documents.find((document) => document.backlinks.some((backlink) => backlink.path === card.path && backlink.field === declaration.agent)) ?? null,
|
|
397
|
+
}));
|
|
398
|
+
entries.sort((left, right) => (left.conversation < right.conversation ? -1 : 1));
|
|
399
|
+
return entries;
|
|
251
400
|
},
|
|
252
|
-
|
|
253
|
-
|
|
401
|
+
async read(conversation, options = {}) {
|
|
402
|
+
const limit = limitOf(options);
|
|
403
|
+
return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
|
|
254
404
|
},
|
|
255
|
-
create
|
|
405
|
+
create(input) {
|
|
256
406
|
return deps.call('conversation.create', input);
|
|
257
407
|
},
|
|
258
|
-
async send
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
408
|
+
async send(conversation) {
|
|
409
|
+
const draft = drafts.get(conversation);
|
|
410
|
+
const pending = draft === undefined ? undefined : { draft };
|
|
411
|
+
if (pending !== undefined) {
|
|
412
|
+
sending.set(conversation, pending);
|
|
413
|
+
deps.publish(conversationChangeSet([], [conversation]));
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
|
|
417
|
+
...(await declaredFile(file)),
|
|
418
|
+
blob: file.blob,
|
|
419
|
+
})));
|
|
420
|
+
const result = await invoke('conversation.send', conversation, {
|
|
421
|
+
text: draft?.text ?? '',
|
|
422
|
+
files,
|
|
423
|
+
});
|
|
424
|
+
const turn = turnOf(result);
|
|
425
|
+
if (turn === null || draft === undefined)
|
|
426
|
+
return result;
|
|
427
|
+
await withDraft(conversation, (current) => {
|
|
428
|
+
if (current !== draft)
|
|
429
|
+
return undefined;
|
|
430
|
+
const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
|
|
431
|
+
return sealed ? null : { ...draft, turn };
|
|
432
|
+
});
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
if (pending !== undefined && sending.get(conversation) === pending) {
|
|
437
|
+
sending.delete(conversation);
|
|
438
|
+
if (drafts.get(conversation) === draft) {
|
|
439
|
+
deps.publish(conversationChangeSet([], [conversation]));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
272
443
|
},
|
|
273
|
-
answer
|
|
274
|
-
return invoke('conversation.answer', conversation, {
|
|
275
|
-
answer
|
|
276
|
-
});
|
|
444
|
+
answer(conversation, answer) {
|
|
445
|
+
return invoke('conversation.answer', conversation, { answer });
|
|
277
446
|
},
|
|
278
|
-
interrupt
|
|
447
|
+
interrupt(conversation) {
|
|
279
448
|
return invoke('conversation.interrupt', conversation);
|
|
280
449
|
},
|
|
281
|
-
kill
|
|
450
|
+
kill(conversation) {
|
|
282
451
|
return invoke('conversation.kill', conversation);
|
|
283
452
|
},
|
|
284
|
-
async cancel
|
|
285
|
-
const result = await invoke('conversation.cancel', conversation, {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (instructionOf(conversation).turn === turn) {
|
|
290
|
-
await keepInstruction(conversation, (stored)=>({
|
|
291
|
-
...stored,
|
|
292
|
-
turn: null
|
|
293
|
-
}));
|
|
294
|
-
}
|
|
453
|
+
async cancel(conversation, turn) {
|
|
454
|
+
const result = await invoke('conversation.cancel', conversation, { turn });
|
|
455
|
+
if (result.kind !== 'action')
|
|
456
|
+
return result;
|
|
457
|
+
await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
|
|
295
458
|
return result;
|
|
296
459
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
460
|
+
delete(conversation) {
|
|
461
|
+
return invoke('conversation.delete', conversation);
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
const chatDraft = {
|
|
465
|
+
write(conversation, draft) {
|
|
466
|
+
return withDraft(conversation, () => ({
|
|
467
|
+
conversation,
|
|
468
|
+
text: draft.text,
|
|
469
|
+
files: [...draft.files],
|
|
470
|
+
turn: null,
|
|
471
|
+
}));
|
|
472
|
+
},
|
|
473
|
+
discard(conversation) {
|
|
474
|
+
return withDraft(conversation, (current) => (current === undefined ? undefined : null));
|
|
475
|
+
},
|
|
312
476
|
};
|
|
313
477
|
return {
|
|
314
478
|
surface,
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
479
|
+
chatDraft,
|
|
480
|
+
hydrate(held, stored) {
|
|
481
|
+
messages.clear();
|
|
482
|
+
reads.clear();
|
|
483
|
+
for (const view of views.values())
|
|
484
|
+
view.messages = null;
|
|
485
|
+
for (const entry of held) {
|
|
486
|
+
messages.set(entry.conversation, [...entry.messages].sort(ordered));
|
|
487
|
+
if (entry.read !== undefined)
|
|
488
|
+
reads.set(entry.conversation, entry.read);
|
|
323
489
|
}
|
|
324
|
-
|
|
325
|
-
for (const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
490
|
+
drafts.clear();
|
|
491
|
+
for (const record of stored)
|
|
492
|
+
drafts.set(record.conversation, record);
|
|
493
|
+
},
|
|
494
|
+
async reconcile() {
|
|
495
|
+
const named = new Set(carded(await documented()).map((entry) => entry.conversation));
|
|
496
|
+
const leaving = [
|
|
497
|
+
...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
|
|
498
|
+
].filter((conversation) => !named.has(conversation));
|
|
499
|
+
for (const conversation of named) {
|
|
500
|
+
if (!replicated.has(conversation))
|
|
501
|
+
behind.add(conversation);
|
|
331
502
|
}
|
|
503
|
+
replicated = named;
|
|
504
|
+
handReplicated();
|
|
505
|
+
if (leaving.length > 0)
|
|
506
|
+
await remove(leaving);
|
|
332
507
|
},
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
508
|
+
async catchUp(progress) {
|
|
509
|
+
const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
|
|
510
|
+
if (reading.length > 0 && !deps.closed()) {
|
|
511
|
+
const failed = await readHistories(reading);
|
|
512
|
+
if (failed?.kind === 'transport-uncertainty')
|
|
513
|
+
progress.reachable = false;
|
|
514
|
+
}
|
|
515
|
+
if (behind.size > 0)
|
|
516
|
+
progress.complete = false;
|
|
517
|
+
},
|
|
518
|
+
current() {
|
|
519
|
+
return behind.size === 0;
|
|
520
|
+
},
|
|
521
|
+
reconnected() {
|
|
522
|
+
for (const conversation of replicated)
|
|
523
|
+
gap(conversation);
|
|
524
|
+
},
|
|
525
|
+
watch(conversation, options, deliver) {
|
|
526
|
+
const limit = limitOf(options);
|
|
527
|
+
const key = JSON.stringify([conversation, limit ?? null]);
|
|
528
|
+
const view = views.get(key) ?? {
|
|
336
529
|
conversation,
|
|
337
|
-
|
|
338
|
-
|
|
530
|
+
limit,
|
|
531
|
+
watchers: new Set(),
|
|
532
|
+
messages: null,
|
|
339
533
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return ()=>{
|
|
353
|
-
if (!
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
hand();
|
|
534
|
+
views.set(key, view);
|
|
535
|
+
const watcher = { notify: deliver, ready: false };
|
|
536
|
+
view.watchers.add(watcher);
|
|
537
|
+
void deps
|
|
538
|
+
.durably(() => Promise.resolve())
|
|
539
|
+
.then(() => {
|
|
540
|
+
if (deps.closed() || !view.watchers.has(watcher))
|
|
541
|
+
return;
|
|
542
|
+
watcher.ready = true;
|
|
543
|
+
watcher.notify(viewOf(conversation, limit));
|
|
544
|
+
})
|
|
545
|
+
.catch(() => { });
|
|
546
|
+
return () => {
|
|
547
|
+
if (!view.watchers.delete(watcher))
|
|
548
|
+
return;
|
|
549
|
+
if (view.watchers.size === 0)
|
|
550
|
+
views.delete(key);
|
|
359
551
|
};
|
|
360
552
|
},
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
553
|
+
changed(set) {
|
|
554
|
+
for (const conversation of set.conversations)
|
|
555
|
+
emit(conversation);
|
|
556
|
+
},
|
|
557
|
+
receive(event) {
|
|
558
|
+
if (deps.closed())
|
|
559
|
+
return;
|
|
560
|
+
const { conversation } = event;
|
|
561
|
+
if (event.kind === 'gap') {
|
|
562
|
+
if (replicated.has(conversation))
|
|
563
|
+
gap(conversation);
|
|
564
|
+
const held = lives.get(conversation);
|
|
565
|
+
if (held === undefined)
|
|
566
|
+
return;
|
|
567
|
+
lives.set(conversation, { ...QUIET, turn: held.turn });
|
|
568
|
+
emit(conversation, false);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (event.kind === 'turn') {
|
|
572
|
+
relayed(conversation, event.turn, event.operation);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (event.kind === 'questions') {
|
|
576
|
+
live(conversation, { questions: structuredClone(event.questions) });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (event.kind === 'process') {
|
|
580
|
+
live(conversation, { process: { running: event.running } });
|
|
369
581
|
return;
|
|
370
582
|
}
|
|
371
|
-
if (event.kind === '
|
|
372
|
-
|
|
373
|
-
void keep(event.conversation, (carried)=>({
|
|
374
|
-
...carried,
|
|
375
|
-
summary
|
|
376
|
-
})).catch(()=>{});
|
|
583
|
+
if (event.kind === 'commands') {
|
|
584
|
+
live(conversation, { commands: structuredClone(event.commands) });
|
|
377
585
|
return;
|
|
378
586
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (growing.get(event.conversation)?.turn === arriving.turn) {
|
|
383
|
-
growing.delete(event.conversation);
|
|
587
|
+
if (event.kind === 'options') {
|
|
588
|
+
live(conversation, { options: structuredClone(event.options) });
|
|
589
|
+
return;
|
|
384
590
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
591
|
+
const arriving = acceptedMessage(event.message);
|
|
592
|
+
if (arriving === null)
|
|
593
|
+
return;
|
|
594
|
+
if (replicated.has(conversation)) {
|
|
595
|
+
void keep(conversation, arriving).catch(() => { });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (sealTurns(conversation, new Set([arriving.turn])))
|
|
599
|
+
emit(conversation);
|
|
600
|
+
},
|
|
601
|
+
messages() {
|
|
602
|
+
return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
|
|
603
|
+
},
|
|
604
|
+
message(conversation, identity) {
|
|
605
|
+
const held = messages.get(conversation)?.find((message) => message.identity === identity);
|
|
606
|
+
return held === undefined ? null : { conversation, identity, text: held.text };
|
|
607
|
+
},
|
|
608
|
+
close() {
|
|
609
|
+
views.clear();
|
|
610
|
+
lives.clear();
|
|
392
611
|
},
|
|
393
|
-
close () {
|
|
394
|
-
viewers.clear();
|
|
395
|
-
growing.clear();
|
|
396
|
-
live.clear();
|
|
397
|
-
}
|
|
398
612
|
};
|
|
399
613
|
}
|
|
400
|
-
|
|
401
|
-
//# sourceMappingURL=conversation.js.map
|