@agentshouse/kit 0.2.1-cca9e1f43de44 → 0.2.1-ccb257fa8a318
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 +42 -40
- package/dist/acp.js +92 -115
- package/dist/agents.js +112 -24
- package/dist/api.js +10 -6
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +11 -25
- package/dist/building.js +46 -0
- package/dist/calls.js +71 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +131 -27
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +116 -0
- package/dist/conversations.js +523 -202
- package/dist/copies.js +185 -123
- package/dist/end.js +24 -5
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +16 -78
- package/dist/files.js +139 -31
- package/dist/git.js +13 -11
- package/dist/home.js +96 -7
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -177
- package/dist/instructions.js +8 -10
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +118 -107
- package/dist/logout.js +14 -16
- package/dist/mcp.js +6 -2
- package/dist/parts.js +34 -16
- package/dist/placement.js +39 -0
- package/dist/relay.js +33 -2
- package/dist/resident.js +118 -51
- package/dist/scope.js +206 -0
- package/dist/scripts.js +255 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/sign-in.js +23 -40
- package/dist/silence.js +28 -0
- package/dist/skills.js +49 -35
- package/dist/transcription.js +85 -45
- package/dist/translate.js +2 -8
- package/dist/update.js +115 -53
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/package.json +20 -20
- package/dist/bootstrap.json +0 -24
- package/dist/stream.js +0 -72
- package/how-we-work.md +0 -23
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
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,9 +32,35 @@ 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
|
}
|
|
45
|
+
function moved(before, after) {
|
|
46
|
+
if (before.house !== after.house)
|
|
47
|
+
return [`${rootOf(after.room_handle)}: ${HOUSE_REF} is at ${short(after.house)}`];
|
|
48
|
+
return before.position === after.position ? [] : [`${rootOf(after.room_handle)}: read-only files refreshed`];
|
|
49
|
+
}
|
|
50
|
+
function expiredAuthority(error) {
|
|
51
|
+
if (!(error instanceof HouseRefusal))
|
|
52
|
+
return undefined;
|
|
53
|
+
try {
|
|
54
|
+
const { error: refused } = JSON.parse(error.text);
|
|
55
|
+
return refused?.code === 'expired' && typeof refused.authority === 'string' ? refused.authority : undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function grouped(items, size) {
|
|
62
|
+
return items.flatMap((_, index) => (index % size === 0 ? [items.slice(index, index + size)] : []));
|
|
63
|
+
}
|
|
35
64
|
function covers(path, prefix) {
|
|
36
65
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
37
66
|
}
|
|
@@ -40,6 +69,9 @@ function sidecar(path) {
|
|
|
40
69
|
segments.splice(-1, 0, PROVENANCE);
|
|
41
70
|
return segments.join('/');
|
|
42
71
|
}
|
|
72
|
+
function recordOf(key) {
|
|
73
|
+
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
74
|
+
}
|
|
43
75
|
function rootOf(handle) {
|
|
44
76
|
return `/rooms/${handle}`;
|
|
45
77
|
}
|
|
@@ -101,7 +133,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
101
133
|
].map(pattern);
|
|
102
134
|
const granted = readOnly.filter((path) => !protectedPath(path)).map(pattern);
|
|
103
135
|
await ruled(join(repository, '.gitignore'), [
|
|
104
|
-
'#
|
|
136
|
+
'# Kit writes this file and .ignore. The paths below are read-only House',
|
|
105
137
|
'# content: Git does not track them, and House refuses a change to them even',
|
|
106
138
|
'# when this file is edited or a path is force-added.',
|
|
107
139
|
'/.gitignore',
|
|
@@ -116,7 +148,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
116
148
|
'',
|
|
117
149
|
].join('\n'));
|
|
118
150
|
await ruled(join(repository, '.ignore'), [
|
|
119
|
-
'#
|
|
151
|
+
'# Kit writes this file so native search still reads the read-only House',
|
|
120
152
|
'# content that .gitignore keeps out of Git.',
|
|
121
153
|
`!${PROVENANCE}`,
|
|
122
154
|
...[...house, ...granted].map((rule) => `!${rule}`),
|
|
@@ -152,8 +184,7 @@ export class LocalCopies {
|
|
|
152
184
|
house;
|
|
153
185
|
copies = new Map();
|
|
154
186
|
queues = new Map();
|
|
155
|
-
|
|
156
|
-
selecting = Promise.resolve();
|
|
187
|
+
answering = Promise.resolve();
|
|
157
188
|
constructor(house) {
|
|
158
189
|
this.house = house;
|
|
159
190
|
this.owner = {
|
|
@@ -185,42 +216,75 @@ export class LocalCopies {
|
|
|
185
216
|
}
|
|
186
217
|
}
|
|
187
218
|
async mapping() {
|
|
188
|
-
await this.
|
|
219
|
+
await this.answering;
|
|
189
220
|
return [...this.copies.values()]
|
|
190
|
-
.filter((copy) => copy.
|
|
221
|
+
.filter((copy) => copy.unfollowed === undefined)
|
|
191
222
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
192
223
|
.sort();
|
|
193
224
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.selecting = selected.catch(() => undefined);
|
|
197
|
-
return selected;
|
|
198
|
-
}
|
|
199
|
-
async reselect() {
|
|
200
|
-
const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
|
|
201
|
-
const selected = (chosen?.rooms ?? []).flatMap((room) => room.room_handle === null ? [] : [{ key: `rooms/${room.room_ref}`, handle: room.room_handle }]);
|
|
202
|
-
const keys = new Set(selected.map((scope) => scope.key));
|
|
203
|
-
const work = [];
|
|
204
|
-
for (const key of this.copies.keys()) {
|
|
205
|
-
if (keys.has(key))
|
|
206
|
-
continue;
|
|
207
|
-
work.push(this.exclusive(key, async () => {
|
|
208
|
-
const copy = this.copies.get(key);
|
|
209
|
-
if (copy.active)
|
|
210
|
-
await this.save({ ...copy, active: false });
|
|
211
|
-
}));
|
|
212
|
-
}
|
|
213
|
-
for (const scope of selected)
|
|
214
|
-
work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
|
|
215
|
-
await Promise.all(work);
|
|
225
|
+
connected() {
|
|
226
|
+
return this.selected();
|
|
216
227
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
async changed(rooms) {
|
|
229
|
+
if (rooms.length > 0)
|
|
230
|
+
await this.selected(rooms);
|
|
231
|
+
}
|
|
232
|
+
fetch() {
|
|
233
|
+
return this.answered(() => this.fetched(true));
|
|
234
|
+
}
|
|
235
|
+
async selected(rooms) {
|
|
236
|
+
const copied = await this.answered(() => this.fetched(false, rooms));
|
|
237
|
+
if (copied.refused)
|
|
238
|
+
logged(copied.text);
|
|
239
|
+
}
|
|
240
|
+
answered(work) {
|
|
241
|
+
const answered = this.answering.then(work);
|
|
242
|
+
this.answering = answered.catch(() => undefined);
|
|
243
|
+
return answered;
|
|
244
|
+
}
|
|
245
|
+
async fetched(refreshing, rooms) {
|
|
246
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
247
|
+
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
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 () => {
|
|
251
|
+
const said = [];
|
|
252
|
+
let refused = false;
|
|
253
|
+
const mark = refreshing ? 'said' : 'unsaid';
|
|
254
|
+
for (const copy of held()) {
|
|
255
|
+
if (synced.has(copy.key) || copy.unfollowed === 'said' || copy.unfollowed === mark)
|
|
256
|
+
continue;
|
|
257
|
+
await this.save({ ...copy, unfollowed: mark });
|
|
258
|
+
if (refreshing)
|
|
259
|
+
said.push(`${rootOf(copy.room_handle)}: no longer followed; kept as it is`);
|
|
260
|
+
}
|
|
261
|
+
const current = [];
|
|
262
|
+
for (const [key, handle] of synced) {
|
|
263
|
+
try {
|
|
264
|
+
const held = this.copies.get(key);
|
|
265
|
+
if (held === undefined || !(await exists(held.repository))) {
|
|
266
|
+
said.push(await this.materialize(key, handle));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const { unfollowed, ...followed } = held;
|
|
270
|
+
current.push(unfollowed === undefined && held.room_handle === handle ? held : await this.save({ ...followed, room_handle: handle }));
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
refused = true;
|
|
274
|
+
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (refreshing && current.length > 0) {
|
|
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) {
|
|
282
|
+
refused = true;
|
|
283
|
+
said.push(failed);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
287
|
+
});
|
|
224
288
|
}
|
|
225
289
|
async observed(report) {
|
|
226
290
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -250,11 +314,11 @@ export class LocalCopies {
|
|
|
250
314
|
async push(cwd, selector, caller) {
|
|
251
315
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
252
316
|
if (located.status !== 0)
|
|
253
|
-
return
|
|
317
|
+
return outside(cwd);
|
|
254
318
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
255
319
|
const key = await this.keyAt(dirname(common));
|
|
256
320
|
if (key === undefined)
|
|
257
|
-
return
|
|
321
|
+
return outside(worktree);
|
|
258
322
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
259
323
|
}
|
|
260
324
|
async keyAt(repository) {
|
|
@@ -270,19 +334,12 @@ export class LocalCopies {
|
|
|
270
334
|
this.queues.set(key, queued.catch(() => undefined));
|
|
271
335
|
return queued;
|
|
272
336
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
this.delivering.add(key);
|
|
277
|
-
void this.exclusive(key, async () => {
|
|
278
|
-
this.delivering.delete(key);
|
|
279
|
-
const copy = this.copies.get(key);
|
|
280
|
-
if (copy.active)
|
|
281
|
-
await this.save(await this.caughtUp(copy));
|
|
282
|
-
}).catch(logged);
|
|
337
|
+
holding(keys, work) {
|
|
338
|
+
const [key, ...rest] = keys;
|
|
339
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
283
340
|
}
|
|
284
341
|
async save(copy) {
|
|
285
|
-
const path =
|
|
342
|
+
const path = recordOf(copy.key);
|
|
286
343
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
287
344
|
const temporary = `${path}.${process.pid}`;
|
|
288
345
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -325,46 +382,13 @@ export class LocalCopies {
|
|
|
325
382
|
}
|
|
326
383
|
return read;
|
|
327
384
|
}
|
|
328
|
-
async
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (copy.room_handle !== scope.handle)
|
|
335
|
-
copy = await this.save({ ...copy, room_handle: scope.handle });
|
|
336
|
-
if (!copy.active) {
|
|
337
|
-
await this.save(await this.received({ ...copy, active: true }));
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const discovery = await this.house.post('/kit/door/discover', {
|
|
341
|
-
prefixes: [rootOf(copy.room_handle)],
|
|
342
|
-
});
|
|
343
|
-
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
344
|
-
if (authority === undefined)
|
|
345
|
-
return;
|
|
346
|
-
if (authority.managed_replication !== 'admitted') {
|
|
347
|
-
logged(`operation_denied: ${rootOf(copy.room_handle)} is not synchronized to this Environment`);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
|
|
351
|
-
JSON.stringify([copy.write, copy.protected])) {
|
|
352
|
-
await this.save(await this.received(copy));
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
await this.save(await this.caughtUp(copy));
|
|
356
|
-
}
|
|
357
|
-
async materialize(scope) {
|
|
358
|
-
const read = await this.read(scope.handle);
|
|
359
|
-
if (read === null) {
|
|
360
|
-
logged(`House serves nothing of ${rootOf(scope.handle)} to copy`);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
385
|
+
async materialize(key, handle) {
|
|
386
|
+
const read = await this.read(handle);
|
|
387
|
+
if (read === null)
|
|
388
|
+
throw new Error('House serves nothing of it to copy');
|
|
363
389
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
364
|
-
if (await exists(repository))
|
|
365
|
-
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
390
|
+
if (await exists(repository))
|
|
391
|
+
throw new Error(`${repository} already exists and Kit leaves it as it is`);
|
|
368
392
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
369
393
|
await mkdir(staging, { recursive: true });
|
|
370
394
|
let house;
|
|
@@ -372,7 +396,7 @@ export class LocalCopies {
|
|
|
372
396
|
try {
|
|
373
397
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
374
398
|
house = await importCommits(staging, [
|
|
375
|
-
{ message: `House ${rootOf(
|
|
399
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
376
400
|
]);
|
|
377
401
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
378
402
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -385,11 +409,10 @@ export class LocalCopies {
|
|
|
385
409
|
throw error;
|
|
386
410
|
}
|
|
387
411
|
await this.save({
|
|
388
|
-
key
|
|
412
|
+
key,
|
|
389
413
|
room_ref: read.anchor,
|
|
390
|
-
room_handle:
|
|
414
|
+
room_handle: handle,
|
|
391
415
|
repository,
|
|
392
|
-
active: true,
|
|
393
416
|
house,
|
|
394
417
|
position: read.position,
|
|
395
418
|
log_epoch: read.log_epoch,
|
|
@@ -399,6 +422,7 @@ export class LocalCopies {
|
|
|
399
422
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
400
423
|
moves: [],
|
|
401
424
|
});
|
|
425
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
402
426
|
}
|
|
403
427
|
async received(copy) {
|
|
404
428
|
const read = await this.read(copy.room_handle);
|
|
@@ -422,29 +446,57 @@ export class LocalCopies {
|
|
|
422
446
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
423
447
|
return next;
|
|
424
448
|
}
|
|
425
|
-
async
|
|
426
|
-
|
|
427
|
-
|
|
449
|
+
async refreshed(copies) {
|
|
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 = [];
|
|
456
|
+
for (const copy of copies) {
|
|
457
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
458
|
+
if (authority !== undefined &&
|
|
459
|
+
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
460
|
+
await this.save(await this.received(copy));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
unchanged.push(copy);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
467
|
+
await this.caughtUp(group, false);
|
|
468
|
+
}
|
|
469
|
+
async caughtUp(copies, whole) {
|
|
470
|
+
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
471
|
+
let asking = [...copies];
|
|
472
|
+
while (asking.length > 0) {
|
|
428
473
|
let answer;
|
|
429
474
|
try {
|
|
430
475
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
431
|
-
prefixes:
|
|
432
|
-
positions:
|
|
476
|
+
prefixes: asking.map((copy) => rootOf(copy.room_handle)),
|
|
477
|
+
positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
|
|
433
478
|
});
|
|
434
479
|
}
|
|
435
480
|
catch (error) {
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
438
|
-
return this.received(copy);
|
|
439
|
-
if (code !== 'replica_behind')
|
|
481
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
482
|
+
if (expired === undefined)
|
|
440
483
|
throw error;
|
|
441
|
-
|
|
484
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
485
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
486
|
+
continue;
|
|
442
487
|
}
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
488
|
+
const advanced = [];
|
|
489
|
+
for (const copy of asking) {
|
|
490
|
+
const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
|
|
491
|
+
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
492
|
+
continue;
|
|
493
|
+
const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
494
|
+
caught.set(copy.key, next);
|
|
495
|
+
advanced.push(next);
|
|
496
|
+
}
|
|
497
|
+
asking = whole ? advanced : [];
|
|
447
498
|
}
|
|
499
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
448
500
|
}
|
|
449
501
|
async incorporated(copy, changes, position, logEpoch) {
|
|
450
502
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -507,9 +559,6 @@ export class LocalCopies {
|
|
|
507
559
|
}
|
|
508
560
|
copy = await this.save(without(copy));
|
|
509
561
|
}
|
|
510
|
-
if (!copy.active) {
|
|
511
|
-
return { refused: true, text: `${rootOf(copy.room_handle)} is no longer synced to this Environment, so House takes no push from it` };
|
|
512
|
-
}
|
|
513
562
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
514
563
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
515
564
|
if (base === undefined) {
|
|
@@ -520,7 +569,7 @@ export class LocalCopies {
|
|
|
520
569
|
changes = await this.derived(copy, base, commit);
|
|
521
570
|
}
|
|
522
571
|
catch (error) {
|
|
523
|
-
return { refused: true, text:
|
|
572
|
+
return { refused: true, text: houseCause(error) };
|
|
524
573
|
}
|
|
525
574
|
if (changes.length === 0)
|
|
526
575
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -547,7 +596,18 @@ export class LocalCopies {
|
|
|
547
596
|
const after = await tree(copy.repository, commit);
|
|
548
597
|
const moved = movedPaths(copy.moves, before, after);
|
|
549
598
|
const arrived = new Set(moved.values());
|
|
550
|
-
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);
|
|
551
611
|
const changes = [];
|
|
552
612
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
553
613
|
if (arrived.has(path))
|
|
@@ -560,7 +620,7 @@ export class LocalCopies {
|
|
|
560
620
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
561
621
|
const moving = after.get(to);
|
|
562
622
|
if (moving !== prior) {
|
|
563
|
-
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) });
|
|
564
624
|
}
|
|
565
625
|
continue;
|
|
566
626
|
}
|
|
@@ -569,9 +629,9 @@ export class LocalCopies {
|
|
|
569
629
|
if (next === undefined)
|
|
570
630
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
571
631
|
else if (prior === undefined)
|
|
572
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
632
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
573
633
|
else
|
|
574
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
634
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
575
635
|
}
|
|
576
636
|
return changes;
|
|
577
637
|
}
|
|
@@ -598,12 +658,14 @@ export class LocalCopies {
|
|
|
598
658
|
const { upload, ...unstaged } = copy.push;
|
|
599
659
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
600
660
|
}
|
|
601
|
-
if (outcome.refused.code === '
|
|
661
|
+
if (outcome.refused.code === 'expired')
|
|
602
662
|
return this.recovered(copy);
|
|
603
|
-
const rebased = rebaseFixes(outcome.refused);
|
|
604
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);
|
|
605
667
|
try {
|
|
606
|
-
settled =
|
|
668
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
607
669
|
}
|
|
608
670
|
catch {
|
|
609
671
|
return {
|
|
@@ -622,7 +684,7 @@ export class LocalCopies {
|
|
|
622
684
|
}
|
|
623
685
|
async recovered(copy) {
|
|
624
686
|
const push = copy.push;
|
|
625
|
-
const current =
|
|
687
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
626
688
|
const house = await tree(copy.repository, current.house);
|
|
627
689
|
const root = rootOf(current.room_handle);
|
|
628
690
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
@@ -638,7 +700,7 @@ export class LocalCopies {
|
|
|
638
700
|
written.push({
|
|
639
701
|
path: `${root}/${path}`,
|
|
640
702
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
641
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
703
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
642
704
|
});
|
|
643
705
|
}
|
|
644
706
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -666,7 +728,7 @@ export class LocalCopies {
|
|
|
666
728
|
const push = copy.push;
|
|
667
729
|
let written = push.written;
|
|
668
730
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
669
|
-
copy =
|
|
731
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
670
732
|
const house = await tree(copy.repository, copy.house);
|
|
671
733
|
const sent = await tree(copy.repository, push.house);
|
|
672
734
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -682,7 +744,7 @@ export class LocalCopies {
|
|
|
682
744
|
{
|
|
683
745
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
684
746
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
685
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
747
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
686
748
|
},
|
|
687
749
|
];
|
|
688
750
|
}
|
|
@@ -727,7 +789,7 @@ export class LocalCopies {
|
|
|
727
789
|
async landed(repository, changes, house, selected) {
|
|
728
790
|
const text = async (path) => {
|
|
729
791
|
const entry = house.get(relative(path));
|
|
730
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
792
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
731
793
|
};
|
|
732
794
|
for (const change of changes) {
|
|
733
795
|
const current = await text(change.path);
|
package/dist/end.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { KIT_RELEASE } from './clis.js';
|
|
2
|
+
import { forgetRejected } from './home.js';
|
|
3
|
+
import { installCommand } from './placement.js';
|
|
4
|
+
import { killDescendants } from './scope.js';
|
|
5
|
+
export const REPLACED = 'another Kit process started for this Environment';
|
|
6
|
+
function exit(reason, status) {
|
|
5
7
|
process.stderr.write(`kit: ${reason}\n`);
|
|
6
8
|
killDescendants();
|
|
7
|
-
process.exit(
|
|
9
|
+
process.exit(status);
|
|
10
|
+
}
|
|
11
|
+
export function end(reason) {
|
|
12
|
+
exit(reason, process.env.HOUSE_KIT_RESIDENT === String(process.pid) ? 0 : 1);
|
|
13
|
+
}
|
|
14
|
+
export function restart(reason) {
|
|
15
|
+
exit(reason, 1);
|
|
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
|
+
}
|
|
24
|
+
export async function signInAgain(rejected) {
|
|
25
|
+
await forgetRejected(rejected);
|
|
26
|
+
restart('sign in again');
|
|
8
27
|
}
|
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
|
+
});
|