@agentshouse/kit 0.1.0-alpha.9 → 0.2.1-alpha.1

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.
Files changed (93) hide show
  1. package/README.md +14 -1740
  2. package/dist/acp.js +120 -0
  3. package/dist/agents.js +90 -0
  4. package/dist/api.js +45 -0
  5. package/dist/blocks.js +51 -0
  6. package/dist/bridge.js +124 -0
  7. package/dist/clis.js +110 -0
  8. package/dist/conversations.js +538 -0
  9. package/dist/files.js +21 -0
  10. package/dist/home.js +24 -0
  11. package/dist/house-main.js +92 -0
  12. package/dist/instructions.js +28 -0
  13. package/dist/kit-main.js +16 -0
  14. package/dist/login.js +112 -0
  15. package/dist/resident.js +66 -0
  16. package/dist/secret-input.js +16 -0
  17. package/dist/sign-in.js +83 -0
  18. package/dist/stream.js +47 -0
  19. package/package.json +34 -10
  20. package/bin/connect-linux.sh +0 -134
  21. package/bin/kit.js +0 -1023
  22. package/bootstrap.json +0 -17
  23. package/src/acp.js +0 -99
  24. package/src/adapter-mcp.js +0 -44
  25. package/src/adapter.js +0 -252
  26. package/src/agent-logins.js +0 -65
  27. package/src/agents.js +0 -504
  28. package/src/apply.js +0 -29
  29. package/src/attach.js +0 -52
  30. package/src/binding.js +0 -90
  31. package/src/blocks.js +0 -156
  32. package/src/bridge.js +0 -449
  33. package/src/bytes.js +0 -89
  34. package/src/channel.js +0 -37
  35. package/src/command.js +0 -109
  36. package/src/configuration.js +0 -465
  37. package/src/connector-login.js +0 -91
  38. package/src/connector.js +0 -107
  39. package/src/continuation.js +0 -61
  40. package/src/contract.js +0 -363
  41. package/src/conversation-originals.js +0 -143
  42. package/src/custom/authorization.js +0 -63
  43. package/src/declaration.js +0 -35
  44. package/src/delivery.js +0 -191
  45. package/src/directory.js +0 -102
  46. package/src/draft.js +0 -89
  47. package/src/google/calendar.js +0 -177
  48. package/src/google/declared.js +0 -146
  49. package/src/google/drive.js +0 -435
  50. package/src/google/gateway.js +0 -166
  51. package/src/google/gmail.js +0 -249
  52. package/src/google/id.js +0 -1
  53. package/src/google/oauth.js +0 -273
  54. package/src/google/source.js +0 -25
  55. package/src/hook.js +0 -376
  56. package/src/host.js +0 -121
  57. package/src/house-main.js +0 -79
  58. package/src/house.js +0 -1090
  59. package/src/inbound.js +0 -175
  60. package/src/instance/google.js +0 -1
  61. package/src/kit-work-stream.js +0 -84
  62. package/src/launcher-main.js +0 -19
  63. package/src/launcher.js +0 -276
  64. package/src/local-files.js +0 -113
  65. package/src/login.js +0 -274
  66. package/src/mcp.js +0 -71
  67. package/src/migrator.js +0 -248
  68. package/src/mount.js +0 -582
  69. package/src/operation.js +0 -20
  70. package/src/original.js +0 -631
  71. package/src/peer.js +0 -124
  72. package/src/plan.js +0 -57
  73. package/src/question.js +0 -316
  74. package/src/refusal.js +0 -79
  75. package/src/repository.js +0 -521
  76. package/src/session-bin/house +0 -2
  77. package/src/settlement.js +0 -57
  78. package/src/setup.js +0 -190
  79. package/src/source-originals.js +0 -116
  80. package/src/source.js +0 -286
  81. package/src/starter-set/grilling/LICENSE +0 -21
  82. package/src/starter-set/grilling/SKILL.md +0 -30
  83. package/src/starter-set/grilling/agents/openai.yaml +0 -3
  84. package/src/starter-set.js +0 -17
  85. package/src/state.js +0 -157
  86. package/src/sync-library.js +0 -293
  87. package/src/sync-state.js +0 -120
  88. package/src/sync.js +0 -482
  89. package/src/telegram.js +0 -1045
  90. package/src/terminal.js +0 -57
  91. package/src/tmux.js +0 -119
  92. package/src/worker.js +0 -925
  93. package/src/workspace.js +0 -500
