@agentshouse/kit 0.2.1-c23b83622c698 → 0.2.1-c2f0855c31ca3

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 (99) hide show
  1. package/README.md +26 -26
  2. package/dist/acp.js +16 -93
  3. package/dist/agents.js +74 -32
  4. package/dist/api.js +34 -2
  5. package/dist/app-git.js +64 -0
  6. package/dist/bootstrap.json +4 -4
  7. package/dist/bridge.js +33 -51
  8. package/dist/chatgpt.js +26 -0
  9. package/dist/clis.js +118 -70
  10. package/dist/codex-main.js +58 -0
  11. package/dist/conversations.js +284 -236
  12. package/dist/copies.js +148 -115
  13. package/dist/end.js +22 -0
  14. package/dist/enrol-main.js +7 -1
  15. package/dist/events.js +521 -0
  16. package/dist/files.js +21 -1
  17. package/dist/git.js +11 -8
  18. package/dist/home.js +78 -6
  19. package/dist/house-main.js +186 -56
  20. package/dist/instructions.js +9 -32
  21. package/dist/kit-main.js +54 -6
  22. package/dist/login.js +94 -104
  23. package/dist/logout.js +16 -13
  24. package/dist/parts.js +82 -0
  25. package/dist/placement.js +48 -0
  26. package/dist/refusals.js +12 -0
  27. package/dist/relay.js +12 -2
  28. package/dist/resident.js +38 -27
  29. package/dist/scope.js +203 -0
  30. package/dist/secret-input.js +3 -2
  31. package/dist/service-main.js +9 -0
  32. package/dist/service.js +33 -0
  33. package/dist/shell.js +3 -0
  34. package/dist/sign-in.js +34 -10
  35. package/dist/skills.js +80 -12
  36. package/dist/stream.js +35 -5
  37. package/dist/transcription.js +57 -9
  38. package/dist/update.js +30 -6
  39. package/dist/wire.js +6 -0
  40. package/dist/work.js +348 -0
  41. package/how-we-work.md +11 -3
  42. package/package.json +3 -2
  43. package/skills/ask-matt/SKILL.md +17 -11
  44. package/skills/ask-matt/agents/openai.yaml +1 -0
  45. package/skills/code-review/SKILL.md +5 -5
  46. package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
  47. package/skills/codebase-design/SKILL.md +0 -2
  48. package/skills/diagnosing-bugs/SKILL.md +2 -4
  49. package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
  50. package/skills/domain-modeling/SKILL.md +12 -14
  51. package/skills/grill-me/SKILL.md +1 -2
  52. package/skills/grill-me/agents/openai.yaml +1 -0
  53. package/skills/grill-with-docs/SKILL.md +1 -2
  54. package/skills/grill-with-docs/agents/openai.yaml +1 -0
  55. package/skills/grilling/SKILL.md +2 -2
  56. package/skills/handoff/SKILL.md +2 -3
  57. package/skills/handoff/agents/openai.yaml +1 -0
  58. package/skills/implement/SKILL.md +5 -4
  59. package/skills/implement/agents/openai.yaml +1 -0
  60. package/skills/implement-spec/SKILL.md +40 -0
  61. package/skills/implement-spec/agents/openai.yaml +5 -0
  62. package/skills/improve-codebase-architecture/SKILL.md +6 -7
  63. package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
  64. package/skills/pr/CREDITS.md +3 -0
  65. package/skills/pr/LICENSE +21 -0
  66. package/skills/pr/SKILL.md +170 -0
  67. package/skills/pr/agents/openai.yaml +3 -0
  68. package/skills/prototype/SKILL.md +0 -2
  69. package/skills/research/SKILL.md +0 -2
  70. package/skills/retro/LICENSE +21 -0
  71. package/skills/retro/SKILL.md +44 -0
  72. package/skills/retro/agents/openai.yaml +5 -0
  73. package/skills/tdd/SKILL.md +2 -4
  74. package/skills/teach/SKILL.md +3 -4
  75. package/skills/teach/agents/openai.yaml +1 -0
  76. package/skills/to-questionnaire/SKILL.md +1 -2
  77. package/skills/to-questionnaire/agents/openai.yaml +1 -0
  78. package/skills/to-spec/SKILL.md +1 -2
  79. package/skills/to-spec/agents/openai.yaml +1 -0
  80. package/skills/to-tickets/SKILL.md +3 -4
  81. package/skills/to-tickets/agents/openai.yaml +1 -0
  82. package/skills/triage/AGENT-BRIEF.md +207 -0
  83. package/skills/triage/LICENSE +21 -0
  84. package/skills/triage/OUT-OF-SCOPE.md +105 -0
  85. package/skills/triage/SKILL.md +112 -0
  86. package/skills/triage/agents/openai.yaml +5 -0
  87. package/skills/wait-what/SKILL.md +2 -3
  88. package/skills/wait-what/agents/openai.yaml +1 -0
  89. package/skills/wayfinder/SKILL.md +5 -6
  90. package/skills/wayfinder/agents/openai.yaml +1 -0
  91. package/skills/wizard/LICENSE +21 -0
  92. package/skills/wizard/SKILL.md +44 -0
  93. package/skills/wizard/agents/openai.yaml +3 -0
  94. package/skills/wizard/template.sh +223 -0
  95. package/skills/writing-for-agents/SKILL.md +0 -2
  96. package/dist/blocks.js +0 -51
  97. package/skills/resolving-merge-conflicts/SKILL.md +0 -16
  98. package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
  99. /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/copies.js CHANGED
