@agentshouse/kit 0.2.1-cbabe8ad337ff → 0.2.1-cca496635583e

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/README.md CHANGED
@@ -1,14 +1,12 @@
1
1
  # House Kit
2
2
 
3
- House Kit connects an Environment, a Linux computer or server, to
4
- [agents.house](https://agents.house). It runs your Agents' conversations
5
- through the agent CLIs you chose and gives each Agent the `house` command.
3
+ House Kit connects an Environment, a Linux computer or server, to [agents.house](https://agents.house). It runs
4
+ your Agents' conversations through the agent CLIs you chose and gives each Agent the `house` command.
6
5
 
7
6
  ## Install
8
7
 
9
- Run `curl -fsSL agents.house/kit | sh`, or `irm agents.house/kit.ps1 | iex` on
10
- Windows, and again to update; options follow `sh -s --`. On macOS and Linux it
11
- installs House Kit natively under your own account, without an admin password,
8
+ Run `curl -fsSL agents.house/kit | sh`, or `irm agents.house/kit.ps1 | iex` on Windows, and again to update;
9
+ options follow `sh -s --`. On macOS and Linux it installs House Kit natively under your own account, without an admin password,
12
10
  as a service that runs while you are logged in. `kit` and `house` are then on
13
11
  your login shell's `PATH`, and your workspace is `~/AgentsHouse` unless you
14
12
  pass `--workspace <path>`.
@@ -20,7 +18,7 @@ workspace. On Windows 11 the container is the one choice. Append a `kit` or
20
18
  Running the command for the other choice on a connected computer asks you to
21
19
  confirm, then replaces its Environment, whose Agents end, with a new one.
22
20
 
23
- On your own Ubuntu 26.04 server, run the Linux command with `--linux` to
21
+ On your own Linux server, run the Linux command with `--linux` to
24
22
  install House Kit natively, with its workspace at `/agents/house`; `kit` and
25
23
  `house` are then on the server's `PATH`.
26
24
 
@@ -33,22 +31,24 @@ them out; running it again without the flag brings them back.
33
31
 
34
32
  ## Commands
35
33
 
36
- `kit login` opens or prints a link you confirm in any browser, on this
37
- computer or another, continues on its own and keeps the Environment's
38
- credential in `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is set. Pass
39
- `--environment <id>` to reconnect an Environment you already have. It also
40
- connects your own agent: outside an Agent conversation, `house <tool>` calls
34
+ `kit login` opens or prints a link you confirm in any browser, on this computer
35
+ or another, continues on its own and keeps the Environment's credential in
36
+ `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is set. `--environment <id>`
37
+ reconnects an Environment you already have; `--rooms <ref>[,<ref>...]` gives it
38
+ only those Rooms and `--no-private` every Room but your Private Room;
39
+ `--code <code>` connects immediately without a link.
40
+ A login also connects your own agent: outside an Agent conversation, `house <tool>` calls
41
41
  House as you and `git` clones and pushes your Apps at `<house>/app/` as you,
42
42
  until you revoke that connection on Connections. `house help` lists the Tools.
43
43
  `kit logout` disconnects this computer, stops House Kit and ends your own
44
44
  agent's connection, so `house` and `git` no longer act as you here; a later
45
45
  `kit login` reconnects the same Environment.
46
46
 
47
- `kit resident` keeps the Environment connected, and keeps the Rooms you chose
48
- for this Environment as Git repositories under `local-copies` in the
49
- workspace. Commit in one and run `house push --owner` there to send the
50
- commit to House; in the container, run it through the install command from the
51
- copy's directory. Inside an Agent conversation the Agent runs `house push`.
47
+ `kit resident` keeps the Environment connected and, as it connects, copies each
48
+ Room you chose for it as a Git repository in the workspace's `local-copies`.
49
+ `house fetch` records House's changes on each copy's `refs/house/received`;
50
+ rebase onto it, commit and run `house push --owner` in the copy, through the
51
+ install command in the container. An Agent runs both, without `--owner`.
52
52
 
53
53
  House Kit turns each audio file of your message to an Agent into text on this
54
54
  computer before the Agent reads it. While transcription is on, the speech
package/dist/agents.js CHANGED
@@ -257,7 +257,7 @@ export class Agents {
257
257
  }
258
258
  async load(kind) {
259
259
  const known = this.readings.get(kind);
260
- if (known !== undefined && known.failure === null)
260
+ if (known !== undefined && known.failure === null && known.minimum === undefined)
261
261
  return;
262
262
  await this.examine(kind, SIGNED_OUT, offer);
263
263
  }
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { setTimeout as delay } from 'node:timers/promises';
2
- import { end, SIGN_IN_AGAIN } from './end.js';
2
+ import { signInAgain } from './end.js';
3
3
  import { readEnrolment } from './home.js';
4
4
  import { retryable } from './refusals.js';
5
5
  import { relayed } from './relay.js';
@@ -54,7 +54,7 @@ async function enrolledPost(path, body, signal) {
54
54
  }
55
55
  catch (error) {
56
56
  if (error instanceof HouseRefusal && error.status === 401)
57
- end(SIGN_IN_AGAIN);
57
+ await signInAgain(credential);
58
58
  throw error;
59
59
  }
60
60
  }
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "version": "0.2.1-cbabe8ad337ff",
3
+ "version": "0.2.1-cca496635583e",
4
4
  "hosts": {
5
5
  "macos": {
6
6
  "asset": {
7
7
  "name": "connect-linux.sh",
8
- "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cbabe8ad337ff/connect-linux.sh"
8
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cca496635583e/connect-linux.sh"
9
9
  }
10
10
  },
11
11
  "linux": {
12
12
  "asset": {
13
13
  "name": "connect-linux.sh",
14
- "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cbabe8ad337ff/connect-linux.sh"
14
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cca496635583e/connect-linux.sh"
15
15
  }
16
16
  },
17
17
  "windows": {
18
18
  "asset": {
19
19
  "name": "connect-windows.ps1",
20
- "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cbabe8ad337ff/connect-windows.ps1"
20
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cca496635583e/connect-windows.ps1"
21
21
  }
22
22
  }
23
23
  }
package/dist/bridge.js CHANGED
@@ -97,7 +97,7 @@ export async function openBridge(house, copies, conversationId, signal) {
97
97
  const after = resendAfter(forwarded, attempt);
98
98
  if (after === null)
99
99
  return forwarded;
100
- // A write House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
100
+ // A Tool call House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
101
101
  await delay(after, undefined, { signal: closed.signal });
102
102
  }
103
103
  };
