@agentshouse/kit 0.1.0-alpha.8 → 0.2.0

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 (83) hide show
  1. package/README.md +14 -1740
  2. package/dist/api.js +11 -0
  3. package/dist/home.js +24 -0
  4. package/dist/house-main.js +4 -0
  5. package/dist/kit-main.js +16 -0
  6. package/dist/login.js +112 -0
  7. package/dist/resident.js +10 -0
  8. package/dist/stream.js +31 -0
  9. package/package.json +16 -11
  10. package/bin/connect-linux.sh +0 -134
  11. package/bin/kit.js +0 -1023
  12. package/bootstrap.json +0 -17
  13. package/src/acp.js +0 -99
  14. package/src/adapter-mcp.js +0 -44
  15. package/src/adapter.js +0 -252
  16. package/src/agent-logins.js +0 -65
  17. package/src/agents.js +0 -504
  18. package/src/apply.js +0 -29
  19. package/src/attach.js +0 -52
  20. package/src/binding.js +0 -90
  21. package/src/blocks.js +0 -156
  22. package/src/bridge.js +0 -449
  23. package/src/bytes.js +0 -89
  24. package/src/channel.js +0 -37
  25. package/src/command.js +0 -109
  26. package/src/configuration.js +0 -465
  27. package/src/connector-login.js +0 -91
  28. package/src/connector.js +0 -107
  29. package/src/continuation.js +0 -61
  30. package/src/contract.js +0 -363
  31. package/src/conversation-originals.js +0 -143
  32. package/src/custom/authorization.js +0 -63
  33. package/src/declaration.js +0 -35
  34. package/src/delivery.js +0 -191
  35. package/src/directory.js +0 -102
  36. package/src/draft.js +0 -89
  37. package/src/google/calendar.js +0 -177
  38. package/src/google/declared.js +0 -146
  39. package/src/google/drive.js +0 -435
  40. package/src/google/gateway.js +0 -166
  41. package/src/google/gmail.js +0 -249
  42. package/src/google/id.js +0 -1
  43. package/src/google/oauth.js +0 -273
  44. package/src/google/source.js +0 -25
  45. package/src/hook.js +0 -376
  46. package/src/host.js +0 -121
  47. package/src/house-main.js +0 -79
  48. package/src/house.js +0 -1090
  49. package/src/inbound.js +0 -175
  50. package/src/instance/google.js +0 -1
  51. package/src/kit-work-stream.js +0 -84
  52. package/src/launcher-main.js +0 -19
  53. package/src/launcher.js +0 -276
  54. package/src/local-files.js +0 -113
  55. package/src/login.js +0 -274
  56. package/src/mcp.js +0 -71
  57. package/src/migrator.js +0 -248
  58. package/src/mount.js +0 -582
  59. package/src/operation.js +0 -20
  60. package/src/original.js +0 -631
  61. package/src/peer.js +0 -124
  62. package/src/plan.js +0 -57
  63. package/src/question.js +0 -316
  64. package/src/refusal.js +0 -79
  65. package/src/repository.js +0 -521
  66. package/src/session-bin/house +0 -2
  67. package/src/settlement.js +0 -57
  68. package/src/setup.js +0 -190
  69. package/src/source-originals.js +0 -116
  70. package/src/source.js +0 -286
  71. package/src/starter-set/grilling/LICENSE +0 -21
  72. package/src/starter-set/grilling/SKILL.md +0 -30
  73. package/src/starter-set/grilling/agents/openai.yaml +0 -3
  74. package/src/starter-set.js +0 -17
  75. package/src/state.js +0 -157
  76. package/src/sync-library.js +0 -293
  77. package/src/sync-state.js +0 -120
  78. package/src/sync.js +0 -482
  79. package/src/telegram.js +0 -1045
  80. package/src/terminal.js +0 -57
  81. package/src/tmux.js +0 -119
  82. package/src/worker.js +0 -925
  83. package/src/workspace.js +0 -500