@@ -2,8 +2,8 @@ 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';
6
- import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
5
+ import { finalRefusal, HouseRefusal, retrying } from './api.js';
6
+ import { blobId, blobText, git, isText, 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';
9
9
  import { refusalOf } from './refusals.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);
@@ -30,9 +29,29 @@ const REBASED = new Set([
30
29
  function rebaseFixes(refusal) {
31
30
  return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
32
31
  }
32
+ function textOnly(paths) {
33
+ const [named, them] = paths.length === 1 ? [`${paths[0]} is`, 'it'] : [`${paths.join(', ')} are`, 'them'];
34
+ return `${named} not text, and house push sends text only; remove ${them} from the commit and place ${them} with house upload`;
35
+ }
33
36
  function short(commit) {
34
37
  return commit.slice(0, 12);
35
38
  }
39
+ function moved(before, after) {
40
+ if (before.house !== after.house)
41
+ return [`${rootOf(after.room_handle)}: ${HOUSE_REF} is at ${short(after.house)}`];
42
+ return before.position === after.position ? [] : [`${rootOf(after.room_handle)}: read-only files refreshed`];
43
+ }
44
+ function expiredAuthority(error) {
45
+ if (!(error instanceof HouseRefusal))
46
+ return undefined;
47
+ try {
48
+ const { error: refused } = JSON.parse(error.text);
49
+ return refused?.code === 'expired' && typeof refused.authority === 'string' ? refused.authority : undefined;
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ }
36
55
  function covers(path, prefix) {
37
56
  return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
38
57
  }
@@ -153,19 +172,18 @@ export class LocalCopies {
153
172
  house;
154
173
  copies = new Map();
155
174
  queues = new Map();
156
- delivering = new Set();
157
- selecting = Promise.resolve();
175
+ answering = Promise.resolve();
158
176
  constructor(house) {
159
177
  this.house = house;
160
178
  this.owner = {
161
179
  key: 'owner',
162
180
  submit: async (operation, changes) => {
163
181
  try {
164
- const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
182
+ const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
165
183
  return { accepted: outcome.contents };
166
184
  }
167
185
  catch (error) {
168
- if (!(error instanceof HouseRefusal) || error.status >= 500)
186
+ if (!finalRefusal(error))
169
187
  throw error;
170
188
  return {
171
189
  refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
@@ -186,42 +204,68 @@ export class LocalCopies {
186
204
  }
187
205
  }
188
206
  async mapping() {
189
- await this.selecting;
207
+ await this.answering;
190
208
  return [...this.copies.values()]
191
209
  .filter((copy) => copy.active)
192
210
  .map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
193
211
  .sort();
194
212
  }
195
- select() {
196
- const selected = this.selecting.then(() => this.reselect());
197
- this.selecting = selected.catch(() => undefined);
198
- return selected;
199
- }
200
- async reselect() {
201
- const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
202
- const selected = (chosen?.rooms ?? []).flatMap((room) => room.room_handle === null ? [] : [{ key: `rooms/${room.room_ref}`, handle: room.room_handle }]);
203
- const keys = new Set(selected.map((scope) => scope.key));
204
- const work = [];
205
- for (const key of this.copies.keys()) {
206
- if (keys.has(key))
207
- continue;
208
- work.push(this.exclusive(key, async () => {
209
- const copy = this.copies.get(key);
210
- if (copy.active)
211
- await this.save({ ...copy, active: false });
212
- }));
213
- }
214
- for (const scope of selected)
215
- work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
216
- await Promise.all(work);
213
+ async connected() {
214
+ const copied = await this.answered(() => this.fetched(false));
215
+ if (copied.refused)
216
+ logged(copied.text);
217
217
  }
218
- entries(frame) {
219
- const copy = [...this.copies.values()].find((held) => held.active && held.room_ref === frame.authority);
220
- if (copy === undefined)
221
- return;
222
- if (copy.log_epoch === frame.log_epoch && BigInt(frame.position) <= BigInt(copy.position))
223
- return;
224
- this.deliver(copy.key);
218
+ fetch() {
219
+ return this.answered(() => this.fetched(true));
220
+ }
221
+ answered(work) {
222
+ const answered = this.answering.then(work);
223
+ this.answering = answered.catch(() => undefined);
224
+ return answered;
225
+ }
226
+ async fetched(refreshing) {
227
+ const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
228
+ const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
229
+ return this.holding([...new Set([...this.copies.keys(), ...synced.keys()])].sort(), async () => {
230
+ const said = [];
231
+ let refused = false;
232
+ for (const copy of this.copies.values()) {
233
+ if (synced.has(copy.key) || !copy.active)
234
+ continue;
235
+ await this.save({ ...copy, active: false });
236
+ said.push(`${rootOf(copy.room_handle)}: no longer synced to this Environment; its files stay`);
237
+ }
238
+ const current = [];
239
+ for (const [key, handle] of synced) {
240
+ try {
241
+ const held = this.copies.get(key);
242
+ if (held === undefined || !(await exists(held.repository))) {
243
+ said.push(await this.materialize(key, handle));
244
+ continue;
245
+ }
246
+ const copy = held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle });
247
+ if (copy.active)
248
+ current.push(copy);
249
+ else
250
+ said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
251
+ }
252
+ catch (error) {
253
+ refused = true;
254
+ said.push(`${rootOf(handle)}: ${causeOf(error)}`);
255
+ }
256
+ }
257
+ if (refreshing && current.length > 0) {
258
+ try {
259
+ const refreshed = await this.refreshed(current);
260
+ current.forEach((copy, index) => said.push(...moved(copy, refreshed[index])));
261
+ }
262
+ catch (error) {
263
+ refused = true;
264
+ said.push(error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error));
265
+ }
266
+ }
267
+ return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
268
+ });
225
269
  }
226
270
  async observed(report) {
227
271
  const key = await this.keyAt(dirname(report.common));
@@ -271,16 +315,9 @@ export class LocalCopies {
271
315
  this.queues.set(key, queued.catch(() => undefined));
272
316
  return queued;
273
317
  }
274
- deliver(key) {
275
- if (this.delivering.has(key))
276
- return;
277
- this.delivering.add(key);
278
- void this.exclusive(key, async () => {
279
- this.delivering.delete(key);
280
- const copy = this.copies.get(key);
281
- if (copy.active)
282
- await this.save(await this.caughtUp(copy));
283
- }).catch(logged);
318
+ holding(keys, work) {
319
+ const [key, ...rest] = keys;
320
+ return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
284
321
  }
285
322
  async save(copy) {
286
323
  const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
@@ -326,46 +363,13 @@ export class LocalCopies {
326
363
  }
327
364
  return read;
328
365
  }
329
- async apply(scope) {
330
- let copy = this.copies.get(scope.key);
331
- if (copy === undefined || !(await exists(copy.repository))) {
332
- await this.materialize(scope);
333
- return;
334
- }
335
- if (copy.room_handle !== scope.handle)
336
- copy = await this.save({ ...copy, room_handle: scope.handle });
337
- if (!copy.active) {
338
- await this.save(await this.received({ ...copy, active: true }));
339
- return;
340
- }
341
- const discovery = await this.house.post('/kit/door/discover', {
342
- prefixes: [rootOf(copy.room_handle)],
343
- });
344
- const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
345
- if (authority === undefined)
346
- return;
347
- if (authority.managed_replication !== 'admitted') {
348
- logged(`operation_denied: ${rootOf(copy.room_handle)} is not synchronized to this Environment`);
349
- return;
350
- }
351
- if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
352
- JSON.stringify([copy.write, copy.protected])) {
353
- await this.save(await this.received(copy));
354
- return;
355
- }
356
- await this.save(await this.caughtUp(copy));
357
- }
358
- async materialize(scope) {
359
- const read = await this.read(scope.handle);
360
- if (read === null) {
361
- logged(`House serves nothing of ${rootOf(scope.handle)} to copy`);
362
- return;
363
- }
366
+ async materialize(key, handle) {
367
+ const read = await this.read(handle);
368
+ if (read === null)
369
+ throw new Error('House serves nothing of it to copy');
364
370
  const repository = join(localCopies(), 'rooms', read.anchor);
365
- if (await exists(repository)) {
366
- logged(`${repository} already exists and House Kit leaves it as it is`);
367
- return;
368
- }
371
+ if (await exists(repository))
372
+ throw new Error(`${repository} already exists and House Kit leaves it as it is`);
369
373
  const staging = join(dirname(repository), `.${randomUUID()}`);
370
374
  await mkdir(staging, { recursive: true });
371
375
  let house;
@@ -373,7 +377,7 @@ export class LocalCopies {
373
377
  try {
374
378
  await git(staging, ['init', '-q', '-b', BRANCH]);
375
379
  house = await importCommits(staging, [
376
- { message: `House ${rootOf(scope.handle)}`, from: null, files: read.writable },
380
+ { message: `House ${rootOf(handle)}`, from: null, files: read.writable },
377
381
  ]);
378
382
  await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
379
383
  await git(staging, ['reset', '-q', '--hard']);
@@ -386,9 +390,9 @@ export class LocalCopies {
386
390
  throw error;
387
391
  }
388
392
  await this.save({
389
- key: scope.key,
393
+ key,
390
394
  room_ref: read.anchor,
391
- room_handle: scope.handle,
395
+ room_handle: handle,
392
396
  repository,
393
397
  active: true,
394
398
  house,
@@ -400,6 +404,7 @@ export class LocalCopies {
400
404
  manifests: { [house]: { parent: null, files: read.revisions } },
401
405
  moves: [],
402
406
  });
407
+ return `${rootOf(handle)}: copied to ${repository}`;
403
408
  }
404
409
  async received(copy) {
405
410
  const read = await this.read(copy.room_handle);
@@ -423,30 +428,52 @@ export class LocalCopies {
423
428
  await ignoreRules(copy.repository, [...read.writable.keys()], next);
424
429
  return next;
425
430
  }
426
- async caughtUp(start) {
427
- let copy = start;
428
- for (;;) {
431
+ async refreshed(copies) {
432
+ const discovery = await this.house.post('/kit/door/discover', {
433
+ prefixes: copies.map((copy) => rootOf(copy.room_handle)),
434
+ });
435
+ const regranted = new Map();
436
+ for (const copy of copies) {
437
+ const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
438
+ if (authority !== undefined &&
439
+ JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
440
+ regranted.set(copy.key, await this.save(await this.received(copy)));
441
+ }
442
+ }
443
+ const caught = await this.caughtUp(copies.filter((copy) => !regranted.has(copy.key)), false);
444
+ return copies.map((copy) => regranted.get(copy.key) ?? caught.find((held) => held.key === copy.key));
445
+ }
446
+ async caughtUp(copies, whole) {
447
+ const caught = new Map(copies.map((copy) => [copy.key, copy]));
448
+ let asking = [...copies];
449
+ while (asking.length > 0) {
429
450
  let answer;
430
451
  try {
431
452
  answer = await this.house.post('/kit/door/enumerate', {
432
- prefixes: [rootOf(copy.room_handle)],
433
- positions: [{ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch }],
453
+ prefixes: asking.map((copy) => rootOf(copy.room_handle)),
454
+ positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
434
455
  });
435
456
  }
436
457
  catch (error) {
437
- const code = error instanceof HouseRefusal ? (refusalOf(error.text)?.code ?? null) : null;
438
- if (code === 'position_expired')
439
- return this.received(copy);
440
- if (code !== 'replica_behind')
458
+ const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
459
+ if (expired === undefined)
441
460
  throw error;
442
- setTimeout(() => this.deliver(copy.key), REPLICA_RETRY_MS).unref();
443
- return copy;
461
+ caught.set(expired.key, await this.save(await this.received(expired)));
462
+ asking = asking.filter((copy) => copy !== expired);
463
+ continue;
464
+ }
465
+ const advanced = [];
466
+ for (const copy of asking) {
467
+ const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
468
+ if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
469
+ continue;
470
+ const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
471
+ caught.set(copy.key, next);
472
+ advanced.push(next);
444
473
  }
445
- const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
446
- if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
447
- return copy;
448
- copy = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
474
+ asking = whole ? advanced : [];
449
475
  }
476
+ return copies.map((copy) => caught.get(copy.key));
450
477
  }
451
478
  async incorporated(copy, changes, position, logEpoch) {
452
479
  const houseTree = await tree(copy.repository, copy.house);
@@ -549,7 +576,13 @@ export class LocalCopies {
549
576
  const after = await tree(copy.repository, commit);
550
577
  const moved = movedPaths(copy.moves, before, after);
551
578
  const arrived = new Set(moved.values());
552
- const text = (entry, path) => blobText(copy.repository, entry, path);
579
+ const binary = [];
580
+ for (const [path, entry] of after)
581
+ if (before.get(path) !== entry && !(await isText(copy.repository, entry)))
582
+ binary.push(path);
583
+ if (binary.length > 0)
584
+ throw new Error(textOnly(binary));
585
+ const text = (entry) => blobText(copy.repository, entry);
553
586
  const changes = [];
554
587
  for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
555
588
  if (arrived.has(path))
@@ -562,7 +595,7 @@ export class LocalCopies {
562
595
  changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
563
596
  const moving = after.get(to);
564
597
  if (moving !== prior) {
565
- changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving, to) });
598
+ changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
566
599
  }
567
600
  continue;
568
601
  }
@@ -571,9 +604,9 @@ export class LocalCopies {
571
604
  if (next === undefined)
572
605
  changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
573
606
  else if (prior === undefined)
574
- changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next, path) });
607
+ changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
575
608
  else
576
- changes.push(...changed(`${root}/${path}`, path, known, await text(prior, path), await text(next, path)));
609
+ changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
577
610
  }
578
611
  return changes;
579
612
  }
@@ -600,12 +633,12 @@ export class LocalCopies {
600
633
  const { upload, ...unstaged } = copy.push;
601
634
  return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
602
635
  }
603
- if (outcome.refused.code === 'operation_expired')
636
+ if (outcome.refused.code === 'expired')
604
637
  return this.recovered(copy);
605
638
  const rebased = rebaseFixes(outcome.refused);
606
639
  let settled = await this.save(without(copy));
607
640
  try {
608
- settled = await this.save(await this.caughtUp(settled));
641
+ settled = (await this.caughtUp([settled], true))[0];
609
642
  }
610
643
  catch {
611
644
  return {
@@ -624,7 +657,7 @@ export class LocalCopies {
624
657
  }
625
658
  async recovered(copy) {
626
659
  const push = copy.push;
627
- const current = await this.save(await this.caughtUp(copy));
660
+ const current = (await this.caughtUp([copy], true))[0];
628
661
  const house = await tree(copy.repository, current.house);
629
662
  const root = rootOf(current.room_handle);
630
663
  if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
@@ -640,7 +673,7 @@ export class LocalCopies {
640
673
  written.push({
641
674
  path: `${root}/${path}`,
642
675
  revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
643
- content: entry === undefined ? null : await blobText(copy.repository, entry, path),
676
+ content: entry === undefined ? null : await blobText(copy.repository, entry),
644
677
  });
645
678
  }
646
679
  return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
@@ -668,7 +701,7 @@ export class LocalCopies {
668
701
  const push = copy.push;
669
702
  let written = push.written;
670
703
  if (push.changes.some((change) => change.op === 'rename')) {
671
- copy = await this.save(await this.caughtUp(copy));
704
+ copy = (await this.caughtUp([copy], true))[0];
672
705
  const house = await tree(copy.repository, copy.house);
673
706
  const sent = await tree(copy.repository, push.house);
674
707
  if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
@@ -684,7 +717,7 @@ export class LocalCopies {
684
717
  {
685
718
  path: `${rootOf(copy.room_handle)}/${path}`,
686
719
  revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
687
- content: entry === undefined ? null : await blobText(copy.repository, entry, path),
720
+ content: entry === undefined ? null : await blobText(copy.repository, entry),
688
721
  },
689
722
  ];
690
723
  }
@@ -729,7 +762,7 @@ export class LocalCopies {
729
762
  async landed(repository, changes, house, selected) {
730
763
  const text = async (path) => {
731
764
  const entry = house.get(relative(path));
732
- return entry === undefined ? undefined : blobText(repository, entry, path);
765
+ return entry === undefined ? undefined : blobText(repository, entry);
733
766
  };
734
767
  for (const change of changes) {
735
768
  const current = await text(change.path);
package/dist/end.js ADDED
@@ -0,0 +1,22 @@
1
+ import { forgetRejected } from './home.js';
2
+ import { installCommand } from './placement.js';
3
+ import { killDescendants } from './scope.js';
4
+ export const REPLACED = 'another Kit started for this Environment';
5
+ function exit(reason, status) {
6
+ process.stderr.write(`kit: ${reason}\n`);
7
+ killDescendants();
8
+ process.exit(status);
9
+ }
10
+ export function end(reason) {
11
+ exit(reason, 0);
12
+ }
13
+ export function restart(reason) {
14
+ exit(reason, 1);
15
+ }
16
+ export function updateRequired(house) {
17
+ end(`This Kit is too old for House. Update it: ${installCommand(house)}`);
18
+ }
19
+ export async function signInAgain(rejected) {
20
+ await forgetRejected(rejected);
21
+ restart('sign in again');
22
+ }
@@ -1,4 +1,10 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { writeEnrolment } from './home.js';
3
3
  const [house, environment] = process.argv.slice(2);
4
- await writeEnrolment({ house: house, environment: environment, credential: readFileSync(0, 'utf8').trim() });
4
+ const relay = process.env.HOUSE_KIT_RELAY || undefined;
5
+ await writeEnrolment({
6
+ house: house,
7
+ ...(relay === undefined ? {} : { relay }),
8
+ environment: environment,
9
+ credential: readFileSync(0, 'utf8').trim(),
10
+ });