@@ -135,23 +135,9 @@ export async function openBridge(house, copies, conversationId, signal) {
135
135
  const held = JSON.parse(text).transfer;
136
136
  return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
137
137
  };
138
- let mutations = null;
139
- const writes = async (tool) => {
140
- mutations ??= house
141
- .post('/kit/tools/mutations', {}, closed.signal)
142
- .then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
143
- try {
144
- return (await mutations).has(tool);
145
- }
146
- catch (error) {
147
- mutations = null;
148
- throw error;
149
- }
150
- };
151
- const forward = async (text) => {
138
+ const forward = (text) => {
152
139
  const message = JSON.parse(text);
153
- const tool = message.method === 'tools/call' ? String(message.params.name) : null;
154
- return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
140
+ return send(message, message.method === 'tools/call' ? operationId() : null);
155
141
  };
156
142
  const caller = {
157
143
  key: `conversation:${conversationId}`,
@@ -173,6 +159,8 @@ export async function openBridge(house, copies, conversationId, signal) {
173
159
  const { cwd, commit } = JSON.parse(text);
174
160
  return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
175
161
  }
162
+ if (path === '/git/fetch')
163
+ return { status: 200, text: JSON.stringify(await copies.fetch()) };
176
164
  if (path === '/git/observed') {
177
165
  await copies.observed(JSON.parse(text));
178
166
  return { status: 200, text: '{}' };
@@ -197,7 +185,8 @@ export async function openBridge(house, copies, conversationId, signal) {
197
185
  response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
198
186
  return;
199
187
  }
200
- response.writeHead(502).end(request.url === '/git/push' && error instanceof Error ? error.message : UNREACHABLE);
188
+ const verb = request.url === '/git/push' || request.url === '/git/fetch';
189
+ response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
201
190
  }
202
191
  });
203
192
  bridge.listen(socketPath);
@@ -132,6 +132,9 @@ export class Conversations {
132
132
  return false;
133
133
  return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
134
134
  }
