@agentshouse/kit 0.2.1-ce145e9aadbf6 → 0.2.1-cf669c6d1ecbb
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 +26 -26
- package/dist/acp.js +16 -93
- package/dist/agents.js +53 -30
- package/dist/api.js +30 -2
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +33 -51
- package/dist/clis.js +85 -70
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +284 -236
- package/dist/copies.js +130 -107
- package/dist/end.js +18 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +521 -0
- package/dist/files.js +14 -0
- package/dist/home.js +41 -3
- package/dist/house-main.js +43 -13
- package/dist/instructions.js +7 -5
- package/dist/kit-main.js +3 -0
- package/dist/login.js +83 -102
- package/dist/logout.js +16 -13
- package/dist/parts.js +82 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +30 -23
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +3 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +33 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -10
- package/dist/stream.js +31 -4
- package/dist/transcription.js +54 -6
- package/dist/update.js +2 -0
- package/dist/work.js +348 -0
- package/package.json +2 -2
- package/dist/blocks.js +0 -51
package/dist/copies.js
CHANGED
|
@@ -2,7 +2,7 @@ import { parse } from '@agentshouse/mdmodel';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
|
-
import { HouseRefusal } from './api.js';
|
|
5
|
+
import { finalRefusal, HouseRefusal, retrying } from './api.js';
|
|
6
6
|
import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
|
|
7
7
|
import { kitHome } from './home.js';
|
|
8
8
|
import { operationId } from './operation.js';
|
|
@@ -11,7 +11,6 @@ import { changed } from './translate.js';
|
|
|
11
11
|
import { relayed } from './relay.js';
|
|
12
12
|
const BRANCH = 'main';
|
|
13
13
|
const PROVENANCE = '_provenance';
|
|
14
|
-
const REPLICA_RETRY_MS = 30_000;
|
|
15
14
|
const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
|
|
16
15
|
function causeOf(error) {
|
|
17
16
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -33,6 +32,22 @@ function rebaseFixes(refusal) {
|
|
|
33
32
|
function short(commit) {
|
|
34
33
|
return commit.slice(0, 12);
|
|
35
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
|
+
}
|
|
36
51
|
function covers(path, prefix) {
|
|
37
52
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
38
53
|
}
|
|
@@ -153,19 +168,18 @@ export class LocalCopies {
|
|
|
153
168
|
house;
|
|
154
169
|
copies = new Map();
|
|
155
170
|
queues = new Map();
|
|
156
|
-
|
|
157
|
-
selecting = Promise.resolve();
|
|
171
|
+
answering = Promise.resolve();
|
|
158
172
|
constructor(house) {
|
|
159
173
|
this.house = house;
|
|
160
174
|
this.owner = {
|
|
161
175
|
key: 'owner',
|
|
162
176
|
submit: async (operation, changes) => {
|
|
163
177
|
try {
|
|
164
|
-
const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
|
|
178
|
+
const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
|
|
165
179
|
return { accepted: outcome.contents };
|
|
166
180
|
}
|
|
167
181
|
catch (error) {
|
|
168
|
-
if (!(error
|
|
182
|
+
if (!finalRefusal(error))
|
|
169
183
|
throw error;
|
|
170
184
|
return {
|
|
171
185
|
refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
|
|
@@ -186,42 +200,68 @@ export class LocalCopies {
|
|
|
186
200
|
}
|
|
187
201
|
}
|
|
188
202
|
async mapping() {
|
|
189
|
-
await this.
|
|
203
|
+
await this.answering;
|
|
190
204
|
return [...this.copies.values()]
|
|
191
205
|
.filter((copy) => copy.active)
|
|
192
206
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
193
207
|
.sort();
|
|
194
208
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
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);
|
|
209
|
+
async connected() {
|
|
210
|
+
const copied = await this.answered(() => this.fetched(false));
|
|
211
|
+
if (copied.refused)
|
|
212
|
+
logged(copied.text);
|
|
217
213
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
});
|
|
225
265
|
}
|
|
226
266
|
async observed(report) {
|
|
227
267
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -271,16 +311,9 @@ export class LocalCopies {
|
|
|
271
311
|
this.queues.set(key, queued.catch(() => undefined));
|
|
272
312
|
return queued;
|
|
273
313
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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);
|
|
314
|
+
holding(keys, work) {
|
|
315
|
+
const [key, ...rest] = keys;
|
|
316
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
284
317
|
}
|
|
285
318
|
async save(copy) {
|
|
286
319
|
const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
|
|
@@ -326,46 +359,13 @@ export class LocalCopies {
|
|
|
326
359
|
}
|
|
327
360
|
return read;
|
|
328
361
|
}
|
|
329
|
-
async
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
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
|
-
}
|
|
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');
|
|
364
366
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
365
|
-
if (await exists(repository))
|
|
366
|
-
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
367
|
+
if (await exists(repository))
|
|
368
|
+
throw new Error(`${repository} already exists and House Kit leaves it as it is`);
|
|
369
369
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
370
370
|
await mkdir(staging, { recursive: true });
|
|
371
371
|
let house;
|
|
@@ -373,7 +373,7 @@ export class LocalCopies {
|
|
|
373
373
|
try {
|
|
374
374
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
375
375
|
house = await importCommits(staging, [
|
|
376
|
-
{ message: `House ${rootOf(
|
|
376
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
377
377
|
]);
|
|
378
378
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
379
379
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -386,9 +386,9 @@ export class LocalCopies {
|
|
|
386
386
|
throw error;
|
|
387
387
|
}
|
|
388
388
|
await this.save({
|
|
389
|
-
key
|
|
389
|
+
key,
|
|
390
390
|
room_ref: read.anchor,
|
|
391
|
-
room_handle:
|
|
391
|
+
room_handle: handle,
|
|
392
392
|
repository,
|
|
393
393
|
active: true,
|
|
394
394
|
house,
|
|
@@ -400,6 +400,7 @@ export class LocalCopies {
|
|
|
400
400
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
401
401
|
moves: [],
|
|
402
402
|
});
|
|
403
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
403
404
|
}
|
|
404
405
|
async received(copy) {
|
|
405
406
|
const read = await this.read(copy.room_handle);
|
|
@@ -423,30 +424,52 @@ export class LocalCopies {
|
|
|
423
424
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
424
425
|
return next;
|
|
425
426
|
}
|
|
426
|
-
async
|
|
427
|
-
|
|
428
|
-
|
|
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) {
|
|
429
446
|
let answer;
|
|
430
447
|
try {
|
|
431
448
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
432
|
-
prefixes:
|
|
433
|
-
positions:
|
|
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 })),
|
|
434
451
|
});
|
|
435
452
|
}
|
|
436
453
|
catch (error) {
|
|
437
|
-
const
|
|
438
|
-
if (
|
|
439
|
-
return this.received(copy);
|
|
440
|
-
if (code !== 'replica_behind')
|
|
454
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
455
|
+
if (expired === undefined)
|
|
441
456
|
throw error;
|
|
442
|
-
|
|
443
|
-
|
|
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);
|
|
444
469
|
}
|
|
445
|
-
|
|
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));
|
|
470
|
+
asking = whole ? advanced : [];
|
|
449
471
|
}
|
|
472
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
450
473
|
}
|
|
451
474
|
async incorporated(copy, changes, position, logEpoch) {
|
|
452
475
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -600,12 +623,12 @@ export class LocalCopies {
|
|
|
600
623
|
const { upload, ...unstaged } = copy.push;
|
|
601
624
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
602
625
|
}
|
|
603
|
-
if (outcome.refused.code === '
|
|
626
|
+
if (outcome.refused.code === 'expired')
|
|
604
627
|
return this.recovered(copy);
|
|
605
628
|
const rebased = rebaseFixes(outcome.refused);
|
|
606
629
|
let settled = await this.save(without(copy));
|
|
607
630
|
try {
|
|
608
|
-
settled =
|
|
631
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
609
632
|
}
|
|
610
633
|
catch {
|
|
611
634
|
return {
|
|
@@ -624,7 +647,7 @@ export class LocalCopies {
|
|
|
624
647
|
}
|
|
625
648
|
async recovered(copy) {
|
|
626
649
|
const push = copy.push;
|
|
627
|
-
const current =
|
|
650
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
628
651
|
const house = await tree(copy.repository, current.house);
|
|
629
652
|
const root = rootOf(current.room_handle);
|
|
630
653
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
@@ -668,7 +691,7 @@ export class LocalCopies {
|
|
|
668
691
|
const push = copy.push;
|
|
669
692
|
let written = push.written;
|
|
670
693
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
671
|
-
copy =
|
|
694
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
672
695
|
const house = await tree(copy.repository, copy.house);
|
|
673
696
|
const sent = await tree(copy.repository, push.house);
|
|
674
697
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
package/dist/end.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { forgetRejected } from './home.js';
|
|
2
|
+
import { killDescendants } from './scope.js';
|
|
3
|
+
export const REPLACED = 'another Kit started for this Environment';
|
|
4
|
+
function exit(reason, status) {
|
|
5
|
+
process.stderr.write(`kit: ${reason}\n`);
|
|
6
|
+
killDescendants();
|
|
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');
|
|
18
|
+
}
|
package/dist/enrol-main.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
});
|