package/src/mount.js DELETED
@@ -1,582 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { join } from "node:path";
3
- import { readFile, writeFile } from "node:fs/promises";
4
- import { apply, links, parse, serialize } from "@agentshouse/mdmodel";
5
- import {
6
- KIT_DOOR_BATCH_PATH,
7
- KIT_DOOR_BOOTSTRAP_PATH,
8
- KIT_DOOR_DISCOVER_PATH,
9
- KIT_DOOR_UNMOUNT_PATH,
10
- MIGRATOR_ATTACHMENTS_HOME,
11
- MIGRATOR_CONTENT_HOME,
12
- MOUNT_REPORT_NAME,
13
- } from "./contract.js";
14
- import { heldControl, houseGetJson, houseRequest, maintainControl, openSession } from "./house.js";
15
- import { operationId } from "./operation.js";
16
- import { placeAttachment } from "./original.js";
17
- import { KitRefusal } from "./refusal.js";
18
- import {
19
- OPERATIONS,
20
- assertAdmitted,
21
- admitsOperation,
22
- isProtected,
23
- protectedPathRefusal,
24
- operationDeniedRefusal,
25
- housePath,
26
- VERSION,
27
- } from "./sync-library.js";
28
-
29
- const COLLECTION_HOME = "library";
30
- const MODEL_HOME = "settings/model";
31
-
32
- function admitModel(authority, relative) {
33
- if (isProtected(relative, authority)) throw protectedPathRefusal(authority, relative);
34
- if (!admitsOperation(relative, authority, "write")) {
35
- throw operationDeniedRefusal(authority, relative, "write");
36
- }
37
- }
38
-
39
- const PENDING_LINK_SCHEME = "house-attachment";
40
- const ATTACHMENT_PREFIX = `${MIGRATOR_ATTACHMENTS_HOME}/`;
41
-
42
- function refuse(code, detail = null) {
43
- throw new KitRefusal(code, detail);
44
- }
45
-
46
- function sessionDependencies(options, dependencies) {
47
- return {
48
- fetch: dependencies.fetch ?? options.fetch,
49
- now: dependencies.now ?? options.now,
50
- };
51
- }
52
-
53
- async function withSession(options, dependencies, operation) {
54
- const session = await openSession(options, sessionDependencies(options, dependencies));
55
- return maintainControl(session, (signal) => operation(session, signal), options.signal);
56
- }
57
-
58
- async function door(session, path, body, signal) {
59
- return houseRequest(
60
- session.house,
61
- await heldControl(session, signal),
62
- path,
63
- body,
64
- session.dependencies,
65
- signal,
66
- );
67
- }
68
-
69
- export function mountTarget(subtree) {
70
- if (typeof subtree !== "string" || !subtree.startsWith("/") || subtree.includes("//")) {
71
- refuse("kit_mount_subtree_invalid", subtree);
72
- }
73
- const segments = subtree.slice(1).split("/");
74
- const own = segments[0] === "private";
75
- if (!own && (segments[0] !== "rooms" || segments.length < 2)) {
76
- refuse("kit_mount_subtree_invalid", subtree);
77
- }
78
- const relative = segments.slice(own ? 1 : 2);
79
- if (
80
- relative.length < 2 ||
81
- relative[0] !== COLLECTION_HOME ||
82
- segments.some((segment) => !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(segment))
83
- ) {
84
- refuse("kit_mount_subtree_invalid", subtree);
85
- }
86
- return {
87
- handle: own ? null : segments[1],
88
- root: own ? "/private" : `/rooms/${segments[1]}`,
89
- subtree,
90
- relative: relative.join("/"),
91
- };
92
- }
93
-
94
- function discoveryOf(value) {
95
- if (typeof value?.md_model_version !== "string" || !Array.isArray(value.authorities)) {
96
- refuse("kit_mount_rejected", "discover");
97
- }
98
- if (value.md_model_version !== VERSION) {
99
- refuse("kit_md_model_version_mismatch", value.md_model_version);
100
- }
101
- if (!Number.isSafeInteger(value.bounds?.max_batch_operations) || value.bounds.max_batch_operations <= 0) {
102
- refuse("kit_mount_rejected", "discover.bounds.max_batch_operations");
103
- }
104
- return value;
105
- }
106
-
107
- function authorityFor(discovery, target) {
108
- const found = discovery.authorities.find((entry) =>
109
- entry.kind === "room" && entry.room_handle === target.handle,
110
- );
111
- if (found === undefined) refuse("kit_mount_authority_unknown", target.root);
112
- if (
113
- typeof found.room_ref !== "string" ||
114
- !Array.isArray(found.protected_paths) ||
115
- found.operations === null ||
116
- typeof found.operations !== "object" ||
117
- OPERATIONS.some((operation) => !Array.isArray(found.operations[operation]))
118
- ) {
119
- refuse("kit_mount_rejected", "discover");
120
- }
121
- return {
122
- kind: "room",
123
- authority: found.room_ref,
124
- path: target.root,
125
- protected_paths: found.protected_paths,
126
- operations: found.operations,
127
- files: [],
128
- };
129
- }
130
-
131
- function hrefSuffix(href) {
132
- const match = /[?#]/.exec(href);
133
- return match === null ? "" : href.slice(match.index);
134
- }
135
-
136
- function replaceLink(document, href, destination) {
137
- const marker = `house-kit-link:${operationId()}`;
138
- const located = apply(document, { kind: "link", href, to: marker });
139
- if (located.outcome !== "applied") refuse("kit_mount_link_unwritable", href);
140
- const cleared = parse(serialize(located.document).replaceAll(`${marker}${hrefSuffix(href)}`, marker));
141
- if (!cleared.ok) refuse("kit_mount_link_unwritable", href);
142
- const changed = apply(cleared.document, { kind: "link", href: marker, to: destination });
143
- if (changed.outcome !== "applied") refuse("kit_mount_link_unwritable", href);
144
- return changed.document;
145
- }
146
-
147
- function sourceHref(source, suffix) {
148
- const sourceFragment = source.indexOf("#");
149
- const base = sourceFragment === -1 ? source : source.slice(0, sourceFragment);
150
- const priorFragment = sourceFragment === -1 ? "" : source.slice(sourceFragment);
151
- const suffixFragment = suffix.indexOf("#");
152
- const query = suffixFragment === -1 ? suffix : suffix.slice(0, suffixFragment);
153
- const fragment = suffixFragment === -1 ? priorFragment : suffix.slice(suffixFragment);
154
- if (query === "") return base + fragment;
155
- const separator = base.includes("?") ? (/[?&]$/.test(base) ? "" : "&") : "?";
156
- return base + separator + query.slice(1) + fragment;
157
- }
158
-
159
- export function rewriteAttachmentLinks(held, mintVersion) {
160
- const content = {};
161
- const saves = [];
162
- for (const [relative, text] of Object.entries(held.content).sort()) {
163
- const file = `${MIGRATOR_CONTENT_HOME}/${relative}`;
164
- const parsed = parse(text, relative);
165
- if (!parsed.ok) {
166
- content[relative] = text;
167
- continue;
168
- }
169
- let document = parsed.document;
170
- for (const link of links(document, file)) {
171
- if (!link.target.startsWith(ATTACHMENT_PREFIX)) continue;
172
- const path = link.target.slice(ATTACHMENT_PREFIX.length);
173
- const declared = held.manifest.attachments.find(
174
- (entry) => entry.path === path && entry.download === "downloaded",
175
- );
176
- if (declared === undefined) refuse("kit_migrator_attachment_undeclared", link.target);
177
- if (!declared.referrers.includes(relative)) {
178
- refuse("kit_migrator_referrer_undeclared", `${relative} -> ${path}`);
179
- }
180
- const version = mintVersion(path);
181
- const applied = apply(document, {
182
- kind: "link",
183
- href: link.href,
184
- to: `${PENDING_LINK_SCHEME}:${version}`,
185
- });
186
- if (applied.outcome !== "applied") refuse("kit_mount_link_unwritable", link.href);
187
- document = applied.document;
188
- let save = saves.find((entry) => entry.path === path);
189
- if (save === undefined) {
190
- save = { path, version, documents: [], hrefs: [] };
191
- saves.push(save);
192
- }
193
- if (!save.documents.includes(relative)) save.documents.push(relative);
194
- const suffix = hrefSuffix(link.href);
195
- const href = `${PENDING_LINK_SCHEME}:${version}${suffix}`;
196
- if (!save.hrefs.some((entry) => entry.document === relative && entry.href === href)) {
197
- save.hrefs.push({ document: relative, href, suffix });
198
- }
199
- }
200
- content[relative] = serialize(document);
201
- }
202
- for (const entry of held.manifest.attachments) {
203
- if (entry.download !== "downloaded" || saves.some((save) => save.path === entry.path)) continue;
204
- refuse("kit_migrator_attachment_unlinked", entry.path);
205
- }
206
- return { content, saves };
207
- }
208
-
209
- export function withinBounds(bounds, target, content) {
210
- if (bounds === null || typeof bounds !== "object") return;
211
- const files = Object.keys(content);
212
- if (typeof bounds.max_mount_files === "number" && files.length > bounds.max_mount_files) {
213
- refuse("mount_files_exceeded", `${files.length}`);
214
- }
215
- let bytes = 0;
216
- for (const relative of files) {
217
- const size = Buffer.byteLength(content[relative], "utf8");
218
- if (typeof bounds.max_authored_file_bytes === "number" && size > bounds.max_authored_file_bytes) {
219
- refuse("content_too_large", `${target.subtree}/${relative}`);
220
- }
221
- bytes += size;
222
- const path = `${target.subtree}/${relative}`;
223
- if (typeof bounds.max_path_length === "number" && path.length > bounds.max_path_length) {
224
- refuse("invalid_authored_path", path);
225
- }
226
- if (
227
- typeof bounds.max_path_depth === "number" &&
228
- `${target.relative}/${relative}`.split("/").length > bounds.max_path_depth
229
- ) {
230
- refuse("invalid_authored_path", path);
231
- }
232
- }
233
- if (typeof bounds.max_mount_bytes === "number" && bytes > bounds.max_mount_bytes) {
234
- refuse("batch_too_large", `${bytes}`);
235
- }
236
- }
237
-
238
- async function currentFiles(session, prefixes, signal) {
239
- const answer = await door(
240
- session,
241
- KIT_DOOR_BOOTSTRAP_PATH,
242
- { prefixes },
243
- signal,
244
- );
245
- const bundle =
246
- typeof answer?.transfer?.url === "string"
247
- ? await houseGetJson(answer.transfer.url, session.dependencies, signal)
248
- : answer;
249
- if (!Array.isArray(bundle?.scopes)) refuse("kit_mount_rejected", "bootstrap");
250
- const held = new Map();
251
- for (const scope of bundle.scopes) {
252
- for (const entry of scope.contents ?? []) {
253
- if (typeof entry?.path !== "string" || typeof entry.content !== "string") continue;
254
- held.set(entry.path, entry.content);
255
- }
256
- }
257
- return held;
258
- }
259
-
260
- async function prepareModel(session, target, authority, model, held, signal) {
261
- const prepared = [];
262
- const changes = [];
263
- for (const [name, definition] of Object.entries(model).sort()) {
264
- const relative = `${MODEL_HOME}/${name}`;
265
- admitModel(authority, relative);
266
- prepared.push(relative);
267
- changes.push({ op: "create", path: housePath(target.root, relative), content: definition });
268
- }
269
- if (changes.length === 0) return { prepared, adopted: [], written: [] };
270
- const adopted = [];
271
- const pending = [];
272
- for (const change of changes) {
273
- const relative = change.path.slice(target.root.length + 1);
274
- const current = held.get(relative);
275
- if (current === undefined) {
276
- pending.push(change);
277
- continue;
278
- }
279
- if (current !== change.content) refuse("kit_mount_model_incompatible", change.path);
280
- adopted.push(relative);
281
- }
282
- if (pending.length > 0) {
283
- await door(
284
- session,
285
- KIT_DOOR_BATCH_PATH,
286
- { operation_id: operationId(), changes: pending },
287
- signal,
288
- );
289
- }
290
- return { prepared, adopted, written: pending.map((change) => change.path) };
291
- }
292
-
293
- function mountedRevisions(outcome, target) {
294
- if (
295
- !Array.isArray(outcome?.contents) ||
296
- typeof outcome.position !== "string" ||
297
- typeof outcome.log_epoch !== "string"
298
- ) {
299
- refuse("kit_mount_rejected", "batch");
300
- }
301
- const revisions = new Map();
302
- const prefix = `${target.subtree}/`;
303
- for (const entry of outcome.contents) {
304
- if (typeof entry?.path !== "string" || typeof entry.revision !== "string") continue;
305
- if (!entry.path.startsWith(prefix)) continue;
306
- revisions.set(entry.path.slice(prefix.length), entry.revision);
307
- }
308
- return revisions;
309
- }
310
-
311
- function transferFailure(error) {
312
- return error instanceof KitRefusal ? error.code : "kit_attachment_transfer_failed";
313
- }
314
-
315
- function pendingTransfer(held, save) {
316
- const declared = held.manifest.attachments.find((entry) => entry.path === save.path);
317
- return {
318
- path: save.path,
319
- documents: save.documents,
320
- source: declared.source,
321
- name: declared.name,
322
- version: save.version,
323
- downloaded: true,
324
- attachment: null,
325
- save: { status: "pending", failure: null },
326
- };
327
- }
328
-
329
- async function submitAttachments(session, held, authority, plan, signal) {
330
- const transfers = [];
331
- for (const save of plan.saves) {
332
- const local = held.attachments.get(save.path);
333
- const declared = held.manifest.attachments.find((entry) => entry.path === save.path);
334
- const record = pendingTransfer(held, save);
335
- try {
336
- const body = await readFile(local.file);
337
- if (body.length !== local.bytes || createHash("sha256").update(body).digest("hex") !== local.sha256) {
338
- refuse("kit_migrator_attachment_changed", save.path);
339
- }
340
- const placed = await placeAttachment(session, {
341
- version: save.version,
342
- name: declared.name,
343
- mediaType: declared.mediaType ?? undefined,
344
- room: authority.authority,
345
- body,
346
- signal,
347
- });
348
- record.attachment = placed.attachment;
349
- record.save = placed.save;
350
- } catch (error) {
351
- record.save = { status: "failed", failure: transferFailure(error) };
352
- }
353
- transfers.push(record);
354
- }
355
- return transfers;
356
- }
357
-
358
- function reportRow(values) {
359
- return `| ${values.map((value) => String(value ?? "").replace(/\|/g, "&#124;").replace(/[\r\n]/g, " ")).join(" | ")} |`;
360
- }
361
-
362
- export function reportDocument(result) {
363
- const lines = [
364
- "# Migration report",
365
- "",
366
- `Migrator: ${result.migrator}`,
367
- `Subtree: ${result.subtree}`,
368
- `Documents mounted: ${result.documents}`,
369
- `Model definitions prepared: ${result.model.prepared.length}`,
370
- "",
371
- "## Attachment transfers",
372
- "",
373
- reportRow(["Source", "Referring document", "Local download", "Save", "Attachment", "Version", "Detail"]),
374
- reportRow(["---", "---", "---", "---", "---", "---", "---"]),
375
- ];
376
- for (const transfer of result.transfers) {
377
- for (const document of transfer.documents) {
378
- lines.push(
379
- reportRow([
380
- transfer.source,
381
- document,
382
- "downloaded",
383
- transfer.save.status,
384
- transfer.save.status === "saved" ? transfer.attachment : "",
385
- transfer.version,
386
- transfer.save.failure ?? "",
387
- ]),
388
- );
389
- }
390
- }
391
- for (const missing of result.unavailable) {
392
- for (const referrer of missing.referrers) {
393
- lines.push(
394
- reportRow([
395
- missing.source,
396
- referrer,
397
- missing.download,
398
- "skipped",
399
- "",
400
- "",
401
- missing.failure ?? "",
402
- ]),
403
- );
404
- }
405
- }
406
- lines.push("");
407
- return `${lines.join("\n")}\n`;
408
- }
409
-
410
- export async function mountPackage(held, options, dependencies = {}) {
411
- const target = mountTarget(options.subtree);
412
- const minted = new Map();
413
- const plan = rewriteAttachmentLinks(held, (path) => {
414
- if (!minted.has(path)) minted.set(path, operationId());
415
- return minted.get(path);
416
- });
417
- return withSession(options, dependencies, async (session, signal) => {
418
- const discovery = discoveryOf(
419
- await door(session, KIT_DOOR_DISCOVER_PATH, { prefixes: [target.root] }, signal),
420
- );
421
- const authority = authorityFor(discovery, target);
422
- for (const relative of Object.keys(plan.content)) {
423
- assertAdmitted(authority, `${target.relative}/${relative}`, "write");
424
- }
425
- const reportRelative = `${target.relative}/${MOUNT_REPORT_NAME}`;
426
- assertAdmitted(authority, reportRelative, "write");
427
- for (const name of Object.keys(held.model)) {
428
- admitModel(authority, `${MODEL_HOME}/${name}`);
429
- }
430
- const existing = Object.keys(held.model).length > 0
431
- ? await currentFiles(session, [`${target.root}/${MODEL_HOME}`], signal)
432
- : new Map();
433
- const unavailable = held.manifest.attachments
434
- .filter((entry) => entry.download !== "downloaded")
435
- .map((entry) => ({ source: entry.source, referrers: entry.referrers,
436
- download: entry.download, failure: entry.failure }));
437
- const pending = plan.saves.map((save) => pendingTransfer(held, save));
438
- const initialReport = reportDocument({ migrator: held.manifest.migrator,
439
- subtree: target.subtree, documents: Object.keys(plan.content).length,
440
- model: { prepared: Object.keys(held.model) }, transfers: pending, unavailable });
441
- const mounting = { ...plan.content, [MOUNT_REPORT_NAME]: initialReport };
442
- withinBounds(discovery.bounds, target, mounting);
443
- const model = await prepareModel(session, target, authority, held.model, existing, signal);
444
- const outcome = await door(
445
- session,
446
- KIT_DOOR_BATCH_PATH,
447
- {
448
- operation_id: operationId(),
449
- mount: target.subtree,
450
- changes: Object.entries(mounting)
451
- .sort()
452
- .map(([relative, content]) => ({
453
- op: "create",
454
- path: `${target.subtree}/${relative}`,
455
- content,
456
- })),
457
- },
458
- signal,
459
- );
460
- const revisions = mountedRevisions(outcome, target);
461
- const result = {
462
- migrator: held.manifest.migrator,
463
- subtree: target.subtree,
464
- authority: outcome.authority,
465
- position: outcome.position,
466
- log_epoch: outcome.log_epoch,
467
- documents: Object.keys(plan.content).length,
468
- model,
469
- transfers: pending,
470
- unavailable,
471
- };
472
- const localReport = join(held.root, "MIGRATION.json");
473
- await writeFile(localReport, `${JSON.stringify(result, null, 2)}\n`);
474
- result.transfers = await submitAttachments(session, held, authority, plan, signal);
475
-
476
- const report = `${target.subtree}/${MOUNT_REPORT_NAME}`;
477
- await writeFile(localReport, `${JSON.stringify(result, null, 2)}\n`);
478
- const changes = [];
479
- for (const [relative, text] of Object.entries(plan.content)) {
480
- let content = text;
481
- for (const transfer of result.transfers.filter((entry) => entry.documents.includes(relative))) {
482
- const save = plan.saves.find((entry) => entry.path === transfer.path);
483
- for (const { href, suffix } of save.hrefs.filter((entry) => entry.document === relative)) {
484
- const parsed = parse(content, relative);
485
- const destination = transfer.save.status === "saved"
486
- ? `${transfer.attachment}${suffix}`
487
- : sourceHref(transfer.source, suffix);
488
- content = serialize(replaceLink(parsed.document, href, destination));
489
- }
490
- }
491
- if (content !== text) changes.push({ op: "replace", path: `${target.subtree}/${relative}`, base: revisions.get(relative), content });
492
- }
493
- changes.push({ op: "replace", path: report, base: revisions.get(MOUNT_REPORT_NAME), content: reportDocument(result) });
494
- try {
495
- let reported;
496
- const limit = discovery.bounds.max_batch_operations;
497
- for (let offset = 0; offset < changes.length; offset += limit) {
498
- reported = await door(session, KIT_DOOR_BATCH_PATH, { operation_id: operationId(), changes: changes.slice(offset, offset + limit) }, signal);
499
- }
500
- return { ...result, report, reportPosition: reported.position, reportLogEpoch: reported.log_epoch, reportSaved: true };
501
- } catch (error) {
502
- return { ...result, report, reportSaved: false, reportFailure: transferFailure(error) };
503
- }
504
- });
505
- }
506
-
507
- export async function unmountSubtree(options, dependencies = {}) {
508
- const target = mountTarget(options.subtree);
509
- const confirm = options.confirm;
510
- if (confirm !== undefined && confirm !== target.subtree) {
511
- refuse("kit_unmount_confirmation_mismatch", confirm);
512
- }
513
- return withSession(options, dependencies, async (session, signal) => {
514
- const discovery = discoveryOf(
515
- await door(session, KIT_DOOR_DISCOVER_PATH, { prefixes: [target.root] }, signal),
516
- );
517
- const authority = authorityFor(discovery, target);
518
- assertAdmitted(authority, target.relative, "delete");
519
- assertAdmitted(authority, target.relative, "write");
520
- const files = await currentFiles(session, [target.subtree], signal);
521
- for (const path of files.keys()) {
522
- if (!path.startsWith(`${target.relative}/`) || path.split("/").includes("_provenance")) continue;
523
- assertAdmitted(authority, path, "delete");
524
- assertAdmitted(authority, path, "write");
525
- }
526
- if (confirm === undefined) {
527
- return {
528
- status: "confirmation_required",
529
- subtree: target.subtree,
530
- summary: `Permanently remove every file and revision under ${target.subtree}.`,
531
- };
532
- }
533
- const outcome = await door(
534
- session,
535
- KIT_DOOR_UNMOUNT_PATH,
536
- { operation_id: operationId(), subtree: target.subtree },
537
- signal,
538
- );
539
- if (outcome?.status !== "unmounted") refuse("kit_unmount_rejected", "unmount");
540
- return {
541
- status: "unmounted",
542
- subtree: target.subtree,
543
- authority: outcome.authority,
544
- position: outcome.position,
545
- log_epoch: outcome.log_epoch,
546
- removed: Array.isArray(outcome.contents) ? outcome.contents.length : 0,
547
- };
548
- });
549
- }
550
-
551
- export function formatMount(result) {
552
- const saved = result.transfers.filter((transfer) => transfer.save.status === "saved").length;
553
- const lines = [
554
- `Mounted ${result.documents} documents into ${result.subtree} at position ${result.position}.`,
555
- `Prepared ${result.model.prepared.length} model definitions: ${result.model.written.length} written, ${result.model.adopted.length} already present.`,
556
- `Downloaded ${result.transfers.length} attachments locally; ${saved} saved as House attachments.`,
557
- ];
558
- for (const transfer of result.transfers) {
559
- if (transfer.save.status === "saved") continue;
560
- lines.push(
561
- `Attachment ${transfer.source} referenced by ${transfer.documents.join(", ")}: ${transfer.save.status}` +
562
- `${transfer.save.failure === null ? "" : ` (${transfer.save.failure})`}.`,
563
- );
564
- }
565
- for (const missing of result.unavailable) {
566
- lines.push(
567
- `Unavailable source ${missing.source} referenced by ${missing.referrers.join(", ")}: ${missing.download}` +
568
- `${missing.failure === null ? "" : ` (${missing.failure})`}.`,
569
- );
570
- }
571
- lines.push(result.reportSaved ? `Report written to ${result.report}.` : `Final report pending at ${result.report}: ${result.reportFailure}. Local outcome: MIGRATION.json in the package.`);
572
- return lines;
573
- }
574
-
575
- export function formatUnmount(result) {
576
- if (result.status === "confirmation_required") {
577
- return [result.summary, `Confirm with --confirm ${result.subtree} to remove it.`];
578
- }
579
- return [
580
- `Unmounted ${result.subtree}: removed ${result.removed} files at position ${result.position}.`,
581
- ];
582
- }
package/src/operation.js DELETED
@@ -1,20 +0,0 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
-
3
- function versioned(bytes) {
4
- bytes[6] = (bytes[6] & 0x0f) | 0x70;
5
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
6
- const hex = bytes.toString("hex");
7
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
8
- }
9
-
10
- export function operationId() {
11
- const bytes = randomBytes(16);
12
- bytes.writeUIntBE(Date.now(), 0, 6);
13
- return versioned(bytes);
14
- }
15
-
16
- export function derivedOperationId(operation, label) {
17
- const bytes = createHash("sha256").update(`${operation}/${label}`).digest().subarray(0, 16);
18
- bytes.write(operation.replaceAll("-", "").slice(0, 12), 0, "hex");
19
- return versioned(bytes);
20
- }