@agentshouse/kit 0.2.1-c15804a0fa28d → 0.2.1-c23bd93c02c83

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 (101) hide show
  1. package/README.md +32 -31
  2. package/dist/acp.js +16 -93
  3. package/dist/agents.js +153 -44
  4. package/dist/api.js +34 -2
  5. package/dist/app-git.js +48 -0
  6. package/dist/bootstrap.json +4 -4
  7. package/dist/bridge.js +35 -56
  8. package/dist/chatgpt.js +32 -0
  9. package/dist/clis.js +137 -65
  10. package/dist/codex-main.js +58 -0
  11. package/dist/conversations.js +352 -244
  12. package/dist/copies.js +157 -116
  13. package/dist/end.js +23 -0
  14. package/dist/enrol-main.js +7 -1
  15. package/dist/events.js +524 -0
  16. package/dist/files.js +21 -1
  17. package/dist/git.js +10 -8
  18. package/dist/home.js +96 -7
  19. package/dist/house-cli.js +291 -0
  20. package/dist/house-main.js +7 -159
  21. package/dist/instructions.js +10 -32
  22. package/dist/kit-cli.js +68 -0
  23. package/dist/kit-main.js +8 -19
  24. package/dist/login.js +118 -103
  25. package/dist/logout.js +14 -13
  26. package/dist/parts.js +82 -0
  27. package/dist/placement.js +30 -0
  28. package/dist/refusals.js +12 -0
  29. package/dist/relay.js +12 -2
  30. package/dist/resident.js +84 -32
  31. package/dist/scope.js +203 -0
  32. package/dist/secret-input.js +4 -1
  33. package/dist/service-main.js +9 -0
  34. package/dist/service.js +33 -0
  35. package/dist/shell.js +3 -0
  36. package/dist/sign-in.js +34 -9
  37. package/dist/skills.js +81 -12
  38. package/dist/stream.js +87 -11
  39. package/dist/transcription.js +57 -9
  40. package/dist/update.js +30 -6
  41. package/dist/wire.js +6 -0
  42. package/dist/work.js +346 -0
  43. package/how-we-work.md +13 -3
  44. package/package.json +3 -2
  45. package/skills/ask-matt/SKILL.md +17 -11
  46. package/skills/ask-matt/agents/openai.yaml +1 -0
  47. package/skills/code-review/SKILL.md +5 -5
  48. package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
  49. package/skills/codebase-design/SKILL.md +0 -2
  50. package/skills/diagnosing-bugs/SKILL.md +2 -4
  51. package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
  52. package/skills/domain-modeling/SKILL.md +12 -14
  53. package/skills/grill-me/SKILL.md +1 -2
  54. package/skills/grill-me/agents/openai.yaml +1 -0
  55. package/skills/grill-with-docs/SKILL.md +1 -2
  56. package/skills/grill-with-docs/agents/openai.yaml +1 -0
  57. package/skills/grilling/SKILL.md +2 -2
  58. package/skills/handoff/SKILL.md +2 -3
  59. package/skills/handoff/agents/openai.yaml +1 -0
  60. package/skills/implement/SKILL.md +5 -4
  61. package/skills/implement/agents/openai.yaml +1 -0
  62. package/skills/implement-spec/SKILL.md +40 -0
  63. package/skills/implement-spec/agents/openai.yaml +5 -0
  64. package/skills/improve-codebase-architecture/SKILL.md +6 -7
  65. package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
  66. package/skills/pr/CREDITS.md +3 -0
  67. package/skills/pr/LICENSE +21 -0
  68. package/skills/pr/SKILL.md +170 -0
  69. package/skills/pr/agents/openai.yaml +3 -0
  70. package/skills/prototype/SKILL.md +0 -2
  71. package/skills/research/SKILL.md +0 -2
  72. package/skills/retro/LICENSE +21 -0
  73. package/skills/retro/SKILL.md +44 -0
  74. package/skills/retro/agents/openai.yaml +5 -0
  75. package/skills/tdd/SKILL.md +2 -4
  76. package/skills/teach/SKILL.md +3 -4
  77. package/skills/teach/agents/openai.yaml +1 -0
  78. package/skills/to-questionnaire/SKILL.md +1 -2
  79. package/skills/to-questionnaire/agents/openai.yaml +1 -0
  80. package/skills/to-spec/SKILL.md +1 -2
  81. package/skills/to-spec/agents/openai.yaml +1 -0
  82. package/skills/to-tickets/SKILL.md +3 -4
  83. package/skills/to-tickets/agents/openai.yaml +1 -0
  84. package/skills/triage/AGENT-BRIEF.md +207 -0
  85. package/skills/triage/LICENSE +21 -0
  86. package/skills/triage/OUT-OF-SCOPE.md +105 -0
  87. package/skills/triage/SKILL.md +112 -0
  88. package/skills/triage/agents/openai.yaml +5 -0
  89. package/skills/wait-what/SKILL.md +2 -3
  90. package/skills/wait-what/agents/openai.yaml +1 -0
  91. package/skills/wayfinder/SKILL.md +5 -6
  92. package/skills/wayfinder/agents/openai.yaml +1 -0
  93. package/skills/wizard/LICENSE +21 -0
  94. package/skills/wizard/SKILL.md +44 -0
  95. package/skills/wizard/agents/openai.yaml +3 -0
  96. package/skills/wizard/template.sh +223 -0
  97. package/skills/writing-for-agents/SKILL.md +0 -2
  98. package/dist/blocks.js +0 -51
  99. package/skills/resolving-merge-conflicts/SKILL.md +0 -16
  100. package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
  101. /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,11 +11,13 @@ 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);
