@agentshouse/kit 0.2.1-cf110d15f69c8 → 0.2.1-cf6326dfa5c3e
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 +20 -19
- package/dist/acp.js +34 -14
- package/dist/agents.js +108 -21
- package/dist/api.js +5 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +3 -6
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +19 -3
- package/dist/conversations.js +209 -77
- package/dist/copies.js +98 -47
- package/dist/end.js +5 -0
- package/dist/events.js +4 -0
- package/dist/files.js +8 -2
- package/dist/git.js +10 -8
- package/dist/home.js +56 -5
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -206
- package/dist/inputs.js +63 -0
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +46 -28
- package/dist/logout.js +0 -2
- package/dist/placement.js +30 -0
- package/dist/relay.js +1 -1
- package/dist/resident.js +65 -16
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/skills.js +81 -12
- package/dist/stream.js +66 -17
- package/dist/transcription.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +48 -29
- package/dist/wire.js +6 -0
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +3 -2
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/copies.js
CHANGED
|
@@ -3,7 +3,7 @@ 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
5
|
import { finalRefusal, HouseRefusal, retrying } from './api.js';
|
|
6
|
-
import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.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';
|
|
@@ -15,6 +15,9 @@ const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-
|
|
|
15
15
|
function causeOf(error) {
|
|
16
16
|
return error instanceof Error ? error.message : String(error);
|
|
17
17
|
}
|
|
18
|
+
function houseCause(error) {
|
|
19
|
+
return error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
20
|
+
}
|
|
18
21
|
function logged(error) {
|
|
19
22
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
20
23
|
}
|
|
@@ -29,6 +32,13 @@ const REBASED = new Set([
|
|
|
29
32
|
function rebaseFixes(refusal) {
|
|
30
33
|
return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
|
|
31
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
|
+
}
|
|
39
|
+
function outside(path) {
|
|
40
|
+
return { refused: true, text: `${path} is not a House Local copy; run house push inside one` };
|
|
41
|
+
}
|
|
32
42
|
function short(commit) {
|
|
33
43
|
return commit.slice(0, 12);
|
|
34
44
|
}
|
|
@@ -48,6 +58,9 @@ function expiredAuthority(error) {
|
|
|
48
58
|
return undefined;
|
|
49
59
|
}
|
|
50
60
|
}
|
|
61
|
+
function grouped(items, size) {
|
|
62
|
+
return items.flatMap((_, index) => (index % size === 0 ? [items.slice(index, index + size)] : []));
|
|
63
|
+
}
|
|
51
64
|
function covers(path, prefix) {
|
|
52
65
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
53
66
|
}
|
|
@@ -56,6 +69,9 @@ function sidecar(path) {
|
|
|
56
69
|
segments.splice(-1, 0, PROVENANCE);
|
|
57
70
|
return segments.join('/');
|
|
58
71
|
}
|
|
72
|
+
function recordOf(key) {
|
|
73
|
+
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
74
|
+
}
|
|
59
75
|
function rootOf(handle) {
|
|
60
76
|
return `/rooms/${handle}`;
|
|
61
77
|
}
|
|
@@ -202,48 +218,62 @@ export class LocalCopies {
|
|
|
202
218
|
async mapping() {
|
|
203
219
|
await this.answering;
|
|
204
220
|
return [...this.copies.values()]
|
|
205
|
-
.filter((copy) => copy.
|
|
221
|
+
.filter((copy) => copy.erasing === undefined)
|
|
206
222
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
207
223
|
.sort();
|
|
208
224
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
225
|
+
connected() {
|
|
226
|
+
return this.selected();
|
|
227
|
+
}
|
|
228
|
+
async changed(rooms) {
|
|
229
|
+
if (rooms.length > 0)
|
|
230
|
+
await this.selected(rooms);
|
|
213
231
|
}
|
|
214
232
|
fetch() {
|
|
215
233
|
return this.answered(() => this.fetched(true));
|
|
216
234
|
}
|
|
235
|
+
async selected(rooms) {
|
|
236
|
+
const copied = await this.answered(() => this.fetched(false, rooms));
|
|
237
|
+
if (copied.refused)
|
|
238
|
+
logged(copied.text);
|
|
239
|
+
}
|
|
217
240
|
answered(work) {
|
|
218
241
|
const answered = this.answering.then(work);
|
|
219
242
|
this.answering = answered.catch(() => undefined);
|
|
220
243
|
return answered;
|
|
221
244
|
}
|
|
222
|
-
async fetched(refreshing) {
|
|
223
|
-
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
|
|
245
|
+
async fetched(refreshing, rooms) {
|
|
246
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
224
247
|
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
225
|
-
|
|
248
|
+
const named = rooms === undefined ? null : new Set(rooms.map((room) => `rooms/${room}`));
|
|
249
|
+
const held = () => [...this.copies.values()].filter((copy) => named === null || named.has(copy.key));
|
|
250
|
+
return this.holding([...new Set([...held().map((copy) => copy.key), ...synced.keys()])].sort(), async () => {
|
|
226
251
|
const said = [];
|
|
227
252
|
let refused = false;
|
|
228
|
-
for (const copy of
|
|
229
|
-
|
|
253
|
+
for (const copy of held()) {
|
|
254
|
+
const returned = synced.has(copy.key);
|
|
255
|
+
if (returned && copy.erasing === undefined)
|
|
230
256
|
continue;
|
|
231
|
-
|
|
232
|
-
|
|
257
|
+
try {
|
|
258
|
+
await this.erase(copy);
|
|
259
|
+
said.push(`${rootOf(copy.room_handle)}: ${returned ? '' : 'no longer synced to this Environment; '}erased from the Local copy`);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
refused = true;
|
|
263
|
+
said.push(`${rootOf(copy.room_handle)}: ${causeOf(error)}`);
|
|
264
|
+
}
|
|
233
265
|
}
|
|
234
266
|
const current = [];
|
|
235
267
|
for (const [key, handle] of synced) {
|
|
236
268
|
try {
|
|
237
269
|
const held = this.copies.get(key);
|
|
270
|
+
if (held?.erasing)
|
|
271
|
+
continue;
|
|
238
272
|
if (held === undefined || !(await exists(held.repository))) {
|
|
239
273
|
said.push(await this.materialize(key, handle));
|
|
240
274
|
continue;
|
|
241
275
|
}
|
|
242
|
-
|
|
243
|
-
if (copy.active)
|
|
244
|
-
current.push(copy);
|
|
245
|
-
else
|
|
246
|
-
said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
|
|
276
|
+
current.push(held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle }));
|
|
247
277
|
}
|
|
248
278
|
catch (error) {
|
|
249
279
|
refused = true;
|
|
@@ -251,13 +281,12 @@ export class LocalCopies {
|
|
|
251
281
|
}
|
|
252
282
|
}
|
|
253
283
|
if (refreshing && current.length > 0) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
catch (error) {
|
|
284
|
+
const failed = await this.refreshed(current).then(() => null, houseCause);
|
|
285
|
+
for (const copy of current)
|
|
286
|
+
said.push(...moved(copy, this.copies.get(copy.key)));
|
|
287
|
+
if (failed !== null) {
|
|
259
288
|
refused = true;
|
|
260
|
-
said.push(
|
|
289
|
+
said.push(failed);
|
|
261
290
|
}
|
|
262
291
|
}
|
|
263
292
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
@@ -269,6 +298,8 @@ export class LocalCopies {
|
|
|
269
298
|
return;
|
|
270
299
|
await this.exclusive(key, async () => {
|
|
271
300
|
const copy = this.copies.get(key);
|
|
301
|
+
if (copy === undefined || copy.erasing)
|
|
302
|
+
return;
|
|
272
303
|
let moves = copy.moves;
|
|
273
304
|
for (const { from, to } of report.moves) {
|
|
274
305
|
const held = moves.find((move) => move.worktree === report.worktree && move.to === from);
|
|
@@ -291,11 +322,11 @@ export class LocalCopies {
|
|
|
291
322
|
async push(cwd, selector, caller) {
|
|
292
323
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
293
324
|
if (located.status !== 0)
|
|
294
|
-
return
|
|
325
|
+
return outside(cwd);
|
|
295
326
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
296
327
|
const key = await this.keyAt(dirname(common));
|
|
297
328
|
if (key === undefined)
|
|
298
|
-
return
|
|
329
|
+
return outside(worktree);
|
|
299
330
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
300
331
|
}
|
|
301
332
|
async keyAt(repository) {
|
|
@@ -316,7 +347,7 @@ export class LocalCopies {
|
|
|
316
347
|
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
317
348
|
}
|
|
318
349
|
async save(copy) {
|
|
319
|
-
const path =
|
|
350
|
+
const path = recordOf(copy.key);
|
|
320
351
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
321
352
|
const temporary = `${path}.${process.pid}`;
|
|
322
353
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -324,6 +355,12 @@ export class LocalCopies {
|
|
|
324
355
|
this.copies.set(copy.key, copy);
|
|
325
356
|
return copy;
|
|
326
357
|
}
|
|
358
|
+
async erase(copy) {
|
|
359
|
+
await this.save({ ...copy, erasing: true });
|
|
360
|
+
await rm(copy.repository, { recursive: true, force: true });
|
|
361
|
+
await rm(recordOf(copy.key), { force: true });
|
|
362
|
+
this.copies.delete(copy.key);
|
|
363
|
+
}
|
|
327
364
|
async read(handle) {
|
|
328
365
|
const prefixes = [rootOf(handle)];
|
|
329
366
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
@@ -390,7 +427,6 @@ export class LocalCopies {
|
|
|
390
427
|
room_ref: read.anchor,
|
|
391
428
|
room_handle: handle,
|
|
392
429
|
repository,
|
|
393
|
-
active: true,
|
|
394
430
|
house,
|
|
395
431
|
position: read.position,
|
|
396
432
|
log_epoch: read.log_epoch,
|
|
@@ -425,19 +461,24 @@ export class LocalCopies {
|
|
|
425
461
|
return next;
|
|
426
462
|
}
|
|
427
463
|
async refreshed(copies) {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
});
|
|
431
|
-
const
|
|
464
|
+
const discover = (group) => this.house.post('/kit/door/discover', { prefixes: group.map((copy) => rootOf(copy.room_handle)) });
|
|
465
|
+
const [first, ...rest] = copies;
|
|
466
|
+
const { bounds, authorities } = await discover([first]);
|
|
467
|
+
for (const group of grouped(rest, bounds.max_prefixes))
|
|
468
|
+
authorities.push(...(await discover(group)).authorities);
|
|
469
|
+
const unchanged = [];
|
|
432
470
|
for (const copy of copies) {
|
|
433
|
-
const authority =
|
|
471
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
434
472
|
if (authority !== undefined &&
|
|
435
473
|
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
436
|
-
|
|
474
|
+
await this.save(await this.received(copy));
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
unchanged.push(copy);
|
|
437
478
|
}
|
|
438
479
|
}
|
|
439
|
-
const
|
|
440
|
-
|
|
480
|
+
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
481
|
+
await this.caughtUp(group, false);
|
|
441
482
|
}
|
|
442
483
|
async caughtUp(copies, whole) {
|
|
443
484
|
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
@@ -519,6 +560,8 @@ export class LocalCopies {
|
|
|
519
560
|
}
|
|
520
561
|
async pushed(key, worktree, selector, caller) {
|
|
521
562
|
let copy = this.copies.get(key);
|
|
563
|
+
if (copy === undefined || copy.erasing)
|
|
564
|
+
return outside(worktree);
|
|
522
565
|
if (copy.push?.state === 'submitted')
|
|
523
566
|
return this.settle(copy, caller);
|
|
524
567
|
const selected = await run(worktree, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${selector}^{commit}`]);
|
|
@@ -532,9 +575,6 @@ export class LocalCopies {
|
|
|
532
575
|
}
|
|
533
576
|
copy = await this.save(without(copy));
|
|
534
577
|
}
|
|
535
|
-
if (!copy.active) {
|
|
536
|
-
return { refused: true, text: `${rootOf(copy.room_handle)} is no longer synced to this Environment, so House takes no push from it` };
|
|
537
|
-
}
|
|
538
578
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
539
579
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
540
580
|
if (base === undefined) {
|
|
@@ -545,7 +585,7 @@ export class LocalCopies {
|
|
|
545
585
|
changes = await this.derived(copy, base, commit);
|
|
546
586
|
}
|
|
547
587
|
catch (error) {
|
|
548
|
-
return { refused: true, text:
|
|
588
|
+
return { refused: true, text: houseCause(error) };
|
|
549
589
|
}
|
|
550
590
|
if (changes.length === 0)
|
|
551
591
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -572,7 +612,18 @@ export class LocalCopies {
|
|
|
572
612
|
const after = await tree(copy.repository, commit);
|
|
573
613
|
const moved = movedPaths(copy.moves, before, after);
|
|
574
614
|
const arrived = new Set(moved.values());
|
|
575
|
-
const
|
|
615
|
+
const arriving = [...after].filter(([path, entry]) => before.get(path) !== entry);
|
|
616
|
+
if (arriving.length > 0) {
|
|
617
|
+
const { bounds } = await this.house.post('/kit/door/discover', { prefixes: [root] });
|
|
618
|
+
const binary = [];
|
|
619
|
+
for (const [path, entry] of arriving) {
|
|
620
|
+
if (!(await isText(copy.repository, entry, bounds.max_authored_file_bytes)))
|
|
621
|
+
binary.push(path);
|
|
622
|
+
}
|
|
623
|
+
if (binary.length > 0)
|
|
624
|
+
throw new Error(textOnly(binary));
|
|
625
|
+
}
|
|
626
|
+
const text = (entry) => blobText(copy.repository, entry);
|
|
576
627
|
const changes = [];
|
|
577
628
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
578
629
|
if (arrived.has(path))
|
|
@@ -585,7 +636,7 @@ export class LocalCopies {
|
|
|
585
636
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
586
637
|
const moving = after.get(to);
|
|
587
638
|
if (moving !== prior) {
|
|
588
|
-
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving
|
|
639
|
+
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
|
|
589
640
|
}
|
|
590
641
|
continue;
|
|
591
642
|
}
|
|
@@ -594,9 +645,9 @@ export class LocalCopies {
|
|
|
594
645
|
if (next === undefined)
|
|
595
646
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
596
647
|
else if (prior === undefined)
|
|
597
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
648
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
598
649
|
else
|
|
599
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
650
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
600
651
|
}
|
|
601
652
|
return changes;
|
|
602
653
|
}
|
|
@@ -663,7 +714,7 @@ export class LocalCopies {
|
|
|
663
714
|
written.push({
|
|
664
715
|
path: `${root}/${path}`,
|
|
665
716
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
666
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
717
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
667
718
|
});
|
|
668
719
|
}
|
|
669
720
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -707,7 +758,7 @@ export class LocalCopies {
|
|
|
707
758
|
{
|
|
708
759
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
709
760
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
710
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
761
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
711
762
|
},
|
|
712
763
|
];
|
|
713
764
|
}
|
|
@@ -752,7 +803,7 @@ export class LocalCopies {
|
|
|
752
803
|
async landed(repository, changes, house, selected) {
|
|
753
804
|
const text = async (path) => {
|
|
754
805
|
const entry = house.get(relative(path));
|
|
755
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
806
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
756
807
|
};
|
|
757
808
|
for (const change of changes) {
|
|
758
809
|
const current = await text(change.path);
|
package/dist/end.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { forgetRejected } from './home.js';
|
|
2
|
+
import { installCommand } from './placement.js';
|
|
2
3
|
import { killDescendants } from './scope.js';
|
|
3
4
|
export const REPLACED = 'another Kit started for this Environment';
|
|
4
5
|
function exit(reason, status) {
|
|
@@ -12,6 +13,10 @@ export function end(reason) {
|
|
|
12
13
|
export function restart(reason) {
|
|
13
14
|
exit(reason, 1);
|
|
14
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
|
+
}
|
|
15
20
|
export async function signInAgain(rejected) {
|
|
16
21
|
await forgetRejected(rejected);
|
|
17
22
|
restart('sign in again');
|
package/dist/events.js
CHANGED
|
@@ -512,6 +512,10 @@ class GrokReader extends Reader {
|
|
|
512
512
|
return [];
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
|
+
export function answeredFailure(answer) {
|
|
516
|
+
const failure = answer._meta?.jetbrains?.air?.sessionFailure;
|
|
517
|
+
return typeof failure?.title === 'string' ? { title: failure.title, limit: failure.category === 'limit' } : null;
|
|
518
|
+
}
|
|
515
519
|
export function readerFor(kind) {
|
|
516
520
|
if (kind === 'claude-agent-acp')
|
|
517
521
|
return new ClaudeReader();
|
package/dist/files.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { HouseRefusal, retrying } from './api.js';
|
|
4
5
|
import { relayed } from './relay.js';
|
|
6
|
+
export function verified(bytes, sha256, name) {
|
|
7
|
+
if (createHash('sha256').update(bytes).digest('hex') !== sha256)
|
|
8
|
+
throw new Error(`${name} did not arrive whole; download it again`);
|
|
9
|
+
return bytes;
|
|
10
|
+
}
|
|
5
11
|
export async function placeFiles(house, directory, files, signal) {
|
|
6
12
|
const paths = [];
|
|
7
13
|
for (const file of files) {
|
|
8
|
-
const original = await house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal);
|
|
14
|
+
const original = await retrying(() => house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal), signal);
|
|
9
15
|
const answer = await fetch(relayed(original.download.url), { signal });
|
|
10
16
|
if (!answer.ok)
|
|
11
17
|
throw new Error(`${file.name} did not download: ${answer.status}`);
|
|
@@ -16,7 +22,7 @@ export async function placeFiles(house, directory, files, signal) {
|
|
|
16
22
|
});
|
|
17
23
|
await mkdir(folder, { recursive: true });
|
|
18
24
|
const path = join(folder, file.name);
|
|
19
|
-
await writeFile(path, Buffer.from(await answer.arrayBuffer()));
|
|
25
|
+
await writeFile(path, verified(Buffer.from(await answer.arrayBuffer()), file.sha256, file.name));
|
|
20
26
|
paths.push(path);
|
|
21
27
|
}
|
|
22
28
|
return paths;
|
package/dist/git.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { isUtf8 } from 'node:buffer';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
5
|
import { withoutProxy } from './clis.js';
|
|
@@ -48,14 +49,15 @@ export async function tree(repository, commit) {
|
|
|
48
49
|
}
|
|
49
50
|
return entries;
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
async function blob(repository, object) {
|
|
53
|
+
return (await run(repository, ['cat-file', 'blob', object])).stdout;
|
|
54
|
+
}
|
|
55
|
+
export async function blobText(repository, object) {
|
|
56
|
+
return (await blob(repository, object)).toString('utf8');
|
|
57
|
+
}
|
|
58
|
+
export async function isText(repository, object, maxBytes) {
|
|
59
|
+
const bytes = await blob(repository, object);
|
|
60
|
+
return bytes.length <= maxBytes && isUtf8(bytes) && !bytes.includes(0);
|
|
59
61
|
}
|
|
60
62
|
export function blobId(content) {
|
|
61
63
|
const bytes = Buffer.from(content, 'utf8');
|
package/dist/home.js
CHANGED
|
@@ -1,15 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { accessSync, chmodSync, constants, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync } from 'node:fs';
|
|
2
3
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { homedir, tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
5
6
|
import { selectRelay } from './relay.js';
|
|
6
7
|
const ENROLMENT = 'credential.json';
|
|
7
8
|
const DISCONNECTED = 'disconnected.json';
|
|
8
9
|
const CONFIGURATION = 'kit.json';
|
|
9
10
|
const OWN_AGENT = 'own-agent.json';
|
|
10
11
|
const RESTART = 'restart';
|
|
12
|
+
let defaulted;
|
|
13
|
+
function writable(directory) {
|
|
14
|
+
try {
|
|
15
|
+
accessSync(directory, constants.W_OK | constants.X_OK);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function privateDirectory(path) {
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(path, { mode: 0o700 });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== 'EEXIST')
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const found = lstatSync(path);
|
|
31
|
+
if (found.isSymbolicLink())
|
|
32
|
+
throw new Error(`${path} is a symbolic link`);
|
|
33
|
+
if (!found.isDirectory())
|
|
34
|
+
throw new Error(`${path} is not a directory`);
|
|
35
|
+
if (found.uid !== process.geteuid())
|
|
36
|
+
throw new Error(`${path} is not owned by this User`);
|
|
37
|
+
chmodSync(path, 0o700);
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
function ownTemporaryHome() {
|
|
41
|
+
return privateDirectory(join(tmpdir(), `house-kit-${process.getuid()}`));
|
|
42
|
+
}
|
|
43
|
+
function defaultHome() {
|
|
44
|
+
const home = join(homedir(), '.house-kit');
|
|
45
|
+
return existsSync(home) || writable(homedir()) ? home : ownTemporaryHome();
|
|
46
|
+
}
|
|
11
47
|
export function kitHome() {
|
|
12
|
-
return process.env.HOUSE_KIT_HOME
|
|
48
|
+
return process.env.HOUSE_KIT_HOME || (defaulted ??= defaultHome());
|
|
49
|
+
}
|
|
50
|
+
export function socketDirectory() {
|
|
51
|
+
const sockets = privateDirectory(join(realpathSync('/tmp'), `hk-${process.geteuid()}`));
|
|
52
|
+
const home = kitHome();
|
|
53
|
+
const physical = existsSync(home) ? realpathSync(home) : resolve(home);
|
|
54
|
+
return privateDirectory(join(sockets, createHash('sha256').update(physical).digest('hex').slice(0, 12)));
|
|
55
|
+
}
|
|
56
|
+
export function ownerSocket() {
|
|
57
|
+
return join(socketDirectory(), 'owner.sock');
|
|
58
|
+
}
|
|
59
|
+
export function ownerLock() {
|
|
60
|
+
return join(socketDirectory(), 'owner.lock');
|
|
13
61
|
}
|
|
14
62
|
export function workspaceRoot() {
|
|
15
63
|
return process.env.HOUSE_KIT_WORKSPACE ?? '/agents/house';
|
|
@@ -30,7 +78,7 @@ export async function readHome(name) {
|
|
|
30
78
|
export async function writeHome(name, value) {
|
|
31
79
|
const home = kitHome();
|
|
32
80
|
await mkdir(home, { recursive: true, mode: 0o700 });
|
|
33
|
-
const temporary = join(home, `${name}.${
|
|
81
|
+
const temporary = join(home, `${name}.${randomUUID()}`);
|
|
34
82
|
await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
35
83
|
await rename(temporary, join(home, name));
|
|
36
84
|
}
|
|
@@ -47,6 +95,9 @@ export async function writeEnrolment(enrolment) {
|
|
|
47
95
|
export function readDisconnected() {
|
|
48
96
|
return readHome(DISCONNECTED);
|
|
49
97
|
}
|
|
98
|
+
export function forgetDisconnected() {
|
|
99
|
+
return rm(join(kitHome(), DISCONNECTED), { force: true });
|
|
100
|
+
}
|
|
50
101
|
export async function forgetEnrolment({ house, environment }) {
|
|
51
102
|
await writeHome(DISCONNECTED, { house, environment });
|
|
52
103
|
await rm(join(kitHome(), ENROLMENT), { force: true });
|