135
+ quiet() {
136
+ return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
137
+ }
135
138
  look() {
136
139
  for (const conversation of this.conversations.values()) {
137
140
  for (const scope of conversation.leftovers) {
package/dist/copies.js CHANGED
@@ -32,6 +32,22 @@ function rebaseFixes(refusal) {
32
32
  function short(commit) {
33
33
  return commit.slice(0, 12);
34
34
  }
35
+ function moved(before, after) {
36
+ if (before.house !== after.house)
37
+ return [`${rootOf(after.room_handle)}: ${HOUSE_REF} is at ${short(after.house)}`];
38
+ return before.position === after.position ? [] : [`${rootOf(after.room_handle)}: read-only files refreshed`];
39
+ }
40
+ function expiredAuthority(error) {
41
+ if (!(error instanceof HouseRefusal))
42
+ return undefined;
43
+ try {
44
+ const { error: refused } = JSON.parse(error.text);
45
+ return refused?.code === 'expired' && typeof refused.authority === 'string' ? refused.authority : undefined;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
35
51
  function covers(path, prefix) {
36
52
  return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
37
53
  }
@@ -152,8 +168,7 @@ export class LocalCopies {
152
168
  house;
153
169
  copies = new Map();
154
170
  queues = new Map();
155
- delivering = new Set();
156
- selecting = Promise.resolve();
171
+ answering = Promise.resolve();
157
172
  constructor(house) {
158
173
  this.house = house;
159
174
  this.owner = {
@@ -185,42 +200,68 @@ export class LocalCopies {
185
200
  }
186
201
  }
187
202
  async mapping() {
188
- await this.selecting;
203
+ await this.answering;
189
204
  return [...this.copies.values()]
190
205
  .filter((copy) => copy.active)
191
206
  .map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
192
207
  .sort();
193
208
  }
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);
209
+ async connected() {
210
+ const copied = await this.answered(() => this.fetched(false));
211
+ if (copied.refused)
212
+ logged(copied.text);
216
213
  }
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);
214
+ fetch() {
215
+ return this.answered(() => this.fetched(true));
216
+ }
217
+ answered(work) {
218
+ const answered = this.answering.then(work);
219
+ this.answering = answered.catch(() => undefined);
220
+ return answered;
221
+ }
222
+ async fetched(refreshing) {
223
+ const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
224
+ const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
225
+ return this.holding([...new Set([...this.copies.keys(), ...synced.keys()])].sort(), async () => {
226
+ const said = [];
227
+ let refused = false;
228
+ for (const copy of this.copies.values()) {
229
+ if (synced.has(copy.key) || !copy.active)
230
+ continue;
231
+ await this.save({ ...copy, active: false });
232
+ said.push(`${rootOf(copy.room_handle)}: no longer synced to this Environment; its files stay`);
233
+ }
234
+ const current = [];
235
+ for (const [key, handle] of synced) {
236
+ try {
237
+ const held = this.copies.get(key);
238
+ if (held === undefined || !(await exists(held.repository))) {
239
+ said.push(await this.materialize(key, handle));
240
+ continue;
241
+ }
242
+ const copy = held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle });
243
+ if (copy.active)
244
+ current.push(copy);
245
+ else
246
+ said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
247
+ }
248
+ catch (error) {
249
+ refused = true;
250
+ said.push(`${rootOf(handle)}: ${causeOf(error)}`);
251
+ }
252
+ }
253
+ if (refreshing && current.length > 0) {
254
+ try {
255
+ const refreshed = await this.refreshed(current);
256
+ current.forEach((copy, index) => said.push(...moved(copy, refreshed[index])));
257
+ }
258
+ catch (error) {
259
+ refused = true;
260
+ said.push(error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error));
261
+ }
262
+ }
263
+ return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
264
+ });
224
265
  }
225
266
  async observed(report) {
226
267
  const key = await this.keyAt(dirname(report.common));
@@ -270,16 +311,9 @@ export class LocalCopies {
270
311
  this.queues.set(key, queued.catch(() => undefined));
271
312
  return queued;
272
313
  }
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);
314
+ holding(keys, work) {
315
+ const [key, ...rest] = keys;
316
+ return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
283
317
  }
284
318
  async save(copy) {
285
319
  const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
@@ -325,46 +359,13 @@ export class LocalCopies {
325
359
  }
326
360
  return read;
327
361
  }
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
- }
362
+ async materialize(key, handle) {
363
+ const read = await this.read(handle);
364
+ if (read === null)
365
+ throw new Error('House serves nothing of it to copy');
363
366
  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
- }
367
+ if (await exists(repository))
368
+ throw new Error(`${repository} already exists and House Kit leaves it as it is`);
368
369
  const staging = join(dirname(repository), `.${randomUUID()}`);
369
370
  await mkdir(staging, { recursive: true });
370
371
  let house;
@@ -372,7 +373,7 @@ export class LocalCopies {
372
373
  try {
373
374
  await git(staging, ['init', '-q', '-b', BRANCH]);
374
375
  house = await importCommits(staging, [
375
- { message: `House ${rootOf(scope.handle)}`, from: null, files: read.writable },
376
+ { message: `House ${rootOf(handle)}`, from: null, files: read.writable },
376
377
  ]);
377
378
  await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
378
379
  await git(staging, ['reset', '-q', '--hard']);
@@ -385,9 +386,9 @@ export class LocalCopies {
385
386
  throw error;
386
387
  }
387
388
  await this.save({
388
- key: scope.key,
389
+ key,
389
390
  room_ref: read.anchor,
390
- room_handle: scope.handle,
391
+ room_handle: handle,
391
392
  repository,
392
393
  active: true,
393
394
  house,
@@ -399,6 +400,7 @@ export class LocalCopies {
399
400
  manifests: { [house]: { parent: null, files: read.revisions } },
400
401
  moves: [],
401
402
  });
403
+ return `${rootOf(handle)}: copied to ${repository}`;
402
404
  }
403
405
  async received(copy) {
404
406
  const read = await this.read(copy.room_handle);
@@ -422,29 +424,52 @@ export class LocalCopies {
422
424
  await ignoreRules(copy.repository, [...read.writable.keys()], next);
423
425
  return next;
424
426
  }
425
- async caughtUp(start) {
426
- let copy = start;
427
- for (;;) {
427
+ async refreshed(copies) {
428
+ const discovery = await this.house.post('/kit/door/discover', {
429
+ prefixes: copies.map((copy) => rootOf(copy.room_handle)),
430
+ });
431
+ const regranted = new Map();
432
+ for (const copy of copies) {
433
+ const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
434
+ if (authority !== undefined &&
435
+ JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
436
+ regranted.set(copy.key, await this.save(await this.received(copy)));
437
+ }
438
+ }
439
+ const caught = await this.caughtUp(copies.filter((copy) => !regranted.has(copy.key)), false);
440
+ return copies.map((copy) => regranted.get(copy.key) ?? caught.find((held) => held.key === copy.key));
441
+ }
442
+ async caughtUp(copies, whole) {
443
+ const caught = new Map(copies.map((copy) => [copy.key, copy]));
444
+ let asking = [...copies];
445
+ while (asking.length > 0) {
428
446
  let answer;
429
447
  try {
430
448
  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 }],
449
+ prefixes: asking.map((copy) => rootOf(copy.room_handle)),
450
+ positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
433
451
  });
434
452
  }
435
453
  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')
454
+ const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
455
+ if (expired === undefined)
440
456
  throw error;
441
- return copy;
457
+ caught.set(expired.key, await this.save(await this.received(expired)));
458
+ asking = asking.filter((copy) => copy !== expired);
459
+ continue;
460
+ }
461
+ const advanced = [];
462
+ for (const copy of asking) {
463
+ const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
464
+ if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
465
+ continue;
466
+ const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
467
+ caught.set(copy.key, next);
468
+ advanced.push(next);
442
469
  }
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));
470
+ asking = whole ? advanced : [];
447
471
  }
472
+ return copies.map((copy) => caught.get(copy.key));
448
473
  }