18
17
  }
18
+ function houseCause(error) {
19
+ return error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
20
+ }
19
21
  function logged(error) {
20
22
  process.stderr.write(`kit: ${causeOf(error)}\n`);
21
23
  }
@@ -30,9 +32,29 @@ const REBASED = new Set([
30
32
  function rebaseFixes(refusal) {
31
33
  return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
32
34
  }
35
+ function textOnly(paths) {
36
+ const [named, them] = paths.length === 1 ? [`${paths[0]} is`, 'it'] : [`${paths.join(', ')} are`, 'them'];
37
+ return `${named} not text, and house push sends text only; remove ${them} from the commit and place ${them} with house upload`;
38
+ }
33
39
  function short(commit) {
34
40
  return commit.slice(0, 12);
35
41
  }
42
+ function moved(before, after) {
43
+ if (before.house !== after.house)
44
+ return [`${rootOf(after.room_handle)}: ${HOUSE_REF} is at ${short(after.house)}`];
45
+ return before.position === after.position ? [] : [`${rootOf(after.room_handle)}: read-only files refreshed`];
46
+ }
47
+ function expiredAuthority(error) {
48
+ if (!(error instanceof HouseRefusal))
49
+ return undefined;
50
+ try {
51
+ const { error: refused } = JSON.parse(error.text);
52
+ return refused?.code === 'expired' && typeof refused.authority === 'string' ? refused.authority : undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
36
58
  function covers(path, prefix) {
37
59
  return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
38
60
  }
@@ -153,19 +175,18 @@ export class LocalCopies {
153
175
  house;
154
176
  copies = new Map();
155
177
  queues = new Map();
156
- delivering = new Set();
157
- selecting = Promise.resolve();
178
+ answering = Promise.resolve();
158
179
  constructor(house) {
159
180
  this.house = house;
160
181
  this.owner = {
161
182
  key: 'owner',
162
183
  submit: async (operation, changes) => {
163
184
  try {
164
- const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
185
+ const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
165
186
  return { accepted: outcome.contents };
166
187
  }
167
188
  catch (error) {
168
- if (!(error instanceof HouseRefusal) || error.status >= 500)
189
+ if (!finalRefusal(error))
169
190
  throw error;
170
191
  return {
171
192
  refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
@@ -186,42 +207,68 @@ export class LocalCopies {
186
207
  }
187
208
  }
188
209
  async mapping() {
189
- await this.selecting;
210
+ await this.answering;
190
211
  return [...this.copies.values()]
191
212
  .filter((copy) => copy.active)
192
213
  .map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
193
214
  .sort();
194
215
  }
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);
216
+ async connected() {
217
+ const copied = await this.answered(() => this.fetched(false));
218
+ if (copied.refused)
219
+ logged(copied.text);
217
220
  }
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);
221
+ fetch() {
222
+ return this.answered(() => this.fetched(true));
223
+ }
224
+ answered(work) {
225
+ const answered = this.answering.then(work);
226
+ this.answering = answered.catch(() => undefined);
227
+ return answered;
228
+ }
229
+ async fetched(refreshing) {
230
+ const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
231
+ const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
232
+ return this.holding([...new Set([...this.copies.keys(), ...synced.keys()])].sort(), async () => {
233
+ const said = [];
234
+ let refused = false;
235
+ for (const copy of this.copies.values()) {
236
+ if (synced.has(copy.key) || !copy.active)
237
+ continue;
238
+ await this.save({ ...copy, active: false });
239
+ said.push(`${rootOf(copy.room_handle)}: no longer synced to this Environment; its files stay`);
240
+ }
241
+ const current = [];
242
+ for (const [key, handle] of synced) {
243
+ try {
244
+ const held = this.copies.get(key);
245
+ if (held === undefined || !(await exists(held.repository))) {
246
+ said.push(await this.materialize(key, handle));
247
+ continue;
248
+ }
249
+ const copy = held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle });
250
+ if (copy.active)
251
+ current.push(copy);
252
+ else
253
+ said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
254
+ }
255
+ catch (error) {
256
+ refused = true;
257
+ said.push(`${rootOf(handle)}: ${causeOf(error)}`);
258
+ }
259
+ }
260
+ if (refreshing && current.length > 0) {
261
+ try {
262
+ const refreshed = await this.refreshed(current);
263
+ current.forEach((copy, index) => said.push(...moved(copy, refreshed[index])));
264
+ }
265
+ catch (error) {
266
+ refused = true;
267
+ said.push(houseCause(error));
268
+ }
269
+ }
270
+ return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
271
+ });
225
272
  }
226
273
  async observed(report) {
227
274
  const key = await this.keyAt(dirname(report.common));
@@ -271,16 +318,9 @@ export class LocalCopies {
271
318
  this.queues.set(key, queued.catch(() => undefined));
272
319
  return queued;
273
320
  }
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);
321
+ holding(keys, work) {
322
+ const [key, ...rest] = keys;
323
+ return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
284
324
  }
285
325
  async save(copy) {
286
326
  const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
@@ -326,46 +366,13 @@ export class LocalCopies {
326
366
  }
327
367
  return read;
328
368
  }
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
- }
369
+ async materialize(key, handle) {
370
+ const read = await this.read(handle);
371
+ if (read === null)
372
+ throw new Error('House serves nothing of it to copy');
364
373
  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
- }
374
+ if (await exists(repository))
375
+ throw new Error(`${repository} already exists and House Kit leaves it as it is`);
369
376
  const staging = join(dirname(repository), `.${randomUUID()}`);
370
377
  await mkdir(staging, { recursive: true });
371
378
  let house;
@@ -373,7 +380,7 @@ export class LocalCopies {
373
380
  try {
374
381
  await git(staging, ['init', '-q', '-b', BRANCH]);
375
382
  house = await importCommits(staging, [
376
- { message: `House ${rootOf(scope.handle)}`, from: null, files: read.writable },
383
+ { message: `House ${rootOf(handle)}`, from: null, files: read.writable },
377
384
  ]);
378
385
  await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
379
386
  await git(staging, ['reset', '-q', '--hard']);
@@ -386,9 +393,9 @@ export class LocalCopies {
386
393
  throw error;
387
394
  }
388
395
  await this.save({
389
- key: scope.key,
396
+ key,
390
397
  room_ref: read.anchor,
391
- room_handle: scope.handle,
398
+ room_handle: handle,
392
399
  repository,
393
400
  active: true,
394
401
  house,
@@ -400,6 +407,7 @@ export class LocalCopies {
400
407
  manifests: { [house]: { parent: null, files: read.revisions } },
401
408
  moves: [],
402
409
  });
410
+ return `${rootOf(handle)}: copied to ${repository}`;
403
411
  }
404
412
  async received(copy) {
405
413
  const read = await this.read(copy.room_handle);
@@ -423,30 +431,52 @@ export class LocalCopies {
423
431
  await ignoreRules(copy.repository, [...read.writable.keys()], next);
424
432
  return next;
425
433
  }
426
- async caughtUp(start) {
427
- let copy = start;
428
- for (;;) {
434
+ async refreshed(copies) {
435
+ const discovery = await this.house.post('/kit/door/discover', {
436
+ prefixes: copies.map((copy) => rootOf(copy.room_handle)),
437
+ });
438
+ const regranted = new Map();
439
+ for (const copy of copies) {
440
+ const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
441
+ if (authority !== undefined &&
442
+ JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
443
+ regranted.set(copy.key, await this.save(await this.received(copy)));
444
+ }
445
+ }
446
+ const caught = await this.caughtUp(copies.filter((copy) => !regranted.has(copy.key)), false);
447
+ return copies.map((copy) => regranted.get(copy.key) ?? caught.find((held) => held.key === copy.key));
448
+ }
449
+ async caughtUp(copies, whole) {
450
+ const caught = new Map(copies.map((copy) => [copy.key, copy]));
451
+ let asking = [...copies];
452
+ while (asking.length > 0) {
429
453
  let answer;
430
454
  try {
431
455
  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 }],
456
+ prefixes: asking.map((copy) => rootOf(copy.room_handle)),
457
+ positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
434
458
  });
435
459
  }
436
460
  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')
461
+ const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
462
+ if (expired === undefined)
441
463
  throw error;
442
- setTimeout(() => this.deliver(copy.key), REPLICA_RETRY_MS).unref();
443
- return copy;
464
+ caught.set(expired.key, await this.save(await this.received(expired)));
465
+ asking = asking.filter((copy) => copy !== expired);
466
+ continue;
444
467
  }
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));
468
+ const advanced = [];
469
+ for (const copy of asking) {
470
+ const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
471
+ if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
472
+ continue;
473
+ const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
474
+ caught.set(copy.key, next);
475
+ advanced.push(next);
476
+ }
477
+ asking = whole ? advanced : [];
449
478
  }
479
+ return copies.map((copy) => caught.get(copy.key));
450
480
  }
451
481
  async incorporated(copy, changes, position, logEpoch) {
452
482
  const houseTree = await tree(copy.repository, copy.house);
@@ -522,7 +552,7 @@ export class LocalCopies {
522
552
  changes = await this.derived(copy, base, commit);
523
553
  }
524
554
  catch (error) {
525
- return { refused: true, text: causeOf(error) };
555
+ return { refused: true, text: houseCause(error) };
526
556
  }
527
557
  if (changes.length === 0)
528
558
  return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
@@ -549,7 +579,18 @@ export class LocalCopies {
549
579
  const after = await tree(copy.repository, commit);
550
580
  const moved = movedPaths(copy.moves, before, after);
551
581
  const arrived = new Set(moved.values());
552
- const text = (entry, path) => blobText(copy.repository, entry, path);
582
+ const arriving = [...after].filter(([path, entry]) => before.get(path) !== entry);
583
+ if (arriving.length > 0) {
584
+ const { bounds } = await this.house.post('/kit/door/discover', { prefixes: [root] });
585
+ const binary = [];
586
+ for (const [path, entry] of arriving) {
587
+ if (!(await isText(copy.repository, entry, bounds.max_authored_file_bytes)))
588
+ binary.push(path);
589
+ }
590
+ if (binary.length > 0)
591
+ throw new Error(textOnly(binary));
592
+ }
593
+ const text = (entry) => blobText(copy.repository, entry);
553
594
  const changes = [];
554
595
  for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
555
596
  if (arrived.has(path))
@@ -562,7 +603,7 @@ export class LocalCopies {
562
603
  changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
563
604
  const moving = after.get(to);
564
605
  if (moving !== prior) {
565
- changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving, to) });
606
+ changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
566
607
  }
567
608
  continue;
568
609
  }
@@ -571,9 +612,9 @@ export class LocalCopies {
571
612
  if (next === undefined)
572
613
  changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
573
614
  else if (prior === undefined)
574
- changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next, path) });
615
+ changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
575
616
  else
