@agentshouse/kit 0.1.0-alpha.1 → 0.1.0-alpha.10
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/README.md +86 -6
- package/bin/connect-linux.sh +58 -12
- package/bin/kit.js +1 -1
- package/bootstrap.json +17 -0
- package/package.json +6 -2
- package/src/agents.js +63 -8
- package/src/contract.js +3 -3
- package/src/house.js +197 -7
- package/src/kit-work-stream.js +84 -0
- package/src/mount.js +7 -7
- package/src/setup.js +7 -2
- package/src/source.js +10 -6
- package/src/sync-state.js +4 -0
- package/src/sync.js +11 -6
- package/src/telegram.js +24 -8
- package/src/worker.js +197 -25
package/src/house.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
1
|
+
import { chmod, mkdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { connect, createServer } from "node:net";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
3
4
|
import { assertAcquiredCommandAdapter } from "./adapter.js";
|
|
5
|
+
import { readFrames, writeFrame } from "./channel.js";
|
|
4
6
|
import {
|
|
5
7
|
assistantDraftsPath,
|
|
6
8
|
assistantTextPath,
|
|
@@ -35,6 +37,8 @@ import { declarationReader } from "./declaration.js";
|
|
|
35
37
|
import { houseRefusal, KitRefusal, throwHouseAnswer } from "./refusal.js";
|
|
36
38
|
|
|
37
39
|
const LEASE = /^ahl_[A-Za-z0-9_-]+$/;
|
|
40
|
+
const RESIDENT = "Resident";
|
|
41
|
+
const LEASE_ACTIVE = "environment_control_lease_active";
|
|
38
42
|
const ASSISTANT_DRAFT_STATES = ["streamed", "unsupported"];
|
|
39
43
|
const ITEM_OUTCOMES = ["accepted", "duplicate", "rejected"];
|
|
40
44
|
|
|
@@ -105,6 +109,172 @@ function heldLease(answer) {
|
|
|
105
109
|
return { lease: answer.lease, expiresAt: Date.parse(answer.expires_at) };
|
|
106
110
|
}
|
|
107
111
|
|
|
112
|
+
function residentSocket(directory) {
|
|
113
|
+
return join(directory, "run", "resident.sock");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function residentAnswer(status, code) {
|
|
117
|
+
const body = Buffer.from(JSON.stringify({ error: { code } })).toString("base64");
|
|
118
|
+
return { status, type: "application/json", body };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function residentRelay(session, request, signal) {
|
|
122
|
+
const path = typeof request.path === "string" ? request.path : "";
|
|
123
|
+
if (!path.startsWith("/kit/") || path.startsWith("/kit/lease")) {
|
|
124
|
+
return residentAnswer(403, "kit_resident_route_refused");
|
|
125
|
+
}
|
|
126
|
+
let authorization;
|
|
127
|
+
try {
|
|
128
|
+
authorization = await heldControl(session, signal);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return residentAnswer(error.status ?? 401, error.code ?? "kit_lease_rejected");
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const response = await (session.dependencies.fetch ?? fetch)(`${session.house}${path}`, {
|
|
134
|
+
method: request.method,
|
|
135
|
+
headers: { ...request.headers, authorization },
|
|
136
|
+
body: request.text ?? (request.bytes === undefined ? undefined : Buffer.from(request.bytes, "base64")),
|
|
137
|
+
signal,
|
|
138
|
+
});
|
|
139
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
140
|
+
return {
|
|
141
|
+
status: response.status,
|
|
142
|
+
type: response.headers.get("content-type"),
|
|
143
|
+
body: body.length === 0 ? null : body.toString("base64"),
|
|
144
|
+
};
|
|
145
|
+
} catch {
|
|
146
|
+
return residentAnswer(503, "kit_house_unavailable");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function serveResident(session, directory, signal) {
|
|
151
|
+
const socket = residentSocket(directory);
|
|
152
|
+
await mkdir(dirname(socket), { recursive: true, mode: 0o700 });
|
|
153
|
+
await rm(socket, { force: true });
|
|
154
|
+
let holder = null;
|
|
155
|
+
const server = createServer((connection) => {
|
|
156
|
+
connection.on("error", () => connection.destroy());
|
|
157
|
+
if (holder !== null) {
|
|
158
|
+
writeFrame(connection, { refused: LEASE_ACTIVE });
|
|
159
|
+
connection.end();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
holder = connection;
|
|
163
|
+
const relayed = new Map();
|
|
164
|
+
connection.on("close", () => {
|
|
165
|
+
if (holder === connection) holder = null;
|
|
166
|
+
for (const cancelled of relayed.values()) cancelled.abort();
|
|
167
|
+
});
|
|
168
|
+
renewControl(session, signal).then(
|
|
169
|
+
() => {
|
|
170
|
+
writeFrame(connection, { held: true });
|
|
171
|
+
readFrames(connection, {
|
|
172
|
+
onFrame: (request) => {
|
|
173
|
+
if (request.cancel === true) {
|
|
174
|
+
relayed.get(request.id)?.abort();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const cancelled = new AbortController();
|
|
178
|
+
relayed.set(request.id, cancelled);
|
|
179
|
+
void residentRelay(session, request, AbortSignal.any([signal, cancelled.signal])).then(
|
|
180
|
+
(answer) => {
|
|
181
|
+
relayed.delete(request.id);
|
|
182
|
+
if (!connection.destroyed) writeFrame(connection, { id: request.id, ...answer });
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
onRefusal: () => connection.destroy(),
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
(error) => {
|
|
190
|
+
writeFrame(connection, { refused: error.code ?? "kit_lease_rejected" });
|
|
191
|
+
connection.end();
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
await new Promise((resolve, reject) => {
|
|
196
|
+
server.once("error", reject);
|
|
197
|
+
server.listen(socket, resolve);
|
|
198
|
+
});
|
|
199
|
+
await chmod(socket, 0o600);
|
|
200
|
+
return async () => {
|
|
201
|
+
holder?.destroy();
|
|
202
|
+
await new Promise((resolve) => server.close(resolve));
|
|
203
|
+
await rm(socket, { force: true });
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function residentDelegation(directory, fetcher) {
|
|
208
|
+
const connection = connect(residentSocket(directory));
|
|
209
|
+
const pending = new Map();
|
|
210
|
+
let greet;
|
|
211
|
+
const greeting = new Promise((resolve) => {
|
|
212
|
+
greet = resolve;
|
|
213
|
+
});
|
|
214
|
+
const unavailable = () => residentAnswer(503, "kit_resident_unavailable");
|
|
215
|
+
function settle(id, answer) {
|
|
216
|
+
pending.get(id)?.(answer);
|
|
217
|
+
pending.delete(id);
|
|
218
|
+
if (pending.size === 0) connection.unref();
|
|
219
|
+
}
|
|
220
|
+
connection.on("error", () => connection.destroy());
|
|
221
|
+
connection.on("close", () => {
|
|
222
|
+
greet(null);
|
|
223
|
+
for (const id of [...pending.keys()]) settle(id, unavailable());
|
|
224
|
+
});
|
|
225
|
+
readFrames(connection, {
|
|
226
|
+
onFrame: (frame) => (frame.id === undefined ? greet(frame) : settle(frame.id, frame)),
|
|
227
|
+
onRefusal: () => connection.destroy(),
|
|
228
|
+
});
|
|
229
|
+
const held = await greeting;
|
|
230
|
+
if (held === null) return null;
|
|
231
|
+
if (held.held !== true) {
|
|
232
|
+
connection.destroy();
|
|
233
|
+
throw new KitRefusal(held.refused);
|
|
234
|
+
}
|
|
235
|
+
connection.unref();
|
|
236
|
+
let next = 0;
|
|
237
|
+
async function delegated(url, init = {}) {
|
|
238
|
+
const { authorization, ...headers } = init.headers ?? {};
|
|
239
|
+
if (authorization !== RESIDENT) return fetcher(url, init);
|
|
240
|
+
init.signal?.throwIfAborted();
|
|
241
|
+
const address = new URL(url);
|
|
242
|
+
const answer = connection.destroyed
|
|
243
|
+
? unavailable()
|
|
244
|
+
: await new Promise((resolve, reject) => {
|
|
245
|
+
const id = (next += 1);
|
|
246
|
+
const cancel = () => {
|
|
247
|
+
pending.delete(id);
|
|
248
|
+
settle(id);
|
|
249
|
+
if (!connection.destroyed) writeFrame(connection, { id, cancel: true });
|
|
250
|
+
reject(init.signal.reason);
|
|
251
|
+
};
|
|
252
|
+
init.signal?.addEventListener("abort", cancel, { once: true });
|
|
253
|
+
pending.set(id, (answer) => {
|
|
254
|
+
init.signal?.removeEventListener("abort", cancel);
|
|
255
|
+
resolve(answer);
|
|
256
|
+
});
|
|
257
|
+
connection.ref();
|
|
258
|
+
writeFrame(connection, {
|
|
259
|
+
id,
|
|
260
|
+
method: init.method ?? "GET",
|
|
261
|
+
path: `${address.pathname}${address.search}`,
|
|
262
|
+
headers,
|
|
263
|
+
...(typeof init.body === "string"
|
|
264
|
+
? { text: init.body }
|
|
265
|
+
: init.body === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: { bytes: Buffer.from(init.body).toString("base64") }),
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
return new Response(answer.body === null ? null : Buffer.from(answer.body, "base64"), {
|
|
271
|
+
status: answer.status,
|
|
272
|
+
headers: answer.type === null ? {} : { "content-type": answer.type },
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return { connection, fetch: delegated };
|
|
276
|
+
}
|
|
277
|
+
|
|
108
278
|
export async function openSession(options, dependencies = {}) {
|
|
109
279
|
const session = {
|
|
110
280
|
house: options.house,
|
|
@@ -114,6 +284,15 @@ export async function openSession(options, dependencies = {}) {
|
|
|
114
284
|
expiresAt: 0,
|
|
115
285
|
signal: options.signal,
|
|
116
286
|
};
|
|
287
|
+
const resident =
|
|
288
|
+
options.acquire === true
|
|
289
|
+
? null
|
|
290
|
+
: await residentDelegation(options.directory, dependencies.fetch ?? fetch);
|
|
291
|
+
if (resident !== null) {
|
|
292
|
+
session.resident = resident.connection;
|
|
293
|
+
session.dependencies = { ...dependencies, fetch: resident.fetch };
|
|
294
|
+
return session;
|
|
295
|
+
}
|
|
117
296
|
const credential = await readCredential(options.directory);
|
|
118
297
|
Object.assign(
|
|
119
298
|
session,
|
|
@@ -131,10 +310,7 @@ export async function openSession(options, dependencies = {}) {
|
|
|
131
310
|
return session;
|
|
132
311
|
}
|
|
133
312
|
|
|
134
|
-
|
|
135
|
-
if (session.expiresAt - session.now().getTime() > LEASE_RENEWAL_MARGIN_MS) {
|
|
136
|
-
return `Bearer ${session.lease}`;
|
|
137
|
-
}
|
|
313
|
+
async function renewControl(session, signal) {
|
|
138
314
|
Object.assign(
|
|
139
315
|
session,
|
|
140
316
|
heldLease(
|
|
@@ -148,6 +324,13 @@ export async function heldControl(session, signal = session.signal) {
|
|
|
148
324
|
),
|
|
149
325
|
),
|
|
150
326
|
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function heldControl(session, signal = session.signal) {
|
|
330
|
+
if (session.resident !== undefined) return RESIDENT;
|
|
331
|
+
if (session.expiresAt - session.now().getTime() <= LEASE_RENEWAL_MARGIN_MS) {
|
|
332
|
+
await renewControl(session, signal);
|
|
333
|
+
}
|
|
151
334
|
return `Bearer ${session.lease}`;
|
|
152
335
|
}
|
|
153
336
|
|
|
@@ -195,6 +378,7 @@ export function mutatingCall(classification) {
|
|
|
195
378
|
}
|
|
196
379
|
|
|
197
380
|
export async function releaseControl(session, options = {}) {
|
|
381
|
+
session.resident?.end();
|
|
198
382
|
if (session.lease === null) return;
|
|
199
383
|
const held = session.lease;
|
|
200
384
|
try {
|
|
@@ -229,6 +413,7 @@ export async function maintainControl(session, operation, signal, { release = tr
|
|
|
229
413
|
signal?.addEventListener("abort", cancelOperation, { once: true });
|
|
230
414
|
});
|
|
231
415
|
const renewal = (async () => {
|
|
416
|
+
if (session.resident !== undefined) return;
|
|
232
417
|
const sleep = session.dependencies.controlSleep ?? controlSleep;
|
|
233
418
|
while (!stopped) {
|
|
234
419
|
const delay = Math.max(
|
|
@@ -480,6 +665,11 @@ export async function reportLogin(session, login, outcome, signal = session.sign
|
|
|
480
665
|
|
|
481
666
|
const envelope = declarationReader("kit_acquire_rejected");
|
|
482
667
|
|
|
668
|
+
function routineInput(value, declaration) {
|
|
669
|
+
if (typeof value !== "string" || Array.from(value).length > 20_000) envelope.refuse(declaration);
|
|
670
|
+
return value;
|
|
671
|
+
}
|
|
672
|
+
|
|
483
673
|
function acquiredRoute(value, declaration) {
|
|
484
674
|
const route = envelope.plain(value, declaration);
|
|
485
675
|
if (!Array.isArray(route.arguments)) envelope.refuse(`${declaration}.arguments`);
|
|
@@ -551,7 +741,7 @@ function acquiredRequest(acquired, declaration) {
|
|
|
551
741
|
name: envelope.text(routine.name, `${declaration}.routine.name`, 256),
|
|
552
742
|
version: envelope.positiveCount(routine.version, `${declaration}.routine.version`),
|
|
553
743
|
},
|
|
554
|
-
payload:
|
|
744
|
+
payload: routineInput(request.payload, `${declaration}.request.payload`),
|
|
555
745
|
requested_by: envelope.text(
|
|
556
746
|
request.requested_by,
|
|
557
747
|
`${declaration}.request.requested_by`,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { KIT_CONTRACT_VERSION } from "./contract.js";
|
|
4
|
+
import { heldControl } from "./house.js";
|
|
5
|
+
import { KitRefusal } from "./refusal.js";
|
|
6
|
+
|
|
7
|
+
const PROTOCOL = "house.kit.stream.1";
|
|
8
|
+
|
|
9
|
+
function streamAddress(house) {
|
|
10
|
+
const url = new URL("/kit/stream", house);
|
|
11
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
12
|
+
else if (url.protocol === "http:") url.protocol = "ws:";
|
|
13
|
+
else throw new KitRefusal("kit_house_unavailable", "/kit/stream");
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function connected(session, signal, available, dependencies) {
|
|
18
|
+
const authorization = await heldControl(session, signal);
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const Socket = dependencies.workSocket ?? WebSocket;
|
|
21
|
+
const socket = new Socket(streamAddress(session.house), PROTOCOL, {
|
|
22
|
+
headers: { authorization, "x-house-kit-version": KIT_CONTRACT_VERSION },
|
|
23
|
+
handshakeTimeout: 10_000,
|
|
24
|
+
maxPayload: 4096,
|
|
25
|
+
perMessageDeflate: false,
|
|
26
|
+
});
|
|
27
|
+
let finished = false;
|
|
28
|
+
const finish = (error = null) => {
|
|
29
|
+
if (finished) return;
|
|
30
|
+
finished = true;
|
|
31
|
+
signal.removeEventListener("abort", stop);
|
|
32
|
+
if (error === null) resolve();
|
|
33
|
+
else reject(error);
|
|
34
|
+
};
|
|
35
|
+
const stop = () => {
|
|
36
|
+
socket.terminate();
|
|
37
|
+
finish();
|
|
38
|
+
};
|
|
39
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
40
|
+
socket.on("open", available);
|
|
41
|
+
socket.on("message", (data) => {
|
|
42
|
+
let frame;
|
|
43
|
+
try {
|
|
44
|
+
frame = JSON.parse(data.toString());
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (frame?.type === "work_available" && frame.subject === "agents") available();
|
|
49
|
+
});
|
|
50
|
+
socket.on("unexpected-response", (_request, response) => {
|
|
51
|
+
response.resume();
|
|
52
|
+
const refusal =
|
|
53
|
+
response.statusCode === 409 ? new KitRefusal("kit_upgrade_required") : null;
|
|
54
|
+
socket.terminate();
|
|
55
|
+
finish(refusal);
|
|
56
|
+
});
|
|
57
|
+
socket.on("error", () => {
|
|
58
|
+
socket.terminate();
|
|
59
|
+
finish();
|
|
60
|
+
});
|
|
61
|
+
socket.on("close", () => finish());
|
|
62
|
+
if (signal.aborted) stop();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function watchKitAgentWork(session, signal, available, dependencies = {}) {
|
|
67
|
+
let retryMs = 500;
|
|
68
|
+
while (!signal.aborted) {
|
|
69
|
+
const began = Date.now();
|
|
70
|
+
try {
|
|
71
|
+
await connected(session, signal, available, dependencies);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof KitRefusal && error.code === "kit_upgrade_required") throw error;
|
|
74
|
+
if (signal.aborted) return;
|
|
75
|
+
}
|
|
76
|
+
if (signal.aborted) return;
|
|
77
|
+
retryMs = Date.now() - began >= 30_000 ? 1000 : Math.min(retryMs * 2, 30_000);
|
|
78
|
+
try {
|
|
79
|
+
await delay(dependencies.workReconnectMs ?? retryMs, undefined, { signal });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (!signal.aborted) throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/mount.js
CHANGED
|
@@ -84,7 +84,6 @@ export function mountTarget(subtree) {
|
|
|
84
84
|
refuse("kit_mount_subtree_invalid", subtree);
|
|
85
85
|
}
|
|
86
86
|
return {
|
|
87
|
-
kind: own ? "private" : "room",
|
|
88
87
|
handle: own ? null : segments[1],
|
|
89
88
|
root: own ? "/private" : `/rooms/${segments[1]}`,
|
|
90
89
|
subtree,
|
|
@@ -107,10 +106,11 @@ function discoveryOf(value) {
|
|
|
107
106
|
|
|
108
107
|
function authorityFor(discovery, target) {
|
|
109
108
|
const found = discovery.authorities.find((entry) =>
|
|
110
|
-
|
|
109
|
+
entry.kind === "room" && entry.room_handle === target.handle,
|
|
111
110
|
);
|
|
112
111
|
if (found === undefined) refuse("kit_mount_authority_unknown", target.root);
|
|
113
112
|
if (
|
|
113
|
+
typeof found.room_ref !== "string" ||
|
|
114
114
|
!Array.isArray(found.protected_paths) ||
|
|
115
115
|
found.operations === null ||
|
|
116
116
|
typeof found.operations !== "object" ||
|
|
@@ -119,8 +119,8 @@ function authorityFor(discovery, target) {
|
|
|
119
119
|
refuse("kit_mount_rejected", "discover");
|
|
120
120
|
}
|
|
121
121
|
return {
|
|
122
|
-
kind:
|
|
123
|
-
authority: found.
|
|
122
|
+
kind: "room",
|
|
123
|
+
authority: found.room_ref,
|
|
124
124
|
path: target.root,
|
|
125
125
|
protected_paths: found.protected_paths,
|
|
126
126
|
operations: found.operations,
|
|
@@ -326,7 +326,7 @@ function pendingTransfer(held, save) {
|
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
async function submitAttachments(session, held,
|
|
329
|
+
async function submitAttachments(session, held, authority, plan, signal) {
|
|
330
330
|
const transfers = [];
|
|
331
331
|
for (const save of plan.saves) {
|
|
332
332
|
const local = held.attachments.get(save.path);
|
|
@@ -341,7 +341,7 @@ async function submitAttachments(session, held, target, authority, plan, signal)
|
|
|
341
341
|
version: save.version,
|
|
342
342
|
name: declared.name,
|
|
343
343
|
mediaType: declared.mediaType ?? undefined,
|
|
344
|
-
room:
|
|
344
|
+
room: authority.authority,
|
|
345
345
|
body,
|
|
346
346
|
signal,
|
|
347
347
|
});
|
|
@@ -471,7 +471,7 @@ export async function mountPackage(held, options, dependencies = {}) {
|
|
|
471
471
|
};
|
|
472
472
|
const localReport = join(held.root, "MIGRATION.json");
|
|
473
473
|
await writeFile(localReport, `${JSON.stringify(result, null, 2)}\n`);
|
|
474
|
-
result.transfers = await submitAttachments(session, held,
|
|
474
|
+
result.transfers = await submitAttachments(session, held, authority, plan, signal);
|
|
475
475
|
|
|
476
476
|
const report = `${target.subtree}/${MOUNT_REPORT_NAME}`;
|
|
477
477
|
await writeFile(localReport, `${JSON.stringify(result, null, 2)}\n`);
|
package/src/setup.js
CHANGED
|
@@ -114,14 +114,14 @@ async function signedIn(module) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
async function setUpModule(context, module) {
|
|
117
|
+
async function setUpModule(context, module, install = true) {
|
|
118
118
|
const summary = (status, reason = null) => ({
|
|
119
119
|
category: module.category,
|
|
120
120
|
name: module.name,
|
|
121
121
|
status,
|
|
122
122
|
reason,
|
|
123
123
|
});
|
|
124
|
-
if (module.install !== null) {
|
|
124
|
+
if (install && module.install !== null) {
|
|
125
125
|
try {
|
|
126
126
|
await module.install();
|
|
127
127
|
} catch (error) {
|
|
@@ -144,10 +144,15 @@ async function setUpModule(context, module) {
|
|
|
144
144
|
return summary("ready");
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
export function signInWantedAgent(context, kind) {
|
|
148
|
+
return setUpModule(context, agentModule(context, kind), false);
|
|
149
|
+
}
|
|
150
|
+
|
|
147
151
|
export async function runSetup(context, request) {
|
|
148
152
|
await prepareLaunchBase(launchBase(context.dependencies));
|
|
149
153
|
const modules = [];
|
|
150
154
|
for (const kind of context.configuration.agents) {
|
|
155
|
+
await context.waitAgentLogin?.(kind);
|
|
151
156
|
modules.push(await setUpModule(context, agentModule(context, kind)));
|
|
152
157
|
}
|
|
153
158
|
for (const instance of context.configuration.instances) {
|
package/src/source.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
newCheckpoint,
|
|
13
13
|
stallOf,
|
|
14
14
|
} from "./delivery.js";
|
|
15
|
-
import { captureBatch, openSession } from "./house.js";
|
|
15
|
+
import { captureBatch, maintainControl, openSession } from "./house.js";
|
|
16
16
|
import { googlePage } from "./google/source.js";
|
|
17
17
|
import { houseAnswered, houseAnsweredFailure, houseFailed, KitRefusal } from "./refusal.js";
|
|
18
18
|
import { recordAcceptedOriginals, sameReference, saveSourceOriginals } from "./source-originals.js";
|
|
@@ -278,9 +278,13 @@ export async function resetSource(options, dependencies = {}) {
|
|
|
278
278
|
const configuration = await readConfiguration(options.configuration);
|
|
279
279
|
const instance = sourceInstance(configuration, options.instance);
|
|
280
280
|
declaredServices(instance, options.service);
|
|
281
|
-
await openSession(options, dependencies);
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
const session = await openSession(options, dependencies);
|
|
282
|
+
return maintainControl(
|
|
283
|
+
session,
|
|
284
|
+
async () => ({
|
|
285
|
+
instance: instance.id,
|
|
286
|
+
services: await resetCheckpoints(options.directory, instance.id, options.service),
|
|
287
|
+
}),
|
|
288
|
+
options.signal,
|
|
289
|
+
);
|
|
286
290
|
}
|
package/src/sync-state.js
CHANGED
|
@@ -26,6 +26,9 @@ function syncFile(directory, key, kind = "") {
|
|
|
26
26
|
function recordedAuthority(authority) {
|
|
27
27
|
const operations = authority?.operations;
|
|
28
28
|
return (
|
|
29
|
+
authority?.kind === "room" &&
|
|
30
|
+
typeof authority.authority === "string" &&
|
|
31
|
+
typeof authority.path === "string" &&
|
|
29
32
|
typeof authority?.log_epoch === "string" &&
|
|
30
33
|
operations !== null &&
|
|
31
34
|
typeof operations === "object" &&
|
|
@@ -43,6 +46,7 @@ export async function readReplicaState(directory, replica) {
|
|
|
43
46
|
}
|
|
44
47
|
const state = await readNote(syncFile(directory, replicaKey(canonical)), null);
|
|
45
48
|
if (state === null) return null;
|
|
49
|
+
if (state.version !== EXPLICIT_SYNC_VERSION) return null;
|
|
46
50
|
if (!Array.isArray(state.authorities) || !state.authorities.every(recordedAuthority)) return null;
|
|
47
51
|
return state;
|
|
48
52
|
}
|
package/src/sync.js
CHANGED
|
@@ -105,9 +105,6 @@ async function bootstrapBundle(session, prefixes, signal) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function matchedDiscovery(discovery, scope) {
|
|
108
|
-
if (scope.manifest?.scope === "private") {
|
|
109
|
-
return discovery.authorities.find((entry) => entry.kind === "private");
|
|
110
|
-
}
|
|
111
108
|
return discovery.authorities.find(
|
|
112
109
|
(entry) => entry.kind === "room" && entry.room_ref === scope.manifest?.anchor,
|
|
113
110
|
);
|
|
@@ -173,11 +170,17 @@ function operationsOf(value) {
|
|
|
173
170
|
function authorityRecord(scope, discovery, files) {
|
|
174
171
|
const found = matchedDiscovery(discovery, scope);
|
|
175
172
|
const operations = found === undefined ? null : operationsOf(found.operations);
|
|
176
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
scope.manifest?.scope !== "room" ||
|
|
175
|
+
typeof scope.manifest.anchor !== "string" ||
|
|
176
|
+
found === undefined ||
|
|
177
|
+
!stringList(found.protected_paths) ||
|
|
178
|
+
operations === null
|
|
179
|
+
) {
|
|
177
180
|
throw new KitRefusal("kit_bootstrap_rejected");
|
|
178
181
|
}
|
|
179
182
|
return {
|
|
180
|
-
kind:
|
|
183
|
+
kind: "room",
|
|
181
184
|
authority: scope.manifest.anchor,
|
|
182
185
|
room_handle: scope.manifest.room_handle,
|
|
183
186
|
path: scope.manifest.path,
|
|
@@ -192,9 +195,10 @@ function authorityRecord(scope, discovery, files) {
|
|
|
192
195
|
async function materialize(options, discovery, bundle, signal, previous = null) {
|
|
193
196
|
const authorities = [];
|
|
194
197
|
for (const scope of bundle.scopes) {
|
|
198
|
+
const record = authorityRecord(scope, discovery, []);
|
|
195
199
|
const held = previous?.authorities.find((entry) => entry.authority === scope.manifest?.anchor);
|
|
196
200
|
const files = await replaceScope(options.replica, held, scope, signal);
|
|
197
|
-
authorities.push(
|
|
201
|
+
authorities.push({ ...record, files });
|
|
198
202
|
}
|
|
199
203
|
const state = {
|
|
200
204
|
version: EXPLICIT_SYNC_VERSION,
|
|
@@ -409,6 +413,7 @@ export async function bootstrap(options, dependencies = {}) {
|
|
|
409
413
|
const canonical = await canonicalReplica(options.replica);
|
|
410
414
|
const existing = await readReplicaState(options.directory, canonical);
|
|
411
415
|
if (existing !== null) throw new KitRefusal("kit_sync_already_bootstrapped", canonical);
|
|
416
|
+
await clearPending(options.directory, canonical);
|
|
412
417
|
return withSession(options, dependencies, async (session, signal) => {
|
|
413
418
|
const discovery = discoveryOf(
|
|
414
419
|
await door(session, KIT_DOOR_DISCOVER_PATH, { prefixes: options.prefixes }, signal),
|
package/src/telegram.js
CHANGED
|
@@ -955,14 +955,14 @@ async function proveOwner(gateway, token, telegramBotUserId) {
|
|
|
955
955
|
throw new KitRefusal("telegram_owner_not_proven");
|
|
956
956
|
}
|
|
957
957
|
|
|
958
|
-
function gatewayContext(session, options,
|
|
958
|
+
function gatewayContext(session, options, signal, summary = emptySummary()) {
|
|
959
959
|
return {
|
|
960
960
|
session,
|
|
961
961
|
directory: options.directory,
|
|
962
|
-
dependencies,
|
|
962
|
+
dependencies: session.dependencies,
|
|
963
963
|
signal,
|
|
964
964
|
gate: rateGate(() => session.now().getTime()),
|
|
965
|
-
wait: dependencies.wait ?? pause,
|
|
965
|
+
wait: session.dependencies.wait ?? pause,
|
|
966
966
|
passes: options.passes ?? Number.POSITIVE_INFINITY,
|
|
967
967
|
summary,
|
|
968
968
|
};
|
|
@@ -970,7 +970,15 @@ function gatewayContext(session, options, dependencies, signal, summary = emptyS
|
|
|
970
970
|
|
|
971
971
|
export async function connectPersonalBot(options, dependencies = {}) {
|
|
972
972
|
const session = await openSession(options, dependencies);
|
|
973
|
-
|
|
973
|
+
return maintainControl(
|
|
974
|
+
session,
|
|
975
|
+
(signal) => connectBot(session, options, dependencies, signal),
|
|
976
|
+
options.signal,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async function connectBot(session, options, dependencies, signal) {
|
|
981
|
+
const gateway = gatewayContext(session, options, signal);
|
|
974
982
|
const ask = dependencies.prompt ?? (() => askForToken(dependencies));
|
|
975
983
|
const token = (await ask()).trim();
|
|
976
984
|
if (token.length === 0) throw new KitRefusal("telegram_bot_token_required");
|
|
@@ -989,7 +997,7 @@ export async function connectPersonalBot(options, dependencies = {}) {
|
|
|
989
997
|
owner_telegram_user_id: await proveOwner(gateway, token, identity.telegramBotUserId),
|
|
990
998
|
has_topics_enabled: true,
|
|
991
999
|
};
|
|
992
|
-
await writeOwned(tokenPath(options.directory), `${token}\n`,
|
|
1000
|
+
await writeOwned(tokenPath(options.directory), `${token}\n`, signal);
|
|
993
1001
|
try {
|
|
994
1002
|
await houseCall(gateway, "personal-bot", setup);
|
|
995
1003
|
} catch (error) {
|
|
@@ -1001,7 +1009,15 @@ export async function connectPersonalBot(options, dependencies = {}) {
|
|
|
1001
1009
|
|
|
1002
1010
|
export async function disconnectPersonalBot(options, dependencies = {}) {
|
|
1003
1011
|
const session = await openSession(options, dependencies);
|
|
1004
|
-
|
|
1012
|
+
return maintainControl(
|
|
1013
|
+
session,
|
|
1014
|
+
(signal) => disconnectBot(session, options, signal),
|
|
1015
|
+
options.signal,
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
async function disconnectBot(session, options, signal) {
|
|
1020
|
+
const gateway = gatewayContext(session, options, signal);
|
|
1005
1021
|
const directory = options.directory;
|
|
1006
1022
|
const marked = await disconnecting(directory);
|
|
1007
1023
|
let token = null;
|
|
@@ -1010,7 +1026,7 @@ export async function disconnectPersonalBot(options, dependencies = {}) {
|
|
|
1010
1026
|
} catch (error) {
|
|
1011
1027
|
if (!marked) throw error;
|
|
1012
1028
|
}
|
|
1013
|
-
if (!marked) await writeOwned(disconnectPath(directory), "{}\n",
|
|
1029
|
+
if (!marked) await writeOwned(disconnectPath(directory), "{}\n", signal);
|
|
1014
1030
|
if (token !== null) {
|
|
1015
1031
|
await telegramCall(gateway, token, "deleteWebhook", { drop_pending_updates: false });
|
|
1016
1032
|
await Promise.all([
|
|
@@ -1032,7 +1048,7 @@ export async function serveTelegramGateway(options, dependencies = {}) {
|
|
|
1032
1048
|
async (signal) => {
|
|
1033
1049
|
const stopping = new AbortController();
|
|
1034
1050
|
const shared = AbortSignal.any([signal, stopping.signal]);
|
|
1035
|
-
const gateway = gatewayContext(session, options,
|
|
1051
|
+
const gateway = gatewayContext(session, options, shared, summary);
|
|
1036
1052
|
summary.abandoned = abandonedOf(await houseCall(gateway, "deliveries/abandoned"));
|
|
1037
1053
|
await serveActivities(gateway, stopping);
|
|
1038
1054
|
},
|