package/src/repository.js DELETED
@@ -1,521 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
- import { constants } from "node:fs";
4
- import { open, readlink, realpath } from "node:fs/promises";
5
- import { join } from "node:path";
6
- import { deliverPublicationChunk, maintainControl, openPublicationManifest, openSession } from "./house.js";
7
- import { KitRefusal } from "./refusal.js";
8
- import { readRepositoryState, writeRepositoryState } from "./state.js";
9
-
10
- const BLOCK_BYTES = 512;
11
- const READ_BYTES = 64 * 1024;
12
- const MANIFEST_ROUNDS = 3;
13
- const USTAR_NAME_BYTES = 100;
14
- const PUBLICATION_REFERENCE = /^p_[0-9abcdefghjkmnpqrstvwxyz]+\.[0-9abcdefghjkmnpqrstvwxyz]$/;
15
- const SOURCE_HANDLE = /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/;
16
- const REFERENCE_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
17
- const MAX_REFERENCE_ORDINAL = (1n << 63n) - 1n;
18
- const ITEM_OUTCOMES = [
19
- "accepted",
20
- "duplicate",
21
- "path_not_in_manifest",
22
- "fingerprint_mismatch",
23
- "refused",
24
- ];
25
- const PUBLICATION_STATES = ["active", "completed"];
26
- const utf8 = new TextDecoder("utf-8", { fatal: true });
27
-
28
- function refuse(code, detail = null) {
29
- throw new KitRefusal(code, detail);
30
- }
31
-
32
- function active(signal) {
33
- if (signal?.aborted === true) refuse("kit_command_cancelled");
34
- }
35
-
36
- function validSourceReference(value) {
37
- if (typeof value !== "string") return false;
38
- const matched = /^s_([0-9a-z]+)\.([0-9a-z])$/.exec(value);
39
- if (matched === null || (matched[1].length > 1 && matched[1][0] === "0")) return false;
40
- let ordinal = 0n;
41
- for (const character of matched[1]) {
42
- const digit = REFERENCE_ALPHABET.indexOf(character);
43
- if (digit === -1) return false;
44
- ordinal = (ordinal << 5n) + BigInt(digit);
45
- if (ordinal > MAX_REFERENCE_ORDINAL) return false;
46
- }
47
- return ordinal > 0n && REFERENCE_ALPHABET.includes(matched[2]);
48
- }
49
-
50
- function validRepositoryPath(value) {
51
- return !(
52
- value.length === 0 ||
53
- value.startsWith("/") ||
54
- value.endsWith("/") ||
55
- value.includes("\\") ||
56
- value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")
57
- );
58
- }
59
-
60
- function repositoryPath(value) {
61
- if (!validRepositoryPath(value)) refuse("kit_repository_path_invalid", value);
62
- return value;
63
- }
64
-
65
- function stateKey(identity) {
66
- return createHash("sha256")
67
- .update(JSON.stringify([identity.checkout, identity.room, identity.sourceHandle]))
68
- .digest("hex");
69
- }
70
-
71
- function exactObject(value, keys) {
72
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
73
- const present = Object.keys(value).sort();
74
- return present.length === keys.length && present.every((key, index) => key === keys[index]);
75
- }
76
-
77
- function stateOf(value, identity) {
78
- if (value === null) {
79
- return {
80
- checkout: identity.checkout,
81
- room: identity.room,
82
- source_handle: identity.sourceHandle,
83
- source_ref: null,
84
- };
85
- }
86
- if (
87
- !exactObject(value, ["checkout", "room", "source_handle", "source_ref"]) ||
88
- value.checkout !== identity.checkout ||
89
- value.room !== identity.room ||
90
- value.source_handle !== identity.sourceHandle ||
91
- (value.source_ref !== null && !validSourceReference(value.source_ref))
92
- ) {
93
- refuse("kit_state_unreadable");
94
- }
95
- return {
96
- checkout: value.checkout,
97
- room: value.room,
98
- source_handle: value.source_handle,
99
- source_ref: value.source_ref,
100
- };
101
- }
102
-
103
- function octal(header, offset, length, value) {
104
- const encoded = value.toString(8).padStart(length - 1, "0");
105
- if (encoded.length >= length) refuse("kit_repository_archive_failed");
106
- header.write(encoded, offset, length - 1, "ascii");
107
- header[offset + length - 1] = 0;
108
- }
109
-
110
- function tarHeader(name, size, type) {
111
- const header = Buffer.alloc(BLOCK_BYTES);
112
- Buffer.from(name, "utf8").copy(header, 0, 0, USTAR_NAME_BYTES);
113
- octal(header, 100, 8, 0o644);
114
- octal(header, 108, 8, 0);
115
- octal(header, 116, 8, 0);
116
- octal(header, 124, 12, size);
117
- octal(header, 136, 12, 0);
118
- header.fill(32, 148, 156);
119
- header[156] = type.charCodeAt(0);
120
- header.write("ustar\0", 257, 6, "ascii");
121
- header.write("00", 263, 2, "ascii");
122
- const checksum = [...header]
123
- .reduce((sum, byte) => sum + byte, 0)
124
- .toString(8)
125
- .padStart(6, "0");
126
- header.write(checksum, 148, 6, "ascii");
127
- header[154] = 0;
128
- header[155] = 32;
129
- return header;
130
- }
131
-
132
- function paxRecord(name, value) {
133
- const content = `${name}=${value}\n`;
134
- const contentBytes = Buffer.byteLength(content);
135
- let length = contentBytes + 3;
136
- for (;;) {
137
- const next = String(length).length + 1 + contentBytes;
138
- if (next === length) return Buffer.from(`${length} ${content}`, "utf8");
139
- length = next;
140
- }
141
- }
142
-
143
- function padding(size) {
144
- const remainder = size % BLOCK_BYTES;
145
- return remainder === 0 ? Buffer.alloc(0) : Buffer.alloc(BLOCK_BYTES - remainder);
146
- }
147
-
148
- async function material(path, signal) {
149
- active(signal);
150
- let handle;
151
- try {
152
- handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
153
- } catch (error) {
154
- if (error.code === "ENOENT") return null;
155
- if (error.code === "ELOOP") {
156
- try {
157
- const body = await readlink(path, { encoding: "buffer" });
158
- active(signal);
159
- return { kind: "buffer", body };
160
- } catch {
161
- active(signal);
162
- refuse("kit_repository_checkout_unreadable", path);
163
- }
164
- }
165
- refuse("kit_repository_checkout_unreadable", path);
166
- }
167
- try {
168
- active(signal);
169
- const status = await handle.stat();
170
- active(signal);
171
- if (!status.isFile()) {
172
- await handle.close();
173
- return null;
174
- }
175
- return { kind: "file", size: status.size, handle };
176
- } catch (error) {
177
- await handle.close().catch(() => undefined);
178
- if (error instanceof KitRefusal) throw error;
179
- if (signal?.aborted === true || error?.name === "AbortError") {
180
- refuse("kit_command_cancelled");
181
- } else {
182
- refuse("kit_repository_checkout_unreadable", path);
183
- }
184
- }
185
- }
186
-
187
- async function* fileBody(selected, signal) {
188
- if (selected.kind === "buffer") {
189
- yield selected.body;
190
- return;
191
- }
192
- const chunk = Buffer.allocUnsafe(READ_BYTES);
193
- let read = 0;
194
- while (read < selected.size) {
195
- active(signal);
196
- const { bytesRead } = await selected.handle.read(
197
- chunk,
198
- 0,
199
- Math.min(chunk.length, selected.size - read),
200
- read,
201
- );
202
- if (bytesRead === 0) break;
203
- read += bytesRead;
204
- yield Buffer.from(chunk.subarray(0, bytesRead));
205
- }
206
- if (read < selected.size) yield Buffer.alloc(selected.size - read);
207
- }
208
-
209
- async function selectedFile(checkout, path, signal) {
210
- const selected = await material(join(checkout, path), signal);
211
- if (selected === null) return null;
212
- try {
213
- const digest = createHash("sha256");
214
- const size = selected.kind === "buffer" ? selected.body.length : selected.size;
215
- for await (const body of fileBody(selected, signal)) digest.update(body);
216
- return { path, fingerprint: digest.digest("hex"), bytes: size };
217
- } catch (error) {
218
- if (error instanceof KitRefusal) throw error;
219
- if (signal?.aborted === true || error?.name === "AbortError") {
220
- refuse("kit_command_cancelled");
221
- }
222
- refuse("kit_repository_checkout_unreadable", path);
223
- } finally {
224
- if (selected.kind === "file") await selected.handle.close().catch(() => undefined);
225
- }
226
- }
227
-
228
- async function manifestOf(checkout, paths, signal) {
229
- const files = [];
230
- for (const path of paths) {
231
- const held = await selectedFile(checkout, path, signal);
232
- if (held !== null) files.push(held);
233
- }
234
- return files;
235
- }
236
-
237
- async function* chunkArchive(checkout, entries, signal) {
238
- for (const entry of entries) {
239
- const selected = await material(join(checkout, entry.path), signal);
240
- if (selected === null) continue;
241
- try {
242
- const size = selected.kind === "buffer" ? selected.body.length : selected.size;
243
- if (Buffer.byteLength(entry.path, "utf8") > USTAR_NAME_BYTES) {
244
- const record = paxRecord("path", entry.path);
245
- yield tarHeader("PaxHeaders.X/entry", record.length, "x");
246
- yield record;
247
- yield padding(record.length);
248
- yield tarHeader("entry", size, "0");
249
- } else {
250
- yield tarHeader(entry.path, size, "0");
251
- }
252
- for await (const body of fileBody(selected, signal)) yield body;
253
- yield padding(size);
254
- } finally {
255
- if (selected.kind === "file") await selected.handle.close().catch(() => undefined);
256
- }
257
- }
258
- yield Buffer.alloc(BLOCK_BYTES * 2);
259
- }
260
-
261
- function runGit(checkout, args, dependencies, capture, signal) {
262
- active(signal);
263
- const launcher = dependencies.spawn ?? spawn;
264
- return new Promise((resolve, reject) => {
265
- let child;
266
- try {
267
- child = launcher("git", args, {
268
- cwd: checkout,
269
- env: process.env,
270
- stdio: capture ? ["ignore", "pipe", "inherit"] : "inherit",
271
- signal,
272
- killSignal: "SIGKILL",
273
- });
274
- } catch (error) {
275
- reject(
276
- signal?.aborted === true || error?.name === "AbortError"
277
- ? new KitRefusal("kit_command_cancelled")
278
- : new KitRefusal("kit_git_unavailable"),
279
- );
280
- return;
281
- }
282
- const output = [];
283
- let settled = false;
284
- const finish = (operation) => {
285
- if (settled) return;
286
- settled = true;
287
- operation();
288
- };
289
- child.stdout?.on("data", (chunk) => output.push(Buffer.from(chunk)));
290
- child.once("error", (error) =>
291
- finish(() =>
292
- reject(
293
- signal?.aborted === true || error?.name === "AbortError"
294
- ? new KitRefusal("kit_command_cancelled")
295
- : new KitRefusal("kit_git_unavailable"),
296
- ),
297
- ),
298
- );
299
- child.once("close", (status) => {
300
- finish(() => {
301
- if (signal?.aborted === true) {
302
- reject(new KitRefusal("kit_command_cancelled"));
303
- return;
304
- }
305
- if (status !== 0) {
306
- reject(
307
- new KitRefusal(args[0] === "pull" ? "kit_git_pull_failed" : "kit_git_selection_failed"),
308
- );
309
- return;
310
- }
311
- resolve(Buffer.concat(output));
312
- });
313
- });
314
- });
315
- }
316
-
317
- async function selectedPaths(checkout, dependencies, signal) {
318
- const output = await runGit(
319
- checkout,
320
- ["ls-files", "--cached", "--others", "--exclude-standard", "-z"],
321
- dependencies,
322
- true,
323
- signal,
324
- );
325
- const paths = [];
326
- let offset = 0;
327
- for (let end = output.indexOf(0); end !== -1; end = output.indexOf(0, offset)) {
328
- let path;
329
- try {
330
- path = utf8.decode(output.subarray(offset, end));
331
- } catch {
332
- refuse("kit_repository_path_invalid");
333
- }
334
- paths.push(repositoryPath(path));
335
- offset = end + 1;
336
- }
337
- if (offset !== output.length) refuse("kit_git_selection_failed");
338
- return [...new Set(paths)].sort();
339
- }
340
-
341
- function openedPublication(answer, sourceRef) {
342
- if (
343
- answer === null ||
344
- typeof answer !== "object" ||
345
- typeof answer.publication !== "string" ||
346
- !PUBLICATION_REFERENCE.test(answer.publication) ||
347
- !validSourceReference(answer.source_ref) ||
348
- !PUBLICATION_STATES.includes(answer.state) ||
349
- !Array.isArray(answer.needed) ||
350
- answer.needed.some((path) => typeof path !== "string" || !validRepositoryPath(path)) ||
351
- answer.needed_count !== answer.needed.length ||
352
- answer.chunk_bound === null ||
353
- typeof answer.chunk_bound !== "object" ||
354
- !Number.isSafeInteger(answer.chunk_bound.paths) ||
355
- answer.chunk_bound.paths < 1 ||
356
- !Number.isSafeInteger(answer.chunk_bound.bytes) ||
357
- answer.chunk_bound.bytes < 1 ||
358
- (sourceRef !== null && answer.source_ref !== sourceRef)
359
- ) {
360
- refuse("kit_publication_rejected");
361
- }
362
- return answer;
363
- }
364
-
365
- function deliveredChunk(answer, expected) {
366
- if (
367
- answer === null ||
368
- typeof answer !== "object" ||
369
- answer.publication !== expected ||
370
- !PUBLICATION_STATES.includes(answer.state) ||
371
- !Array.isArray(answer.outstanding) ||
372
- answer.outstanding.some((path) => typeof path !== "string" || !validRepositoryPath(path)) ||
373
- answer.outstanding_count !== answer.outstanding.length ||
374
- !Array.isArray(answer.items) ||
375
- answer.items.some(
376
- (item) =>
377
- item === null ||
378
- typeof item !== "object" ||
379
- typeof item.path !== "string" ||
380
- !ITEM_OUTCOMES.includes(item.outcome),
381
- )
382
- ) {
383
- refuse("kit_publication_rejected");
384
- }
385
- return answer;
386
- }
387
-
388
- function chunksOf(needed, files, bound) {
389
- const bytes = new Map(files.map((file) => [file.path, file.bytes]));
390
- const chunks = [];
391
- let held = [];
392
- let size = 0;
393
- for (const path of needed) {
394
- const entry = { path, bytes: bytes.get(path) ?? 0 };
395
- if (held.length > 0 && (held.length >= bound.paths || size + entry.bytes > bound.bytes)) {
396
- chunks.push(held);
397
- held = [];
398
- size = 0;
399
- }
400
- held.push(entry);
401
- size += entry.bytes;
402
- }
403
- if (held.length > 0) chunks.push(held);
404
- return chunks;
405
- }
406
-
407
- async function deliverRound(session, options, state, opened, files, signal) {
408
- const delivered = [];
409
- const refused = [];
410
- let state_ = opened.state;
411
- for (const chunk of chunksOf(opened.needed, files, opened.chunk_bound)) {
412
- active(signal);
413
- let answer;
414
- try {
415
- answer = deliveredChunk(
416
- await deliverPublicationChunk(
417
- session,
418
- { room: state.room, publication: opened.publication },
419
- chunkArchive(options.checkout, chunk, signal),
420
- signal,
421
- ),
422
- opened.publication,
423
- );
424
- } catch (error) {
425
- if (error instanceof KitRefusal && error.code === "publication_ended") {
426
- return {
427
- delivered,
428
- refused,
429
- state: "ended",
430
- ending: error.payload?.error?.ending ?? "completed",
431
- };
432
- }
433
- throw error;
434
- }
435
- for (const item of answer.items) {
436
- if (item.outcome === "accepted" || item.outcome === "duplicate") delivered.push(item.path);
437
- else refused.push(item);
438
- }
439
- state_ = answer.state;
440
- }
441
- return { delivered, refused, state: state_, ending: state_ === "completed" ? "completed" : null };
442
- }
443
-
444
- async function checkoutOf(value) {
445
- try {
446
- return await realpath(value);
447
- } catch {
448
- refuse("kit_repository_checkout_invalid", value);
449
- }
450
- }
451
-
452
- export async function captureRepository(options, dependencies = {}) {
453
- if (
454
- typeof options.room !== "string" ||
455
- options.room.length === 0 ||
456
- typeof options.sourceHandle !== "string" ||
457
- !SOURCE_HANDLE.test(options.sourceHandle)
458
- ) {
459
- refuse("kit_repository_declaration_invalid");
460
- }
461
- const identity = {
462
- checkout: await checkoutOf(options.checkout),
463
- room: options.room,
464
- sourceHandle: options.sourceHandle,
465
- };
466
- const key = stateKey(identity);
467
- const session = await openSession(options, dependencies);
468
- return maintainControl(
469
- session,
470
- async (signal) => {
471
- const state = stateOf(await readRepositoryState(options.directory, key), identity);
472
- await runGit(identity.checkout, ["pull"], dependencies, false, signal);
473
- const now = dependencies.now ?? (() => new Date());
474
- let sourceRef = state.source_ref;
475
- let publication = null;
476
- let delivered = [];
477
- let refused = [];
478
- let ending = null;
479
- for (let round = 0; round < MANIFEST_ROUNDS; round += 1) {
480
- const paths = await selectedPaths(identity.checkout, dependencies, signal);
481
- const files = await manifestOf(identity.checkout, paths, signal);
482
- if (files.length === 0) refuse("kit_repository_empty");
483
- const opened = openedPublication(
484
- await openPublicationManifest(
485
- session,
486
- {
487
- room_ref: state.room,
488
- source_handle: state.source_handle,
489
- observed_at: now().toISOString(),
490
- paths: files,
491
- ...(sourceRef === null ? {} : { source_ref: sourceRef }),
492
- },
493
- signal,
494
- ),
495
- sourceRef,
496
- );
497
- sourceRef = opened.source_ref;
498
- publication = opened.publication;
499
- if (sourceRef !== state.source_ref) {
500
- state.source_ref = sourceRef;
501
- await writeRepositoryState(options.directory, key, state, signal);
502
- }
503
- const round_ = await deliverRound(session, options, state, opened, files, signal);
504
- delivered = round_.delivered;
505
- refused = round_.refused;
506
- ending = round_.ending;
507
- if (round_.state === "completed") break;
508
- if (round_.state === "ended" && round_.ending === "superseded") break;
509
- if (round_.state === "active" && refused.length === 0) break;
510
- }
511
- return {
512
- sourceRef,
513
- publication,
514
- delivered: delivered.length,
515
- refused,
516
- ending: ending ?? "active",
517
- };
518
- },
519
- options.signal,
520
- );
521
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import "../house-main.js";
package/src/settlement.js DELETED
@@ -1,57 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readdir, rm } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { KitRefusal } from "./refusal.js";
5
- import { readNote, writeNote } from "./state.js";
6
- import { reportedNote, retainedReport } from "./terminal.js";
7
-
8
- function settlementDirectory(directory) {
9
- return join(directory, "state", "settlements");
10
- }
11
-
12
- function settlementName(run) {
13
- return `${createHash("sha256").update(run).digest("hex")}.json`;
14
- }
15
-
16
- export async function writeSettlement(directory, settlement) {
17
- await writeNote(join(settlementDirectory(directory), settlementName(settlement.run)), {
18
- installation: directory,
19
- run: settlement.run,
20
- session: settlement.session,
21
- reported: reportedNote(settlement.reported),
22
- });
23
- }
24
-
25
- export async function removeSettlement(directory, run) {
26
- await rm(join(settlementDirectory(directory), settlementName(run)), { force: true });
27
- }
28
-
29
- function completeSettlement(directory, stored, name) {
30
- if (typeof stored !== "object" || stored === null || Array.isArray(stored)) return null;
31
- if (stored.installation !== directory) return null;
32
- const named = [stored.run, stored.session];
33
- if (named.some((value) => typeof value !== "string" || value.length === 0)) return null;
34
- if (settlementName(stored.run) !== name) return null;
35
- const reported = retainedReport(stored.reported);
36
- if (reported === null || reported === undefined) return null;
37
- return { run: stored.run, session: stored.session, reported };
38
- }
39
-
40
- export async function readSettlements(directory) {
41
- let present;
42
- try {
43
- present = await readdir(settlementDirectory(directory));
44
- } catch (error) {
45
- if (error?.code === "ENOENT") return [];
46
- throw new KitRefusal("kit_state_unreadable", settlementDirectory(directory));
47
- }
48
- const retained = [];
49
- for (const name of present.filter((entry) => entry.endsWith(".json")).sort()) {
50
- const stored = await readNote(join(settlementDirectory(directory), name), null);
51
- if (stored === null) continue;
52
- const settlement = completeSettlement(directory, stored, name);
53
- if (settlement === null) continue;
54
- retained.push(settlement);
55
- }
56
- return retained;
57
- }