576
- changes.push(...changed(`${root}/${path}`, path, known, await text(prior, path), await text(next, path)));
617
+ changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
577
618
  }
578
619
  return changes;
579
620
  }
@@ -600,12 +641,12 @@ export class LocalCopies {
600
641
  const { upload, ...unstaged } = copy.push;
601
642
  return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
602
643
  }
603
- if (outcome.refused.code === 'operation_expired')
644
+ if (outcome.refused.code === 'expired')
604
645
  return this.recovered(copy);
605
646
  const rebased = rebaseFixes(outcome.refused);
606
647
  let settled = await this.save(without(copy));
607
648
  try {
608
- settled = await this.save(await this.caughtUp(settled));
649
+ settled = (await this.caughtUp([settled], true))[0];
609
650
  }
610
651
  catch {
611
652
  return {
@@ -624,7 +665,7 @@ export class LocalCopies {
624
665
  }
625
666
  async recovered(copy) {
626
667
  const push = copy.push;
627
- const current = await this.save(await this.caughtUp(copy));
668
+ const current = (await this.caughtUp([copy], true))[0];
628
669
  const house = await tree(copy.repository, current.house);
629
670
  const root = rootOf(current.room_handle);
630
671
  if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
@@ -640,7 +681,7 @@ export class LocalCopies {
640
681
  written.push({
641
682
  path: `${root}/${path}`,
642
683
  revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
643
- content: entry === undefined ? null : await blobText(copy.repository, entry, path),
684
+ content: entry === undefined ? null : await blobText(copy.repository, entry),
644
685
  });
645
686
  }
646
687
  return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
@@ -668,7 +709,7 @@ export class LocalCopies {
668
709
  const push = copy.push;
669
710
  let written = push.written;
670
711
  if (push.changes.some((change) => change.op === 'rename')) {
671
- copy = await this.save(await this.caughtUp(copy));
712
+ copy = (await this.caughtUp([copy], true))[0];
672
713
  const house = await tree(copy.repository, copy.house);
673
714
  const sent = await tree(copy.repository, push.house);
674
715
  if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
@@ -684,7 +725,7 @@ export class LocalCopies {
684
725
  {
685
726
  path: `${rootOf(copy.room_handle)}/${path}`,
686
727
  revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
687
- content: entry === undefined ? null : await blobText(copy.repository, entry, path),
728
+ content: entry === undefined ? null : await blobText(copy.repository, entry),
688
729
  },
689
730
  ];
690
731
  }
@@ -729,7 +770,7 @@ export class LocalCopies {
729
770
  async landed(repository, changes, house, selected) {
730
771
  const text = async (path) => {
731
772
  const entry = house.get(relative(path));
732
- return entry === undefined ? undefined : blobText(repository, entry, path);
773
+ return entry === undefined ? undefined : blobText(repository, entry);
733
774
  };
734
775
  for (const change of changes) {
735
776
  const current = await text(change.path);
package/dist/end.js ADDED
@@ -0,0 +1,23 @@
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() {
17
+ const command = installCommand();
18
+ end(command === null ? 'This Kit is too old for House; install it again.' : `This Kit is too old for House. Update it: ${command}`);
19
+ }
20
+ export async function signInAgain(rejected) {
21
+ await forgetRejected(rejected);
22
+ restart('sign in again');
23
+ }
@@ -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
+ });