449
474
  async incorporated(copy, changes, position, logEpoch) {
450
475
  const houseTree = await tree(copy.repository, copy.house);
@@ -598,12 +623,12 @@ export class LocalCopies {
598
623
  const { upload, ...unstaged } = copy.push;
599
624
  return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
600
625
  }
601
- if (outcome.refused.code === 'operation_expired')
626
+ if (outcome.refused.code === 'expired')
602
627
  return this.recovered(copy);
603
628
  const rebased = rebaseFixes(outcome.refused);
604
629
  let settled = await this.save(without(copy));
605
630
  try {
606
- settled = await this.save(await this.caughtUp(settled));
631
+ settled = (await this.caughtUp([settled], true))[0];
607
632
  }
608
633
  catch {
609
634
  return {
@@ -622,7 +647,7 @@ export class LocalCopies {
622
647
  }
623
648
  async recovered(copy) {
624
649
  const push = copy.push;
625
- const current = await this.save(await this.caughtUp(copy));
650
+ const current = (await this.caughtUp([copy], true))[0];
626
651
  const house = await tree(copy.repository, current.house);
627
652
  const root = rootOf(current.room_handle);
628
653
  if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
@@ -666,7 +691,7 @@ export class LocalCopies {
666
691
  const push = copy.push;
667
692
  let written = push.written;
668
693
  if (push.changes.some((change) => change.op === 'rename')) {
669
- copy = await this.save(await this.caughtUp(copy));
694
+ copy = (await this.caughtUp([copy], true))[0];
670
695
  const house = await tree(copy.repository, copy.house);
671
696
  const sent = await tree(copy.repository, push.house);
672
697
  if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
package/dist/end.js CHANGED
@@ -1,8 +1,18 @@
1
+ import { forgetRejected } from './home.js';
1
2
  import { killDescendants } from './scope.js';
2
3
  export const REPLACED = 'another Kit started for this Environment';
3
- export const SIGN_IN_AGAIN = 'sign in again';
4
- export function end(reason) {
4
+ function exit(reason, status) {
5
5
  process.stderr.write(`kit: ${reason}\n`);
6
6
  killDescendants();
7
- process.exit(0);
7
+ process.exit(status);
8
+ }
9
+ export function end(reason) {
10
+ exit(reason, 0);
11
+ }
12
+ export function restart(reason) {
13
+ exit(reason, 1);
14
+ }
15
+ export async function signInAgain(rejected) {
16
+ await forgetRejected(rejected);
17
+ restart('sign in again');
8
18
  }
@@ -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
+ });
package/dist/home.js CHANGED
@@ -1,11 +1,13 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, linkSync, readFileSync, renameSync, rmSync } from 'node:fs';
2
2
  import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
+ import { selectRelay } from './relay.js';
5
6
  const ENROLMENT = 'credential.json';
6
7
  const DISCONNECTED = 'disconnected.json';
7
8
  const CONFIGURATION = 'kit.json';
8
9
  const OWN_AGENT = 'own-agent.json';
10
+ const RESTART = 'restart';
9
11
  export function kitHome() {
10
12
  return process.env.HOUSE_KIT_HOME ?? join(homedir(), '.house-kit');
11
13
  }
@@ -32,8 +34,11 @@ export async function writeHome(name, value) {
32
34
  await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
33
35
  await rename(temporary, join(home, name));
34
36
  }
35
- export function readEnrolment() {
36
- return readHome(ENROLMENT);
37
+ export async function readEnrolment() {
38
+ const enrolment = await readHome(ENROLMENT);
39
+ if (enrolment !== null)
40
+ selectRelay(process.env.HOUSE_KIT_RELAY || enrolment.relay);
41
+ return enrolment;
37
42
  }
38
43
  export async function writeEnrolment(enrolment) {
39
44
  await writeHome(ENROLMENT, enrolment);
@@ -46,9 +51,42 @@ export async function forgetEnrolment({ house, environment }) {
46
51
  await writeHome(DISCONNECTED, { house, environment });
47
52
  await rm(join(kitHome(), ENROLMENT), { force: true });
48
53
  }
54
+ export async function forgetRejected(rejected) {
55
+ const held = await readHome(ENROLMENT);
56
+ if (held?.credential !== rejected)
57
+ return;
58
+ await writeHome(DISCONNECTED, { house: held.house, environment: held.environment });
59
+ const enrolment = join(kitHome(), ENROLMENT);
60
+ const taken = `${enrolment}.${process.pid}`;
61
+ try {
62
+ renameSync(enrolment, taken);
63
+ }
64
+ catch (error) {
65
+ if (error.code === 'ENOENT')
66
+ return;
67
+ throw error;
68
+ }
69
+ if (JSON.parse(readFileSync(taken, 'utf8')).credential !== rejected) {
70
+ try {
71
+ linkSync(taken, enrolment);
72
+ }
73
+ catch (error) {
74
+ if (error.code !== 'EEXIST')
75
+ throw error;
76
+ }
77
+ rmSync(join(kitHome(), DISCONNECTED), { force: true });
78
+ }
79
+ rmSync(taken);
80
+ }
49
81
  export function enrolled() {
50
82
  return existsSync(join(kitHome(), ENROLMENT));
51
83
  }
84
+ export function restartAsked() {
85
+ return existsSync(join(kitHome(), RESTART));
86
+ }
87
+ export function forgetRestart() {
88
+ return rm(join(kitHome(), RESTART), { force: true });
89
+ }
52
90
  export function readOwnAgent() {
53
91
  return readHome(OWN_AGENT);
54
92
  }
@@ -3,7 +3,8 @@ import { createHash } from 'node:crypto';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { request, setGlobalProxyFromEnv } from 'node:http';
5
5
  import { basename, join } from 'node:path';
6
- import { kitHome, readOwnAgent } from './home.js';
6
+ import { HOUSE_REF } from './git.js';
7
+ import { kitHome, readEnrolment, readOwnAgent } from './home.js';
7
8
  import { callHouse, rpc, UNREACHABLE } from './mcp.js';
8
9
  import { refusalOf } from './refusals.js';
9
10
  import { relayed } from './relay.js';
@@ -11,6 +12,8 @@ import { parse } from 'yaml';
11
12
  const USAGE = "usage: house <tool> ['<arguments as JSON>']";
12
13
  const PUSH_USAGE = 'usage: house push [commit] [--owner]';
13
14
  const PUSH = "push [commit]: sends the Local copy's committed changes to House";
15
+ const FETCH_USAGE = 'usage: house fetch';
16
+ const FETCH = `fetch: records House's changes on each Local copy's ${HOUSE_REF}`;
14
17
  const ARGUMENTS = `the arguments are one JSON object in single quotes, like house search '{"query":"invoice"}'`;
15
18
  const CLOSED = "this conversation's House connection is closed; nothing to do from here";
16
19
  const SANDBOXED = "your CLI's sandbox kept house from House; run house directly, not through another program";
@@ -53,6 +56,7 @@ async function direct(message) {
53
56
  const ownAgent = await readOwnAgent();
54
57
  if (ownAgent === null)
55
58
  throw new Error(UNCONNECTED);
59
+ await readEnrolment();
56
60
  setGlobalProxyFromEnv();
57
61
  const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
58
62
  throw new Error(UNREACHABLE);
@@ -125,6 +129,14 @@ async function push(args) {
125
129
  throw new Error(pushed.text);
126
130
  return pushed.text;
127
131
  }
132
+ async function fetched(args) {
133
+ if (args.includes('--help'))
134
+ return FETCH_USAGE;
135
+ const answer = JSON.parse(await bridged('/git/fetch', {}, undefined, socketPath ?? join(kitHome(), 'owner.sock')));
136
+ if (answer.refused)
137
+ throw new Error(answer.text);
138
+ return answer.text;
139
+ }
128
140
  function argumentsOf(argument) {
129
141
  if (argument === undefined)
130
142
  return {};
@@ -141,7 +153,7 @@ function argumentsOf(argument) {
141
153
  }
142
154
  async function house([verb, argument]) {
143
155
  if (verb === undefined || verb === '--help' || verb === 'help') {
144
- return [USAGE, 'help', PUSH, ...(await tools()).map((tool) => (tool.description === undefined ? tool.name : `${tool.name}: ${tool.description}`))].join('\n');
156
+ return [USAGE, 'help', PUSH, FETCH, ...(await tools()).map((tool) => (tool.description === undefined ? tool.name : `${tool.name}: ${tool.description}`))].join('\n');
145
157
  }
146
158
  if (argument === '--help') {
147
159
  const tool = (await tools()).find((listed) => listed.name === verb);
@@ -170,7 +182,7 @@ async function called(name, args) {
170
182
  return text;
171
183
  }
172
184
  const argv = process.argv.slice(2);
173
- (argv[0] === 'push' ? push(argv.slice(1)) : house(argv)).then((text) => {
185
+ (argv[0] === 'push' ? push(argv.slice(1)) : argv[0] === 'fetch' ? fetched(argv.slice(1)) : house(argv)).then((text) => {
174
186
  process.stdout.write(`${text}\n`);
175
187
  if (argv[0] === 'shell')
176
188
  process.exitCode = Number(/^exit: (\d+)(?:\n|$)/.exec(text)?.[1] ?? 0);
@@ -1,8 +1,10 @@
1
+ import { HOUSE_REF } from './git.js';
1
2
  import { HOW_WE_WORK } from './skills.js';
2
- const HOUSE_LINE = 'Work with House through the `house` CLI: run `house --help`.';
3
- const FILE_LINE = 'To give the User a file or a page, upload it with `house upload` and link it; never start a server.';
4
- const READ_LINE = 'Before working in a Room, read `/rooms/private/rooms/<handle>/AGENTS.md`.';
5
- const COPIES_LINE = 'House Local copies; commit, then `house push`:';
3
+ const SOURCE_LINE = "From House:";
4
+ const HOUSE_LINE = "Work with House through the `house` CLI: run `house --help`, and `house find_command` for House's guides and formats.";
5
+ const FILE_LINE = 'To give the User a file, upload it with `house upload` and link it; for an App, a Site or a website, follow `house find_command \'{"query":"app"}\'`. Never start a server.';
6
+ const READ_LINE = "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.";
7
+ const COPIES_LINE = `House Local copies; before work run \`house fetch\` and rebase onto \`${HOUSE_REF}\`; after it commit, then \`house push\`:`;
6
8
  const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
7
9
  const FAILED = /^exit: [1-9]\d*$/;
8
10
  const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
@@ -29,6 +31,6 @@ async function howWeWork(bridge) {
29
31
  return document.replace(/\n$/, '');
30
32
  }
31
33
  export async function instructions(bridge, base, copies) {
32
- const lines = [HOUSE_LINE, FILE_LINE, READ_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
34
+ const lines = [SOURCE_LINE, HOUSE_LINE, FILE_LINE, READ_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
33
35
  return [lines.join('\n'), base, await howWeWork(bridge)].filter((part) => part !== '').join('\n\n');
34
36
  }
package/dist/login.js CHANGED
@@ -6,6 +6,8 @@ import { post, retrying } from './api.js';
6
6
  import { placeAppGit } from './app-git.js';
7
7
  import { forgetOwnAgent, readDisconnected, readEnrolment, readOwnAgent, writeEnrolment, writeOwnAgent } from './home.js';
8
8
  import { callHouse, rpc } from './mcp.js';
9
+ import { selectRelay } from './relay.js';
10
+ import { startService } from './service.js';
9
11
  const DEFAULT_HOUSE = 'https://agents.house';
10
12
  const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
11
13
  // Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
@@ -23,39 +25,93 @@ async function accepted(house, credential) {
23
25
  return false;
24
26
  throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
25
27
  }
28
+ const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private] [--code <code>]
29
+ --house <origin> the House to connect to, ${DEFAULT_HOUSE} unless one is stored
30
+ --environment <id> reconnect this Environment
31
+ --rooms <ref>[,<ref>...] give this Environment only these Rooms
32
+ --no-private give this Environment every Room but your Private Room
33
+ --code <code> connect this machine with a login code
34
+ `;
35
+ function chosenRooms(rooms, noPrivate) {
36
+ if (rooms !== undefined && noPrivate)
37
+ throw new Error('--rooms and --no-private are two choices of Rooms; pass one');
38
+ if (rooms !== undefined)
39
+ return { rooms: { reach: 'selected', refs: rooms.split(',') } };
40
+ return noPrivate ? { rooms: { reach: 'all_but_private' } } : {};
41
+ }
26
42
  export async function login(argv) {
27
43
  const { values } = parseArgs({
28
44
  args: argv,
29
45
  options: {
30
46
  house: { type: 'string' },
31
47
  environment: { type: 'string' },
48
+ rooms: { type: 'string' },
49
+ 'no-private': { type: 'boolean', default: false },
50
+ code: { type: 'string' },
51
+ help: { type: 'boolean', default: false },
32
52
  },
33
53
  });
54
+ if (values.help) {
55
+ process.stdout.write(USAGE);
56
+ return;
57
+ }
58
+ if (values.code !== undefined && values.rooms !== undefined)
59
+ throw new Error('--code cannot be used with --rooms');
60
+ if (values.code !== undefined && values['no-private'])
61
+ throw new Error('--code cannot be used with --no-private');
62
+ if (values.code !== undefined && values.environment !== undefined)
63
+ throw new Error('--code cannot be used with --environment');
64
+ const rooms = chosenRooms(values.rooms, values['no-private']);
34
65
  const enrolled = (await readEnrolment()) ?? (await readDisconnected());
66
+ if (values.code !== undefined && enrolled !== null) {
67
+ throw new Error(`--code connects a new machine; this Kit home records Environment ${enrolled.environment}, so run kit login to reconnect it`);
68
+ }
69
+ const relay = process.env.HOUSE_KIT_RELAY || undefined;
70
+ selectRelay(relay);
35
71
  const house = values.house ?? enrolled?.house ?? DEFAULT_HOUSE;
36
72
  const replaces = process.env.HOUSE_KIT_REPLACES;
37
73
  const environment = replaces === undefined ? (values.environment ?? (enrolled?.house === house ? enrolled.environment : undefined)) : undefined;
38
74
  const ownAgent = await readOwnAgent();
39
- const held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
40
- const verifier = randomBytes(32).toString('base64url');
41
- const started = await retrying(() => post(house, '/kit', {
42
- ...(environment === undefined
43
- ? { act: 'new_environment', label: hostname(), ...(replaces === undefined ? {} : { replaces }) }
44
- : { act: 'existing_environment', environment_id: environment }),
45
- code_challenge: createHash('sha256').update(verifier).digest('base64url'),
46
- ...(held ? {} : { headless: true }),
47
- }));
48
- process.stdout.write(`Open this link and confirm: ${started.start_url}\n`);
49
- open(started.start_url);
50
- const exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
51
- await writeEnrolment({ house, environment: exchanged.environment, credential: exchanged.credential });
75
+ let held = false;
76
+ let exchanged;
77
+ if (values.code !== undefined) {
78
+ exchanged = await post(house, '/kit/token', { code: values.code, label: hostname() });
79
+ }
80
+ else {
81
+ held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
82
+ const verifier = randomBytes(32).toString('base64url');
83
+ const started = await retrying(() => post(house, '/kit', {
84
+ label: hostname(),
85
+ ...(environment === undefined
86
+ ? { act: 'new_environment', ...(replaces === undefined ? {} : { replaces }) }
87
+ : { act: 'existing_environment', environment_id: environment }),
88
+ ...rooms,
89
+ code_challenge: createHash('sha256').update(verifier).digest('base64url'),
90
+ ...(held ? {} : { headless: true }),
91
+ }));
92
+ process.stdout.write(`Open this link and confirm: ${started.start_url}\n`);
93
+ open(started.start_url);
94
+ exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
95
+ }
96
+ await writeEnrolment({
97
+ house,
98
+ ...(relay === undefined ? {} : { relay }),
99
+ environment: exchanged.environment,
100
+ credential: exchanged.credential,
101
+ });
52
102
  process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
53
103
  if (exchanged.headless_credential !== undefined) {
54
104
  await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
55
105
  }
56
- else if (held && ownAgent.user !== exchanged.user) {
106
+ else if (held && ownAgent !== null && ownAgent.user !== exchanged.user) {
57
107
  await forgetOwnAgent();
58
108
  process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
59
109
  }
60
110
  await placeAppGit(ownAgent, await readOwnAgent());
111
+ try {
112
+ startService();
113
+ }
114
+ catch (error) {
115
+ process.stderr.write(`kit: ${error.message}\n`);
116
+ }
61
117
  }
package/dist/relay.js CHANGED
@@ -1,7 +1,17 @@
1
+ let selected;
2
+ export function selectRelay(relay) {
3
+ selected = relay === undefined || relay === '' ? null : relay;
4
+ }
5
+ function relayOrigin() {
6
+ if (selected !== undefined)
7
+ return selected ?? undefined;
8
+ const ambient = process.env.HOUSE_KIT_RELAY;
9
+ return ambient === undefined || ambient === '' ? undefined : ambient;
10
+ }
1
11
  export function relayed(target) {
2
12
  const url = new URL(target);
3
- const relay = process.env.HOUSE_KIT_RELAY;
4
- if (relay === undefined || relay === '')
13
+ const relay = relayOrigin();
14
+ if (relay === undefined)
5
15
  return url;
6
16
  const through = new URL(`${url.host}${url.pathname}${url.search}`, `${relay}/`);
7
17
  if (url.protocol === 'wss:' || url.protocol === 'ws:')
package/dist/resident.js CHANGED
@@ -9,7 +9,8 @@ import { house } from './api.js';
9
9
  import { received } from './bridge.js';
10
10
  import { Conversations } from './conversations.js';
11
11
  import { LocalCopies } from './copies.js';
12
- import { agentBase, enrolled, kitHome } from './home.js';
12
+ import { restart } from './end.js';
13
+ import { agentBase, enrolled, forgetRestart, kitHome, restartAsked } from './home.js';
13
14
  import { killDescendants } from './scope.js';
14
15
  import { quoted } from './shell.js';
15
16
  import { SignIns } from './sign-in.js';
@@ -33,13 +34,20 @@ async function serveOwner(copies) {
33
34
  await rm(socketPath, { force: true });
34
35
  const server = createServer(async (request, response) => {
35
36
  try {
36
- if (request.url !== '/git/push') {
37
+ const text = await received(request);
38
+ let answered;
39
+ if (request.url === '/git/fetch') {
40
+ answered = await copies.fetch();
41
+ }
42
+ else if (request.url === '/git/push') {
43
+ const { cwd, commit } = JSON.parse(text);
44
+ answered = await copies.push(cwd, commit, copies.owner);
45
+ }
46
+ else {
37
47
  response.writeHead(404).end();
38
48
  return;
39
49
  }
40
- const { cwd, commit } = JSON.parse(await received(request));
41
- const pushed = await copies.push(cwd, commit, copies.owner);
42
- response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(pushed));
50
+ response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(answered));
43
51
  }
44
52
  catch (error) {
45
53
  response.writeHead(502).end(error instanceof Error ? error.message : String(error));
@@ -66,6 +74,7 @@ function connected() {
66
74
  }
67
75
  export async function resident() {
68
76
  await connected();
77
+ await forgetRestart();
69
78
  watch(kitHome(), () => {
70
79
  if (enrolled())
71
80
  return;
@@ -88,6 +97,9 @@ export async function resident() {
88
97
  if (!idle()) {
89
98
  reported = false;
90
99
  }
100
+ else if (restartAsked() && conversations.quiet()) {
101
+ restart('restarting for an updated CLI');
102
+ }
91
103
  else if (!reported) {
92
104
  reported = true;
93
105
  logged(house.deliver('/kit/idle', {}, idle));
@@ -115,18 +127,13 @@ export async function resident() {
115
127
  opened: () => {
116
128
  conversations.opened();
117
129
  changed();
118
- logged(copies.select());
130
+ logged(copies.connected());
119
131
  },
120
132
  frame: (arrived) => {
121
133
  if (arrived.type === 'watching')
122
134
  conversations.watching(arrived.watching === true);
123
135
  if (arrived.type === 'work_available' && arrived.subject === 'agents')
124
136
  logged(agents.refresh());
125
- if (arrived.type === 'work_available' && arrived.subject === 'local_copy')
126
- logged(copies.select());
127
- if (arrived.type === 'entries') {
128
- copies.entries(arrived);
129
- }
130
137
  if (arrived.type === 'input') {
131
138
  reported = false;
132
139
  if (arrived.kind === 'sign_in')
@@ -0,0 +1,9 @@
1
+ import { startService } from './service.js';
2
+ try {
3
+ if (startService())
4
+ process.stdout.write('started\n');
5
+ }
6
+ catch (error) {
7
+ process.stderr.write(`kit: ${error.message}\n`);
8
+ process.exitCode = 1;
9
+ }
@@ -0,0 +1,33 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ const UNIT = 'house-kit.service';
3
+ // launchctl and systemctl answer at once, so ten seconds only bounds a stuck service manager.
4
+ const MANAGER_MS = 10_000;
5
+ function manage(command, args) {
6
+ return spawnSync(command, args, { encoding: 'utf8', timeout: MANAGER_MS });
7
+ }
8
+ function start(command, args) {
9
+ const started = manage(command, args);
10
+ if (started.status === 0)
11
+ return;
12
+ const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
13
+ throw new Error(`the House Kit service could not be started: ${cause}`);
14
+ }
15
+ export function startService() {
16
+ if (process.platform === 'darwin') {
17
+ const service = `gui/${process.getuid()}/house-kit`;
18
+ const printed = manage('launchctl', ['print', service]);
19
+ if (printed.status !== 0 || /^\tstate = running$/m.test(printed.stdout))
20
+ return false;
21
+ start('launchctl', ['kickstart', service]);
22
+ return true;
23
+ }
24
+ for (const scope of [['--user'], []]) {
25
+ if (manage('systemctl', [...scope, 'is-enabled', '--quiet', UNIT]).status !== 0)
26
+ continue;
27
+ if (manage('systemctl', [...scope, 'is-active', '--quiet', UNIT]).status === 0)
28
+ return false;
29
+ start('systemctl', [...scope, 'start', UNIT]);
30
+ return true;
31
+ }
32
+ return false;
33
+ }
package/dist/stream.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { subscribe } from 'node:diagnostics_channel';
2
2
  import { finalRefusal, HouseRefusal, retryDelay } from './api.js';
3
- import { end, REPLACED, SIGN_IN_AGAIN } from './end.js';
3
+ import { end, REPLACED, signInAgain } from './end.js';
4
4
  import { readEnrolment } from './home.js';
5
5
  import { relayed } from './relay.js';
6
6
  const PATH = '/kit/stream';
@@ -52,8 +52,10 @@ export function holdStream(handlers) {
52
52
  process.stderr.write(`kit: control stream closed ${event.code} ${event.reason}\n`);
53
53
  if (event.reason === 'replaced')
54
54
  end(REPLACED);
55
- if (event.reason === 'credential_rejected' || refused?.status === 401)
56
- end(SIGN_IN_AGAIN);
55
+ if (event.reason === 'credential_rejected' || refused?.status === 401) {
56
+ void signInAgain(credential);
57
+ return;
58
+ }
57
59
  if (finalRefusal(refused))
58
60
  end(refused.message);
59
61
  // A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused as retryable or left unanswered backs off on the jittered curve.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.2.1-cbabe8ad337ff",
3
+ "version": "0.2.1-cca496635583e",
4
4
  "description": "House Kit connects an Environment to agents.house.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@agentclientprotocol/sdk": "1.7.0",
30
- "@agentshouse/mdmodel": "0.1.0-alpha.19",
30
+ "@agentshouse/mdmodel": "0.1.0-c68e2039f1ca8",
31
31
  "yaml": "2.9.1"
32
32
  },
33
33
  "peerDependencies": {