@agentshouse/kit 0.2.1-cca496635583e → 0.2.1-ccf188e02985d
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 +23 -22
- 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 +51 -9
- package/dist/conversations.js +137 -70
- package/dist/copies.js +78 -34
- 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 -189
- package/dist/inputs.js +37 -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 +47 -13
- package/dist/logout.js +0 -2
- package/dist/parts.js +34 -16
- 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/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 +2 -1
- 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
|
}
|
|
@@ -56,6 +66,9 @@ function sidecar(path) {
|
|
|
56
66
|
segments.splice(-1, 0, PROVENANCE);
|
|
57
67
|
return segments.join('/');
|
|
58
68
|
}
|
|
69
|
+
function recordOf(key) {
|
|
70
|
+
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
71
|
+
}
|
|
59
72
|
function rootOf(handle) {
|
|
60
73
|
return `/rooms/${handle}`;
|
|
61
74
|
}
|
|
@@ -202,48 +215,62 @@ export class LocalCopies {
|
|
|
202
215
|
async mapping() {
|
|
203
216
|
await this.answering;
|
|
204
217
|
return [...this.copies.values()]
|
|
205
|
-
.filter((copy) => copy.
|
|
218
|
+
.filter((copy) => copy.erasing === undefined)
|
|
206
219
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
207
220
|
.sort();
|
|
208
221
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
connected() {
|
|
223
|
+
return this.selected();
|
|
224
|
+
}
|
|
225
|
+
async changed(rooms) {
|
|
226
|
+
if (rooms.length > 0)
|
|
227
|
+
await this.selected(rooms);
|
|
213
228
|
}
|
|
214
229
|
fetch() {
|
|
215
230
|
return this.answered(() => this.fetched(true));
|
|
216
231
|
}
|
|
232
|
+
async selected(rooms) {
|
|
233
|
+
const copied = await this.answered(() => this.fetched(false, rooms));
|
|
234
|
+
if (copied.refused)
|
|
235
|
+
logged(copied.text);
|
|
236
|
+
}
|
|
217
237
|
answered(work) {
|
|
218
238
|
const answered = this.answering.then(work);
|
|
219
239
|
this.answering = answered.catch(() => undefined);
|
|
220
240
|
return answered;
|
|
221
241
|
}
|
|
222
|
-
async fetched(refreshing) {
|
|
223
|
-
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
|
|
242
|
+
async fetched(refreshing, rooms) {
|
|
243
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
224
244
|
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
225
|
-
|
|
245
|
+
const named = rooms === undefined ? null : new Set(rooms.map((room) => `rooms/${room}`));
|
|
246
|
+
const held = () => [...this.copies.values()].filter((copy) => named === null || named.has(copy.key));
|
|
247
|
+
return this.holding([...new Set([...held().map((copy) => copy.key), ...synced.keys()])].sort(), async () => {
|
|
226
248
|
const said = [];
|
|
227
249
|
let refused = false;
|
|
228
|
-
for (const copy of
|
|
229
|
-
|
|
250
|
+
for (const copy of held()) {
|
|
251
|
+
const returned = synced.has(copy.key);
|
|
252
|
+
if (returned && copy.erasing === undefined)
|
|
230
253
|
continue;
|
|
231
|
-
|
|
232
|
-
|
|
254
|
+
try {
|
|
255
|
+
await this.erase(copy);
|
|
256
|
+
said.push(`${rootOf(copy.room_handle)}: ${returned ? '' : 'no longer synced to this Environment; '}erased from the Local copy`);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
refused = true;
|
|
260
|
+
said.push(`${rootOf(copy.room_handle)}: ${causeOf(error)}`);
|
|
261
|
+
}
|
|
233
262
|
}
|
|
234
263
|
const current = [];
|
|
235
264
|
for (const [key, handle] of synced) {
|
|
236
265
|
try {
|
|
237
266
|
const held = this.copies.get(key);
|
|
267
|
+
if (held?.erasing)
|
|
268
|
+
continue;
|
|
238
269
|
if (held === undefined || !(await exists(held.repository))) {
|
|
239
270
|
said.push(await this.materialize(key, handle));
|
|
240
271
|
continue;
|
|
241
272
|
}
|
|
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 }))));
|
|
273
|
+
current.push(held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle }));
|
|
247
274
|
}
|
|
248
275
|
catch (error) {
|
|
249
276
|
refused = true;
|
|
@@ -257,7 +284,7 @@ export class LocalCopies {
|
|
|
257
284
|
}
|
|
258
285
|
catch (error) {
|
|
259
286
|
refused = true;
|
|
260
|
-
said.push(
|
|
287
|
+
said.push(houseCause(error));
|
|
261
288
|
}
|
|
262
289
|
}
|
|
263
290
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
@@ -269,6 +296,8 @@ export class LocalCopies {
|
|
|
269
296
|
return;
|
|
270
297
|
await this.exclusive(key, async () => {
|
|
271
298
|
const copy = this.copies.get(key);
|
|
299
|
+
if (copy === undefined || copy.erasing)
|
|
300
|
+
return;
|
|
272
301
|
let moves = copy.moves;
|
|
273
302
|
for (const { from, to } of report.moves) {
|
|
274
303
|
const held = moves.find((move) => move.worktree === report.worktree && move.to === from);
|
|
@@ -291,11 +320,11 @@ export class LocalCopies {
|
|
|
291
320
|
async push(cwd, selector, caller) {
|
|
292
321
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
293
322
|
if (located.status !== 0)
|
|
294
|
-
return
|
|
323
|
+
return outside(cwd);
|
|
295
324
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
296
325
|
const key = await this.keyAt(dirname(common));
|
|
297
326
|
if (key === undefined)
|
|
298
|
-
return
|
|
327
|
+
return outside(worktree);
|
|
299
328
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
300
329
|
}
|
|
301
330
|
async keyAt(repository) {
|
|
@@ -316,7 +345,7 @@ export class LocalCopies {
|
|
|
316
345
|
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
317
346
|
}
|
|
318
347
|
async save(copy) {
|
|
319
|
-
const path =
|
|
348
|
+
const path = recordOf(copy.key);
|
|
320
349
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
321
350
|
const temporary = `${path}.${process.pid}`;
|
|
322
351
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -324,6 +353,12 @@ export class LocalCopies {
|
|
|
324
353
|
this.copies.set(copy.key, copy);
|
|
325
354
|
return copy;
|
|
326
355
|
}
|
|
356
|
+
async erase(copy) {
|
|
357
|
+
await this.save({ ...copy, erasing: true });
|
|
358
|
+
await rm(copy.repository, { recursive: true, force: true });
|
|
359
|
+
await rm(recordOf(copy.key), { force: true });
|
|
360
|
+
this.copies.delete(copy.key);
|
|
361
|
+
}
|
|
327
362
|
async read(handle) {
|
|
328
363
|
const prefixes = [rootOf(handle)];
|
|
329
364
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
@@ -390,7 +425,6 @@ export class LocalCopies {
|
|
|
390
425
|
room_ref: read.anchor,
|
|
391
426
|
room_handle: handle,
|
|
392
427
|
repository,
|
|
393
|
-
active: true,
|
|
394
428
|
house,
|
|
395
429
|
position: read.position,
|
|
396
430
|
log_epoch: read.log_epoch,
|
|
@@ -519,6 +553,8 @@ export class LocalCopies {
|
|
|
519
553
|
}
|
|
520
554
|
async pushed(key, worktree, selector, caller) {
|
|
521
555
|
let copy = this.copies.get(key);
|
|
556
|
+
if (copy === undefined || copy.erasing)
|
|
557
|
+
return outside(worktree);
|
|
522
558
|
if (copy.push?.state === 'submitted')
|
|
523
559
|
return this.settle(copy, caller);
|
|
524
560
|
const selected = await run(worktree, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${selector}^{commit}`]);
|
|
@@ -532,9 +568,6 @@ export class LocalCopies {
|
|
|
532
568
|
}
|
|
533
569
|
copy = await this.save(without(copy));
|
|
534
570
|
}
|
|
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
571
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
539
572
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
540
573
|
if (base === undefined) {
|
|
@@ -545,7 +578,7 @@ export class LocalCopies {
|
|
|
545
578
|
changes = await this.derived(copy, base, commit);
|
|
546
579
|
}
|
|
547
580
|
catch (error) {
|
|
548
|
-
return { refused: true, text:
|
|
581
|
+
return { refused: true, text: houseCause(error) };
|
|
549
582
|
}
|
|
550
583
|
if (changes.length === 0)
|
|
551
584
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -572,7 +605,18 @@ export class LocalCopies {
|
|
|
572
605
|
const after = await tree(copy.repository, commit);
|
|
573
606
|
const moved = movedPaths(copy.moves, before, after);
|
|
574
607
|
const arrived = new Set(moved.values());
|
|
575
|
-
const
|
|
608
|
+
const arriving = [...after].filter(([path, entry]) => before.get(path) !== entry);
|
|
609
|
+
if (arriving.length > 0) {
|
|
610
|
+
const { bounds } = await this.house.post('/kit/door/discover', { prefixes: [root] });
|
|
611
|
+
const binary = [];
|
|
612
|
+
for (const [path, entry] of arriving) {
|
|
613
|
+
if (!(await isText(copy.repository, entry, bounds.max_authored_file_bytes)))
|
|
614
|
+
binary.push(path);
|
|
615
|
+
}
|
|
616
|
+
if (binary.length > 0)
|
|
617
|
+
throw new Error(textOnly(binary));
|
|
618
|
+
}
|
|
619
|
+
const text = (entry) => blobText(copy.repository, entry);
|
|
576
620
|
const changes = [];
|
|
577
621
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
578
622
|
if (arrived.has(path))
|
|
@@ -585,7 +629,7 @@ export class LocalCopies {
|
|
|
585
629
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
586
630
|
const moving = after.get(to);
|
|
587
631
|
if (moving !== prior) {
|
|
588
|
-
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving
|
|
632
|
+
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
|
|
589
633
|
}
|
|
590
634
|
continue;
|
|
591
635
|
}
|
|
@@ -594,9 +638,9 @@ export class LocalCopies {
|
|
|
594
638
|
if (next === undefined)
|
|
595
639
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
596
640
|
else if (prior === undefined)
|
|
597
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
641
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
598
642
|
else
|
|
599
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
643
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
600
644
|
}
|
|
601
645
|
return changes;
|
|
602
646
|
}
|
|
@@ -663,7 +707,7 @@ export class LocalCopies {
|
|
|
663
707
|
written.push({
|
|
664
708
|
path: `${root}/${path}`,
|
|
665
709
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
666
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
710
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
667
711
|
});
|
|
668
712
|
}
|
|
669
713
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -707,7 +751,7 @@ export class LocalCopies {
|
|
|
707
751
|
{
|
|
708
752
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
709
753
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
710
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
754
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
711
755
|
},
|
|
712
756
|
];
|
|
713
757
|
}
|
|
@@ -752,7 +796,7 @@ export class LocalCopies {
|
|
|
752
796
|
async landed(repository, changes, house, selected) {
|
|
753
797
|
const text = async (path) => {
|
|
754
798
|
const entry = house.get(relative(path));
|
|
755
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
799
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
756
800
|
};
|
|
757
801
|
for (const change of changes) {
|
|
758
802
|
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 });
|