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