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