@agentshouse/kit 0.2.1-cd9cbd8b6428c → 0.2.1-cfe80fd914176

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/dist/copies.js CHANGED
@@ -2,7 +2,7 @@ import { parse } from '@agentshouse/mdmodel';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { mkdir, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join } from 'node:path';
5
- import { HouseRefusal } from './api.js';
5
+ import { finalRefusal, HouseRefusal, retrying } from './api.js';
6
6
  import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
7
7
  import { kitHome } from './home.js';
8
8
  import { operationId } from './operation.js';
@@ -11,7 +11,6 @@ import { changed } from './translate.js';
11
11
  import { relayed } from './relay.js';
12
12
  const BRANCH = 'main';
13
13
  const PROVENANCE = '_provenance';
14
- const REPLICA_RETRY_MS = 30_000;
15
14
  const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
16
15
  function causeOf(error) {
17
16
  return error instanceof Error ? error.message : String(error);
@@ -41,11 +40,11 @@ function sidecar(path) {
41
40
  segments.splice(-1, 0, PROVENANCE);
42
41
  return segments.join('/');
43
42
  }
44
- function rootOf(copy) {
45
- return copy.private ? '/private' : `/rooms/${copy.room_handle}`;
43
+ function rootOf(handle) {
44
+ return `/rooms/${handle}`;
46
45
  }
47
46
  function relative(path) {
48
- return path.replace(/^\/private\//, '').replace(/^\/rooms\/[^/]+\//, '');
47
+ return path.replace(/^\/rooms\/[^/]+\//, '');
49
48
  }
50
49
  function declaredWritable(path, copy) {
51
50
  const deepest = (scopes) => Math.max(-1, ...scopes.filter((scope) => covers(path, scope)).map((scope) => scope.length));
@@ -161,11 +160,11 @@ export class LocalCopies {
161
160
  key: 'owner',
162
161
  submit: async (operation, changes) => {
163
162
  try {
164
- const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
163
+ const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
165
164
  return { accepted: outcome.contents };
166
165
  }
167
166
  catch (error) {
168
- if (!(error instanceof HouseRefusal) || error.status >= 500)
167
+ if (!finalRefusal(error))
169
168
  throw error;
170
169
  return {
171
170
  refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
@@ -177,7 +176,7 @@ export class LocalCopies {
177
176
  async load() {
178
177
  const home = join(kitHome(), 'local-copies');
179
178
  const names = (await readdir(join(home, 'rooms')).catch(() => [])).filter((name) => name.endsWith('.json'));
180
- for (const path of [...names.map((name) => join(home, 'rooms', name)), join(home, 'private.json')]) {
179
+ for (const path of names.map((name) => join(home, 'rooms', name))) {
181
180
  const text = await readFile(path, 'utf8').catch(() => null);
182
181
  if (text === null)
183
182
  continue;
@@ -189,7 +188,7 @@ export class LocalCopies {
189
188
  await this.selecting;
190
189
  return [...this.copies.values()]
191
190
  .filter((copy) => copy.active)
192
- .map((copy) => `${rootOf(copy)}: ${copy.repository}`)
191
+ .map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
193
192
  .sort();
194
193
  }
195
194
  select() {
@@ -199,12 +198,7 @@ export class LocalCopies {
199
198
  }
200
199
  async reselect() {
201
200
  const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
202
- const selected = [
203
- ...(chosen?.rooms ?? [])
204
- .filter((room) => room.room_handle !== null)
205
- .map((room) => ({ key: `rooms/${room.room_ref}`, private: false, handle: room.room_handle })),
206
- ...(chosen?.private === true ? [{ key: 'private', private: true, handle: null }] : []),
207
- ];
201
+ const selected = (chosen?.rooms ?? []).flatMap((room) => room.room_handle === null ? [] : [{ key: `rooms/${room.room_ref}`, handle: room.room_handle }]);
208
202
  const keys = new Set(selected.map((scope) => scope.key));
209
203
  const work = [];
210
204
  for (const key of this.copies.keys()) {
@@ -288,7 +282,7 @@ export class LocalCopies {
288
282
  }).catch(logged);
289
283
  }
290
284
  async save(copy) {
291
- const path = join(kitHome(), 'local-copies', copy.private ? 'private.json' : `${copy.key}.json`);
285
+ const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
292
286
  await mkdir(dirname(path), { recursive: true, mode: 0o700 });
293
287
  const temporary = `${path}.${process.pid}`;
294
288
  await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
@@ -296,8 +290,8 @@ export class LocalCopies {
296
290
  this.copies.set(copy.key, copy);
297
291
  return copy;
298
292
  }
299
- async read(selected) {
300
- const prefixes = [rootOf(selected)];
293
+ async read(handle) {
294
+ const prefixes = [rootOf(handle)];
301
295
  const discovery = await this.house.post('/kit/door/discover', { prefixes });
302
296
  let bundle = await this.house.post('/kit/door/bootstrap', { prefixes });
303
297
  if ('transfer' in bundle)
@@ -344,13 +338,13 @@ export class LocalCopies {
344
338
  return;
345
339
  }
346
340
  const discovery = await this.house.post('/kit/door/discover', {
347
- prefixes: [rootOf(copy)],
341
+ prefixes: [rootOf(copy.room_handle)],
348
342
  });
349
343
  const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
350
344
  if (authority === undefined)
351
345
  return;
352
346
  if (authority.managed_replication !== 'admitted') {
353
- logged(`operation_denied: ${rootOf(copy)} is not synchronized to this Environment`);
347
+ logged(`operation_denied: ${rootOf(copy.room_handle)} is not synchronized to this Environment`);
354
348
  return;
355
349
  }
356
350
  if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
@@ -361,12 +355,12 @@ export class LocalCopies {
361
355
  await this.save(await this.caughtUp(copy));
362
356
  }
363
357
  async materialize(scope) {
364
- const read = await this.read({ private: scope.private, room_handle: scope.handle });
358
+ const read = await this.read(scope.handle);
365
359
  if (read === null) {
366
- logged(`House serves nothing of ${rootOf({ private: scope.private, room_handle: scope.handle })} to copy`);
360
+ logged(`House serves nothing of ${rootOf(scope.handle)} to copy`);
367
361
  return;
368
362
  }
369
- const repository = scope.private ? join(localCopies(), 'private') : join(localCopies(), 'rooms', read.anchor);
363
+ const repository = join(localCopies(), 'rooms', read.anchor);
370
364
  if (await exists(repository)) {
371
365
  logged(`${repository} already exists and House Kit leaves it as it is`);
372
366
  return;
@@ -378,7 +372,7 @@ export class LocalCopies {
378
372
  try {
379
373
  await git(staging, ['init', '-q', '-b', BRANCH]);
380
374
  house = await importCommits(staging, [
381
- { message: `House ${rootOf({ private: scope.private, room_handle: scope.handle })}`, from: null, files: read.writable },
375
+ { message: `House ${rootOf(scope.handle)}`, from: null, files: read.writable },
382
376
  ]);
383
377
  await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
384
378
  await git(staging, ['reset', '-q', '--hard']);
@@ -392,7 +386,6 @@ export class LocalCopies {
392
386
  }
393
387
  await this.save({
394
388
  key: scope.key,
395
- private: scope.private,
396
389
  room_ref: read.anchor,
397
390
  room_handle: scope.handle,
398
391
  repository,
@@ -408,11 +401,11 @@ export class LocalCopies {
408
401
  });
409
402
  }
410
403
  async received(copy) {
411
- const read = await this.read(copy);
404
+ const read = await this.read(copy.room_handle);
412
405
  if (read === null)
413
406
  return copy;
414
407
  const house = await importCommits(copy.repository, [
415
- { message: `House ${rootOf(copy)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
408
+ { message: `House ${rootOf(copy.room_handle)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
416
409
  ]);
417
410
  const removed = copy.read_only.filter((path) => !read.readOnly.has(path));
418
411
  const readOnly = await readOnlyPlaced(copy.repository, copy.read_only, read.readOnly, removed);
@@ -435,7 +428,7 @@ export class LocalCopies {
435
428
  let answer;
436
429
  try {
437
430
  answer = await this.house.post('/kit/door/enumerate', {
438
- prefixes: [rootOf(copy)],
431
+ prefixes: [rootOf(copy.room_handle)],
439
432
  positions: [{ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch }],
440
433
  });
441
434
  }
@@ -445,7 +438,6 @@ export class LocalCopies {
445
438
  return this.received(copy);
446
439
  if (code !== 'replica_behind')
447
440
  throw error;
448
- setTimeout(() => this.deliver(copy.key), REPLICA_RETRY_MS).unref();
449
441
  return copy;
450
442
  }
451
443
  const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
@@ -485,7 +477,7 @@ export class LocalCopies {
485
477
  let { house, manifests } = copy;
486
478
  if (files.size > 0) {
487
479
  house = await importCommits(copy.repository, [
488
- { message: `House ${rootOf(copy)} at position ${position}`, from: copy.house, files },
480
+ { message: `House ${rootOf(copy.room_handle)} at position ${position}`, from: copy.house, files },
489
481
  ]);
490
482
  manifests = { ...manifests, [house]: { parent: copy.house, files: revisions } };
491
483
  }
@@ -516,7 +508,7 @@ export class LocalCopies {
516
508
  copy = await this.save(without(copy));
517
509
  }
518
510
  if (!copy.active) {
519
- return { refused: true, text: `${rootOf(copy)} is no longer synced to this Environment, so House takes no push from it` };
511
+ return { refused: true, text: `${rootOf(copy.room_handle)} is no longer synced to this Environment, so House takes no push from it` };
520
512
  }
521
513
  const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
522
514
  const base = listed.find((ancestor) => ancestor in copy.manifests);
@@ -550,7 +542,7 @@ export class LocalCopies {
550
542
  return this.settle(submitted, caller);
551
543
  }
552
544
  async derived(copy, base, commit) {
553
- const root = rootOf(copy);
545
+ const root = rootOf(copy.room_handle);
554
546
  const before = await tree(copy.repository, base);
555
547
  const after = await tree(copy.repository, commit);
556
548
  const moved = movedPaths(copy.moves, before, after);
@@ -632,7 +624,7 @@ export class LocalCopies {
632
624
  const push = copy.push;
633
625
  const current = await this.save(await this.caughtUp(copy));
634
626
  const house = await tree(copy.repository, current.house);
635
- const root = rootOf(current);
627
+ const root = rootOf(current.room_handle);
636
628
  if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
637
629
  await this.save(without(current));
638
630
  return {
@@ -688,7 +680,7 @@ export class LocalCopies {
688
680
  written = [
689
681
  ...written,
690
682
  {
691
- path: `${rootOf(copy)}/${path}`,
683
+ path: `${rootOf(copy.room_handle)}/${path}`,
692
684
  revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
693
685
  content: entry === undefined ? null : await blobText(copy.repository, entry, path),
694
686
  },
@@ -708,7 +700,7 @@ export class LocalCopies {
708
700
  }
709
701
  const edits = new Map([...files].filter(([path, content]) => (content === null ? selected.has(path) : selected.get(path) !== blobId(content))));
710
702
  const received = [...files.keys()].filter((path) => revisionAt(copy, copy.house, path) === revisionAt(copy, push.house, path));
711
- const accepted = `House ${rootOf(copy)} with ${push.commit} accepted`;
703
+ const accepted = `House ${rootOf(copy.room_handle)} with ${push.commit} accepted`;
712
704
  const house = await importCommits(copy.repository, [
713
705
  ...(edits.size === 0 ? [] : [{ message: accepted, from: push.commit, files: edits }]),
714
706
  {
package/dist/end.js ADDED
@@ -0,0 +1,8 @@
1
+ import { killDescendants } from './acp.js';
2
+ export const REPLACED = 'another Kit started for this Environment';
3
+ export const SIGN_IN_AGAIN = 'sign in again';
4
+ export function end(reason) {
5
+ process.stderr.write(`kit: ${reason}\n`);
6
+ killDescendants();
7
+ process.exit(0);
8
+ }