@agentshouse/kit 0.2.1-c2c600f2d2b30 → 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 +70 -32
  4. package/dist/api.js +25 -3
  5. package/dist/app-git.js +64 -0
  6. package/dist/bootstrap.json +4 -4
  7. package/dist/bridge.js +33 -52
  8. package/dist/chatgpt.js +26 -0
  9. package/dist/clis.js +116 -70
  10. package/dist/codex-main.js +58 -0
  11. package/dist/conversations.js +284 -236
  12. package/dist/copies.js +148 -113
  13. package/dist/end.js +18 -4
  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 +2 -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 +12 -7
  37. package/dist/transcription.js +57 -11
  38. package/dist/update.js +28 -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';
@@ -29,9 +29,29 @@ const REBASED = new Set([
29
29
  function rebaseFixes(refusal) {
30
30
  return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
31
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
+ }
32
36
  function short(commit) {
33
37
  return commit.slice(0, 12);
34
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
+ }
35
55
  function covers(path, prefix) {
36
56
  return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
37
57
  }
@@ -152,19 +172,18 @@ export class LocalCopies {
152
172
  house;
153
173
  copies = new Map();
154
174
  queues = new Map();
155
- delivering = new Set();
156
- selecting = Promise.resolve();
175
+ answering = Promise.resolve();
157
176
  constructor(house) {
158
177
  this.house = house;
159
178
  this.owner = {
160
179
  key: 'owner',
161
180
  submit: async (operation, changes) => {
162
181
  try {
163
- 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 }));
164
183
  return { accepted: outcome.contents };
165
184
  }
166
185
  catch (error) {
167
- if (!(error instanceof HouseRefusal) || error.status >= 500)
186
+ if (!finalRefusal(error))
168
187
  throw error;
169
188
  return {
170
189
  refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
@@ -185,42 +204,68 @@ export class LocalCopies {
185
204
  }
186
205
  }
187
206
  async mapping() {
188
- await this.selecting;
207
+ await this.answering;
189
208
  return [...this.copies.values()]
190
209
  .filter((copy) => copy.active)
191
210
  .map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
192
211
  .sort();
193
212
  }
194
- select() {
195
- const selected = this.selecting.then(() => this.reselect());
196
- this.selecting = selected.catch(() => undefined);
197
- return selected;
198
- }
199
- async reselect() {
200
- const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
201
- const selected = (chosen?.rooms ?? []).flatMap((room) => room.room_handle === null ? [] : [{ key: `rooms/${room.room_ref}`, handle: room.room_handle }]);
202
- const keys = new Set(selected.map((scope) => scope.key));
203
- const work = [];
204
- for (const key of this.copies.keys()) {
205
- if (keys.has(key))
206
- continue;
207
- work.push(this.exclusive(key, async () => {
208
- const copy = this.copies.get(key);
209
- if (copy.active)
210
- await this.save({ ...copy, active: false });
211
- }));
212
- }
213
- for (const scope of selected)
214
- work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
215
- await Promise.all(work);
213
+ async connected() {
214
+ const copied = await this.answered(() => this.fetched(false));
215
+ if (copied.refused)
216
+ logged(copied.text);
216
217
  }
217
- entries(frame) {
218
- const copy = [...this.copies.values()].find((held) => held.active && held.room_ref === frame.authority);
219
- if (copy === undefined)
220
- return;
221
- if (copy.log_epoch === frame.log_epoch && BigInt(frame.position) <= BigInt(copy.position))
222
- return;
223
- 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
+ });
224
269
  }
225
270
  async observed(report) {
226
271
  const key = await this.keyAt(dirname(report.common));
@@ -270,16 +315,9 @@ export class LocalCopies {
270
315
  this.queues.set(key, queued.catch(() => undefined));
271
316
  return queued;
272
317
  }
273
- deliver(key) {
274
- if (this.delivering.has(key))
275
- return;
276
- this.delivering.add(key);
277
- void this.exclusive(key, async () => {
278
- this.delivering.delete(key);
279
- const copy = this.copies.get(key);
280
- if (copy.active)
281
- await this.save(await this.caughtUp(copy));
282
- }).catch(logged);
318
+ holding(keys, work) {
319
+ const [key, ...rest] = keys;
320
+ return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
283
321
  }
284
322
  async save(copy) {
285
323
  const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
@@ -325,46 +363,13 @@ export class LocalCopies {
325
363
  }
326
364
  return read;
327
365
  }
328
- async apply(scope) {
329
- let copy = this.copies.get(scope.key);
330
- if (copy === undefined || !(await exists(copy.repository))) {
331
- await this.materialize(scope);
332
- return;
333
- }
334
- if (copy.room_handle !== scope.handle)
335
- copy = await this.save({ ...copy, room_handle: scope.handle });
336
- if (!copy.active) {
337
- await this.save(await this.received({ ...copy, active: true }));
338
- return;
339
- }
340
- const discovery = await this.house.post('/kit/door/discover', {
341
- prefixes: [rootOf(copy.room_handle)],
342
- });
343
- const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
344
- if (authority === undefined)
345
- return;
346
- if (authority.managed_replication !== 'admitted') {
347
- logged(`operation_denied: ${rootOf(copy.room_handle)} is not synchronized to this Environment`);
348
- return;
349
- }
350
- if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
351
- JSON.stringify([copy.write, copy.protected])) {
352
- await this.save(await this.received(copy));
353
- return;
354
- }
355
- await this.save(await this.caughtUp(copy));
356
- }
357
- async materialize(scope) {
358
- const read = await this.read(scope.handle);
359
- if (read === null) {
360
- logged(`House serves nothing of ${rootOf(scope.handle)} to copy`);
361
- return;
362
- }
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');
363
370
  const repository = join(localCopies(), 'rooms', read.anchor);
364
- if (await exists(repository)) {
365
- logged(`${repository} already exists and House Kit leaves it as it is`);
366
- return;
367
- }
371
+ if (await exists(repository))
372
+ throw new Error(`${repository} already exists and House Kit leaves it as it is`);
368
373
  const staging = join(dirname(repository), `.${randomUUID()}`);
369
374
  await mkdir(staging, { recursive: true });
370
375
  let house;
@@ -372,7 +377,7 @@ export class LocalCopies {
372
377
  try {
373
378
  await git(staging, ['init', '-q', '-b', BRANCH]);
374
379
  house = await importCommits(staging, [
375
- { message: `House ${rootOf(scope.handle)}`, from: null, files: read.writable },
380
+ { message: `House ${rootOf(handle)}`, from: null, files: read.writable },
376
381
  ]);
377
382
  await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
378
383
  await git(staging, ['reset', '-q', '--hard']);
@@ -385,9 +390,9 @@ export class LocalCopies {
385
390
  throw error;
386
391
  }
387
392
  await this.save({
388
- key: scope.key,
393
+ key,
389
394
  room_ref: read.anchor,
390
- room_handle: scope.handle,
395
+ room_handle: handle,
391
396
  repository,
392
397
  active: true,
393
398
  house,
@@ -399,6 +404,7 @@ export class LocalCopies {
399
404
  manifests: { [house]: { parent: null, files: read.revisions } },
400
405
  moves: [],
401
406
  });
407
+ return `${rootOf(handle)}: copied to ${repository}`;
402
408
  }
403
409
  async received(copy) {
404
410
  const read = await this.read(copy.room_handle);
@@ -422,29 +428,52 @@ export class LocalCopies {
422
428
  await ignoreRules(copy.repository, [...read.writable.keys()], next);
423
429
  return next;
424
430
  }
425
- async caughtUp(start) {
426
- let copy = start;
427
- 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) {
428
450
  let answer;
429
451
  try {
430
452
  answer = await this.house.post('/kit/door/enumerate', {
431
- prefixes: [rootOf(copy.room_handle)],
432
- 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 })),
433
455
  });
434
456
  }
435
457
  catch (error) {
436
- const code = error instanceof HouseRefusal ? (refusalOf(error.text)?.code ?? null) : null;
437
- if (code === 'position_expired')
438
- return this.received(copy);
439
- if (code !== 'replica_behind')
458
+ const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
459
+ if (expired === undefined)
440
460
  throw error;
441
- 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);
442
473
  }
443
- const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
444
- if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
445
- return copy;
446
- copy = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
474
+ asking = whole ? advanced : [];
447
475
  }
476
+ return copies.map((copy) => caught.get(copy.key));
448
477
  }
449
478
  async incorporated(copy, changes, position, logEpoch) {
450
479
  const houseTree = await tree(copy.repository, copy.house);
@@ -547,7 +576,13 @@ export class LocalCopies {
547
576
  const after = await tree(copy.repository, commit);
548
577
  const moved = movedPaths(copy.moves, before, after);
549
578
  const arrived = new Set(moved.values());
550
- 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);
551
586
  const changes = [];
552
587
  for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
553
588
  if (arrived.has(path))
@@ -560,7 +595,7 @@ export class LocalCopies {
560
595
  changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
561
596
  const moving = after.get(to);
562
597
  if (moving !== prior) {
563
- 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) });
564
599
  }
565
600
  continue;
566
601
  }
@@ -569,9 +604,9 @@ export class LocalCopies {
569
604
  if (next === undefined)
570
605
  changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
571
606
  else if (prior === undefined)
572
- changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next, path) });
607
+ changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
573
608
  else
