@agentshouse/kit 0.2.1-cf110d15f69c8 → 0.2.1-cf3a50c90b895
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 +31 -30
- package/dist/acp.js +36 -16
- package/dist/agents.js +110 -23
- 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/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +20 -4
- package/dist/connection.js +125 -0
- package/dist/conversations.js +307 -120
- package/dist/copies.js +89 -52
- package/dist/end.js +11 -2
- package/dist/events.js +6 -2
- package/dist/files.js +8 -2
- package/dist/git.js +11 -9
- package/dist/home.js +56 -5
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -206
- 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/mcp.js +2 -2
- package/dist/placement.js +39 -0
- package/dist/relay.js +1 -1
- package/dist/resident.js +82 -33
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +19 -37
- package/dist/skills.js +84 -15
- package/dist/transcription.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +69 -50
- package/dist/wire.js +22 -0
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +4 -3
- 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/dist/stream.js +0 -74
- 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';
|
|
@@ -11,10 +11,13 @@ import { changed } from './translate.js';
|
|
|
11
11
|
import { relayed } from './relay.js';
|
|
12
12
|
const BRANCH = 'main';
|
|
13
13
|
const PROVENANCE = '_provenance';
|
|
14
|
-
const IDENTITY = { GIT_COMMITTER_NAME: '
|
|
14
|
+
const IDENTITY = { GIT_COMMITTER_NAME: 'Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
|
|
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
|
}
|
|
@@ -117,7 +133,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
117
133
|
].map(pattern);
|
|
118
134
|
const granted = readOnly.filter((path) => !protectedPath(path)).map(pattern);
|
|
119
135
|
await ruled(join(repository, '.gitignore'), [
|
|
120
|
-
'#
|
|
136
|
+
'# Kit writes this file and .ignore. The paths below are read-only House',
|
|
121
137
|
'# content: Git does not track them, and House refuses a change to them even',
|
|
122
138
|
'# when this file is edited or a path is force-added.',
|
|
123
139
|
'/.gitignore',
|
|
@@ -132,7 +148,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
132
148
|
'',
|
|
133
149
|
].join('\n'));
|
|
134
150
|
await ruled(join(repository, '.ignore'), [
|
|
135
|
-
'#
|
|
151
|
+
'# Kit writes this file so native search still reads the read-only House',
|
|
136
152
|
'# content that .gitignore keeps out of Git.',
|
|
137
153
|
`!${PROVENANCE}`,
|
|
138
154
|
...[...house, ...granted].map((rule) => `!${rule}`),
|
|
@@ -202,34 +218,45 @@ 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.unfollowed === 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
|
-
|
|
229
|
-
|
|
253
|
+
const mark = refreshing ? 'said' : 'unsaid';
|
|
254
|
+
for (const copy of held()) {
|
|
255
|
+
if (synced.has(copy.key) || copy.unfollowed === 'said' || copy.unfollowed === mark)
|
|
230
256
|
continue;
|
|
231
|
-
await this.save({ ...copy,
|
|
232
|
-
|
|
257
|
+
await this.save({ ...copy, unfollowed: mark });
|
|
258
|
+
if (refreshing)
|
|
259
|
+
said.push(`${rootOf(copy.room_handle)}: no longer followed; kept as it is`);
|
|
233
260
|
}
|
|
234
261
|
const current = [];
|
|
235
262
|
for (const [key, handle] of synced) {
|
|
@@ -239,11 +266,8 @@ export class LocalCopies {
|
|
|
239
266
|
said.push(await this.materialize(key, handle));
|
|
240
267
|
continue;
|
|
241
268
|
}
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
current.push(copy);
|
|
245
|
-
else
|
|
246
|
-
said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
|
|
269
|
+
const { unfollowed, ...followed } = held;
|
|
270
|
+
current.push(unfollowed === undefined && held.room_handle === handle ? held : await this.save({ ...followed, room_handle: handle }));
|
|
247
271
|
}
|
|
248
272
|
catch (error) {
|
|
249
273
|
refused = true;
|
|
@@ -251,13 +275,12 @@ export class LocalCopies {
|
|
|
251
275
|
}
|
|
252
276
|
}
|
|
253
277
|
if (refreshing && current.length > 0) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
catch (error) {
|
|
278
|
+
const failed = await this.refreshed(current).then(() => null, houseCause);
|
|
279
|
+
for (const copy of current)
|
|
280
|
+
said.push(...moved(copy, this.copies.get(copy.key)));
|
|
281
|
+
if (failed !== null) {
|
|
259
282
|
refused = true;
|
|
260
|
-
said.push(
|
|
283
|
+
said.push(failed);
|
|
261
284
|
}
|
|
262
285
|
}
|
|
263
286
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
@@ -291,11 +314,11 @@ export class LocalCopies {
|
|
|
291
314
|
async push(cwd, selector, caller) {
|
|
292
315
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
293
316
|
if (located.status !== 0)
|
|
294
|
-
return
|
|
317
|
+
return outside(cwd);
|
|
295
318
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
296
319
|
const key = await this.keyAt(dirname(common));
|
|
297
320
|
if (key === undefined)
|
|
298
|
-
return
|
|
321
|
+
return outside(worktree);
|
|
299
322
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
300
323
|
}
|
|
301
324
|
async keyAt(repository) {
|
|
@@ -316,7 +339,7 @@ export class LocalCopies {
|
|
|
316
339
|
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
317
340
|
}
|
|
318
341
|
async save(copy) {
|
|
319
|
-
const path =
|
|
342
|
+
const path = recordOf(copy.key);
|
|
320
343
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
321
344
|
const temporary = `${path}.${process.pid}`;
|
|
322
345
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -365,7 +388,7 @@ export class LocalCopies {
|
|
|
365
388
|
throw new Error('House serves nothing of it to copy');
|
|
366
389
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
367
390
|
if (await exists(repository))
|
|
368
|
-
throw new Error(`${repository} already exists and
|
|
391
|
+
throw new Error(`${repository} already exists and Kit leaves it as it is`);
|
|
369
392
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
370
393
|
await mkdir(staging, { recursive: true });
|
|
371
394
|
let house;
|
|
@@ -390,7 +413,6 @@ export class LocalCopies {
|
|
|
390
413
|
room_ref: read.anchor,
|
|
391
414
|
room_handle: handle,
|
|
392
415
|
repository,
|
|
393
|
-
active: true,
|
|
394
416
|
house,
|
|
395
417
|
position: read.position,
|
|
396
418
|
log_epoch: read.log_epoch,
|
|
@@ -425,19 +447,24 @@ export class LocalCopies {
|
|
|
425
447
|
return next;
|
|
426
448
|
}
|
|
427
449
|
async refreshed(copies) {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
});
|
|
431
|
-
const
|
|
450
|
+
const discover = (group) => this.house.post('/kit/door/discover', { prefixes: group.map((copy) => rootOf(copy.room_handle)) });
|
|
451
|
+
const [first, ...rest] = copies;
|
|
452
|
+
const { bounds, authorities } = await discover([first]);
|
|
453
|
+
for (const group of grouped(rest, bounds.max_prefixes))
|
|
454
|
+
authorities.push(...(await discover(group)).authorities);
|
|
455
|
+
const unchanged = [];
|
|
432
456
|
for (const copy of copies) {
|
|
433
|
-
const authority =
|
|
457
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
434
458
|
if (authority !== undefined &&
|
|
435
459
|
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
436
|
-
|
|
460
|
+
await this.save(await this.received(copy));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
unchanged.push(copy);
|
|
437
464
|
}
|
|
438
465
|
}
|
|
439
|
-
const
|
|
440
|
-
|
|
466
|
+
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
467
|
+
await this.caughtUp(group, false);
|
|
441
468
|
}
|
|
442
469
|
async caughtUp(copies, whole) {
|
|
443
470
|
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
@@ -532,9 +559,6 @@ export class LocalCopies {
|
|
|
532
559
|
}
|
|
533
560
|
copy = await this.save(without(copy));
|
|
534
561
|
}
|
|
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
562
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
539
563
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
540
564
|
if (base === undefined) {
|
|
@@ -545,7 +569,7 @@ export class LocalCopies {
|
|
|
545
569
|
changes = await this.derived(copy, base, commit);
|
|
546
570
|
}
|
|
547
571
|
catch (error) {
|
|
548
|
-
return { refused: true, text:
|
|
572
|
+
return { refused: true, text: houseCause(error) };
|
|
549
573
|
}
|
|
550
574
|
if (changes.length === 0)
|
|
551
575
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -572,7 +596,18 @@ export class LocalCopies {
|
|
|
572
596
|
const after = await tree(copy.repository, commit);
|
|
573
597
|
const moved = movedPaths(copy.moves, before, after);
|
|
574
598
|
const arrived = new Set(moved.values());
|
|
575
|
-
const
|
|
599
|
+
const arriving = [...after].filter(([path, entry]) => before.get(path) !== entry);
|
|
600
|
+
if (arriving.length > 0) {
|
|
601
|
+
const { bounds } = await this.house.post('/kit/door/discover', { prefixes: [root] });
|
|
602
|
+
const binary = [];
|
|
603
|
+
for (const [path, entry] of arriving) {
|
|
604
|
+
if (!(await isText(copy.repository, entry, bounds.max_authored_file_bytes)))
|
|
605
|
+
binary.push(path);
|
|
606
|
+
}
|
|
607
|
+
if (binary.length > 0)
|
|
608
|
+
throw new Error(textOnly(binary));
|
|
609
|
+
}
|
|
610
|
+
const text = (entry) => blobText(copy.repository, entry);
|
|
576
611
|
const changes = [];
|
|
577
612
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
578
613
|
if (arrived.has(path))
|
|
@@ -585,7 +620,7 @@ export class LocalCopies {
|
|
|
585
620
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
586
621
|
const moving = after.get(to);
|
|
587
622
|
if (moving !== prior) {
|
|
588
|
-
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving
|
|
623
|
+
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
|
|
589
624
|
}
|
|
590
625
|
continue;
|
|
591
626
|
}
|
|
@@ -594,9 +629,9 @@ export class LocalCopies {
|
|
|
594
629
|
if (next === undefined)
|
|
595
630
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
596
631
|
else if (prior === undefined)
|
|
597
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
632
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
598
633
|
else
|
|
599
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
634
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
600
635
|
}
|
|
601
636
|
return changes;
|
|
602
637
|
}
|
|
@@ -625,8 +660,10 @@ export class LocalCopies {
|
|
|
625
660
|
}
|
|
626
661
|
if (outcome.refused.code === 'expired')
|
|
627
662
|
return this.recovered(copy);
|
|
628
|
-
const rebased = rebaseFixes(outcome.refused);
|
|
629
663
|
let settled = await this.save(without(copy));
|
|
664
|
+
if (settled.unfollowed !== undefined)
|
|
665
|
+
return { refused: true, text: outcome.refused.text };
|
|
666
|
+
const rebased = rebaseFixes(outcome.refused);
|
|
630
667
|
try {
|
|
631
668
|
settled = (await this.caughtUp([settled], true))[0];
|
|
632
669
|
}
|
|
@@ -663,7 +700,7 @@ export class LocalCopies {
|
|
|
663
700
|
written.push({
|
|
664
701
|
path: `${root}/${path}`,
|
|
665
702
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
666
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
703
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
667
704
|
});
|
|
668
705
|
}
|
|
669
706
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -707,7 +744,7 @@ export class LocalCopies {
|
|
|
707
744
|
{
|
|
708
745
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
709
746
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
710
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
747
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
711
748
|
},
|
|
712
749
|
];
|
|
713
750
|
}
|
|
@@ -752,7 +789,7 @@ export class LocalCopies {
|
|
|
752
789
|
async landed(repository, changes, house, selected) {
|
|
753
790
|
const text = async (path) => {
|
|
754
791
|
const entry = house.get(relative(path));
|
|
755
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
792
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
756
793
|
};
|
|
757
794
|
for (const change of changes) {
|
|
758
795
|
const current = await text(change.path);
|
package/dist/end.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
+
import { KIT_RELEASE } from './clis.js';
|
|
1
2
|
import { forgetRejected } from './home.js';
|
|
3
|
+
import { installCommand } from './placement.js';
|
|
2
4
|
import { killDescendants } from './scope.js';
|
|
3
|
-
export const REPLACED = 'another Kit started for this Environment';
|
|
5
|
+
export const REPLACED = 'another Kit process started for this Environment';
|
|
4
6
|
function exit(reason, status) {
|
|
5
7
|
process.stderr.write(`kit: ${reason}\n`);
|
|
6
8
|
killDescendants();
|
|
7
9
|
process.exit(status);
|
|
8
10
|
}
|
|
9
11
|
export function end(reason) {
|
|
10
|
-
exit(reason, 0);
|
|
12
|
+
exit(reason, process.env.HOUSE_KIT_RESIDENT === String(process.pid) ? 0 : 1);
|
|
11
13
|
}
|
|
12
14
|
export function restart(reason) {
|
|
13
15
|
exit(reason, 1);
|
|
14
16
|
}
|
|
17
|
+
export function updateRequired() {
|
|
18
|
+
const command = installCommand();
|
|
19
|
+
end(command === null ? 'This Kit is too old for House; install it again.' : `This Kit is too old for House. Update it: ${command}`);
|
|
20
|
+
}
|
|
21
|
+
export function newerThanHouse(release, command) {
|
|
22
|
+
end(`Kit ${KIT_RELEASE} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|
|
23
|
+
}
|
|
15
24
|
export async function signInAgain(rejected) {
|
|
16
25
|
await forgetRejected(rejected);
|
|
17
26
|
restart('sign in again');
|
package/dist/events.js
CHANGED
|
@@ -2,7 +2,7 @@ const COMMAND = { shown: 'command' };
|
|
|
2
2
|
const HIDDEN = { shown: 'hidden' };
|
|
3
3
|
const FINISHED = new Set(['completed', 'failed']);
|
|
4
4
|
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
|
-
const
|
|
5
|
+
const MESSAGE_TARGETS = { send_routine_request: 'access', cancel_routine_request: 'request', send_mail: 'request' };
|
|
6
6
|
function words(line) {
|
|
7
7
|
const found = [];
|
|
8
8
|
let word = null;
|
|
@@ -48,7 +48,7 @@ export function houseMessage(line) {
|
|
|
48
48
|
catch {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
const argument =
|
|
51
|
+
const argument = MESSAGE_TARGETS[call.command];
|
|
52
52
|
if (argument === undefined)
|
|
53
53
|
return null;
|
|
54
54
|
const target = call.arguments?.[argument];
|
|
@@ -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';
|
|
@@ -7,7 +8,7 @@ export function localCopies() {
|
|
|
7
8
|
return join(workspaceRoot(), 'local-copies');
|
|
8
9
|
}
|
|
9
10
|
export const HOUSE_REF = 'refs/house/received';
|
|
10
|
-
const COMMITTER = '
|
|
11
|
+
const COMMITTER = 'Kit <house-kit@localhost>';
|
|
11
12
|
export function run(cwd, args, input, env = {}) {
|
|
12
13
|
return new Promise((resolve, reject) => {
|
|
13
14
|
const child = spawn('git', args, { cwd, env: { ...withoutProxy(), ...env } });
|
|
@@ -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 });
|