574
- 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)));
575
610
  }
576
611
  return changes;
577
612
  }
@@ -598,12 +633,12 @@ export class LocalCopies {
598
633
  const { upload, ...unstaged } = copy.push;
599
634
  return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
600
635
  }
601
- if (outcome.refused.code === 'operation_expired')
636
+ if (outcome.refused.code === 'expired')
602
637
  return this.recovered(copy);
603
638
  const rebased = rebaseFixes(outcome.refused);
604
639
  let settled = await this.save(without(copy));
605
640
  try {
606
- settled = await this.save(await this.caughtUp(settled));
641
+ settled = (await this.caughtUp([settled], true))[0];
607
642
  }
608
643
  catch {
609
644
  return {
@@ -622,7 +657,7 @@ export class LocalCopies {
622
657
  }
623
658
  async recovered(copy) {
624
659
  const push = copy.push;
625
- const current = await this.save(await this.caughtUp(copy));
660
+ const current = (await this.caughtUp([copy], true))[0];
626
661
  const house = await tree(copy.repository, current.house);
627
662
  const root = rootOf(current.room_handle);
628
663
  if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
@@ -638,7 +673,7 @@ export class LocalCopies {
638
673
  written.push({
639
674
  path: `${root}/${path}`,
640
675
  revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
641
- content: entry === undefined ? null : await blobText(copy.repository, entry, path),
676
+ content: entry === undefined ? null : await blobText(copy.repository, entry),
642
677
  });
643
678
  }
644
679
  return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
@@ -666,7 +701,7 @@ export class LocalCopies {
666
701
  const push = copy.push;
667
702
  let written = push.written;
668
703
  if (push.changes.some((change) => change.op === 'rename')) {
669
- copy = await this.save(await this.caughtUp(copy));
704
+ copy = (await this.caughtUp([copy], true))[0];
670
705
  const house = await tree(copy.repository, copy.house);
671
706
  const sent = await tree(copy.repository, push.house);
672
707
  if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
@@ -682,7 +717,7 @@ export class LocalCopies {
682
717
  {
683
718
  path: `${rootOf(copy.room_handle)}/${path}`,
684
719
  revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
685
- content: entry === undefined ? null : await blobText(copy.repository, entry, path),
720
+ content: entry === undefined ? null : await blobText(copy.repository, entry),
686
721
  },
687
722
  ];
688
723
  }
@@ -727,7 +762,7 @@ export class LocalCopies {
727
762
  async landed(repository, changes, house, selected) {
728
763
  const text = async (path) => {
729
764
  const entry = house.get(relative(path));
730
- return entry === undefined ? undefined : blobText(repository, entry, path);
765
+ return entry === undefined ? undefined : blobText(repository, entry);
731
766
  };
732
767
  for (const change of changes) {
733
768
  const current = await text(change.path);
package/dist/end.js CHANGED
@@ -1,8 +1,22 @@
1
- import { killDescendants } from './acp.js';
1
+ import { forgetRejected } from './home.js';
2
+ import { installCommand } from './placement.js';
3
+ import { killDescendants } from './scope.js';
2
4
  export const REPLACED = 'another Kit started for this Environment';
3
- export const SIGN_IN_AGAIN = 'sign in again';
4
- export function end(reason) {
5
+ function exit(reason, status) {
5
6
  process.stderr.write(`kit: ${reason}\n`);
6
7
  killDescendants();
7
- process.exit(0);
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');
8
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
+ });