@agentshouse/kit 0.2.1-cca9e1f43de44 → 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 +30 -28
- package/dist/acp.js +41 -111
- package/dist/agents.js +110 -22
- package/dist/api.js +9 -5
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +10 -24
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +95 -14
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +181 -114
- package/dist/copies.js +188 -119
- package/dist/end.js +19 -4
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +14 -76
- package/dist/files.js +8 -2
- package/dist/git.js +10 -8
- package/dist/home.js +96 -7
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -177
- package/dist/inputs.js +37 -0
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +117 -106
- package/dist/logout.js +14 -16
- package/dist/parts.js +34 -16
- package/dist/placement.js +30 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +89 -33
- package/dist/scope.js +203 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/sign-in.js +6 -24
- package/dist/skills.js +81 -12
- package/dist/stream.js +68 -17
- package/dist/transcription.js +3 -3
- package/dist/update.js +48 -29
- package/dist/wire.js +6 -0
- package/dist/work.js +346 -0
- 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,9 +32,32 @@ 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
|
+
}
|
|
35
61
|
function covers(path, prefix) {
|
|
36
62
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
37
63
|
}
|
|
@@ -40,6 +66,9 @@ function sidecar(path) {
|
|
|
40
66
|
segments.splice(-1, 0, PROVENANCE);
|
|
41
67
|
return segments.join('/');
|
|
42
68
|
}
|
|
69
|
+
function recordOf(key) {
|
|
70
|
+
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
71
|
+
}
|
|
43
72
|
function rootOf(handle) {
|
|
44
73
|
return `/rooms/${handle}`;
|
|
45
74
|
}
|
|
@@ -152,8 +181,7 @@ export class LocalCopies {
|
|
|
152
181
|
house;
|
|
153
182
|
copies = new Map();
|
|
154
183
|
queues = new Map();
|
|
155
|
-
|
|
156
|
-
selecting = Promise.resolve();
|
|
184
|
+
answering = Promise.resolve();
|
|
157
185
|
constructor(house) {
|
|
158
186
|
this.house = house;
|
|
159
187
|
this.owner = {
|
|
@@ -185,42 +213,82 @@ export class LocalCopies {
|
|
|
185
213
|
}
|
|
186
214
|
}
|
|
187
215
|
async mapping() {
|
|
188
|
-
await this.
|
|
216
|
+
await this.answering;
|
|
189
217
|
return [...this.copies.values()]
|
|
190
|
-
.filter((copy) => copy.
|
|
218
|
+
.filter((copy) => copy.erasing === undefined)
|
|
191
219
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
192
220
|
.sort();
|
|
193
221
|
}
|
|
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);
|
|
222
|
+
connected() {
|
|
223
|
+
return this.selected();
|
|
216
224
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
225
|
+
async changed(rooms) {
|
|
226
|
+
if (rooms.length > 0)
|
|
227
|
+
await this.selected(rooms);
|
|
228
|
+
}
|
|
229
|
+
fetch() {
|
|
230
|
+
return this.answered(() => this.fetched(true));
|
|
231
|
+
}
|
|
232
|
+
async selected(rooms) {
|
|
233
|
+
const copied = await this.answered(() => this.fetched(false, rooms));
|
|
234
|
+
if (copied.refused)
|
|
235
|
+
logged(copied.text);
|
|
236
|
+
}
|
|
237
|
+
answered(work) {
|
|
238
|
+
const answered = this.answering.then(work);
|
|
239
|
+
this.answering = answered.catch(() => undefined);
|
|
240
|
+
return answered;
|
|
241
|
+
}
|
|
242
|
+
async fetched(refreshing, rooms) {
|
|
243
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
244
|
+
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
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 () => {
|
|
248
|
+
const said = [];
|
|
249
|
+
let refused = false;
|
|
250
|
+
for (const copy of held()) {
|
|
251
|
+
const returned = synced.has(copy.key);
|
|
252
|
+
if (returned && copy.erasing === undefined)
|
|
253
|
+
continue;
|
|
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
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const current = [];
|
|
264
|
+
for (const [key, handle] of synced) {
|
|
265
|
+
try {
|
|
266
|
+
const held = this.copies.get(key);
|
|
267
|
+
if (held?.erasing)
|
|
268
|
+
continue;
|
|
269
|
+
if (held === undefined || !(await exists(held.repository))) {
|
|
270
|
+
said.push(await this.materialize(key, handle));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
current.push(held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle }));
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
refused = true;
|
|
277
|
+
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (refreshing && current.length > 0) {
|
|
281
|
+
try {
|
|
282
|
+
const refreshed = await this.refreshed(current);
|
|
283
|
+
current.forEach((copy, index) => said.push(...moved(copy, refreshed[index])));
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
refused = true;
|
|
287
|
+
said.push(houseCause(error));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
291
|
+
});
|
|
224
292
|
}
|
|
225
293
|
async observed(report) {
|
|
226
294
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -228,6 +296,8 @@ export class LocalCopies {
|
|
|
228
296
|
return;
|
|
229
297
|
await this.exclusive(key, async () => {
|
|
230
298
|
const copy = this.copies.get(key);
|
|
299
|
+
if (copy === undefined || copy.erasing)
|
|
300
|
+
return;
|
|
231
301
|
let moves = copy.moves;
|
|
232
302
|
for (const { from, to } of report.moves) {
|
|
233
303
|
const held = moves.find((move) => move.worktree === report.worktree && move.to === from);
|
|
@@ -250,11 +320,11 @@ export class LocalCopies {
|
|
|
250
320
|
async push(cwd, selector, caller) {
|
|
251
321
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
252
322
|
if (located.status !== 0)
|
|
253
|
-
return
|
|
323
|
+
return outside(cwd);
|
|
254
324
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
255
325
|
const key = await this.keyAt(dirname(common));
|
|
256
326
|
if (key === undefined)
|
|
257
|
-
return
|
|
327
|
+
return outside(worktree);
|
|
258
328
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
259
329
|
}
|
|
260
330
|
async keyAt(repository) {
|
|
@@ -270,19 +340,12 @@ export class LocalCopies {
|
|
|
270
340
|
this.queues.set(key, queued.catch(() => undefined));
|
|
271
341
|
return queued;
|
|
272
342
|
}
|
|
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);
|
|
343
|
+
holding(keys, work) {
|
|
344
|
+
const [key, ...rest] = keys;
|
|
345
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
283
346
|
}
|
|
284
347
|
async save(copy) {
|
|
285
|
-
const path =
|
|
348
|
+
const path = recordOf(copy.key);
|
|
286
349
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
287
350
|
const temporary = `${path}.${process.pid}`;
|
|
288
351
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -290,6 +353,12 @@ export class LocalCopies {
|
|
|
290
353
|
this.copies.set(copy.key, copy);
|
|
291
354
|
return copy;
|
|
292
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
|
+
}
|
|
293
362
|
async read(handle) {
|
|
294
363
|
const prefixes = [rootOf(handle)];
|
|
295
364
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
@@ -325,46 +394,13 @@ export class LocalCopies {
|
|
|
325
394
|
}
|
|
326
395
|
return read;
|
|
327
396
|
}
|
|
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
|
-
}
|
|
397
|
+
async materialize(key, handle) {
|
|
398
|
+
const read = await this.read(handle);
|
|
399
|
+
if (read === null)
|
|
400
|
+
throw new Error('House serves nothing of it to copy');
|
|
363
401
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
364
|
-
if (await exists(repository))
|
|
365
|
-
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
402
|
+
if (await exists(repository))
|
|
403
|
+
throw new Error(`${repository} already exists and House Kit leaves it as it is`);
|
|
368
404
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
369
405
|
await mkdir(staging, { recursive: true });
|
|
370
406
|
let house;
|
|
@@ -372,7 +408,7 @@ export class LocalCopies {
|
|
|
372
408
|
try {
|
|
373
409
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
374
410
|
house = await importCommits(staging, [
|
|
375
|
-
{ message: `House ${rootOf(
|
|
411
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
376
412
|
]);
|
|
377
413
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
378
414
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -385,11 +421,10 @@ export class LocalCopies {
|
|
|
385
421
|
throw error;
|
|
386
422
|
}
|
|
387
423
|
await this.save({
|
|
388
|
-
key
|
|
424
|
+
key,
|
|
389
425
|
room_ref: read.anchor,
|
|
390
|
-
room_handle:
|
|
426
|
+
room_handle: handle,
|
|
391
427
|
repository,
|
|
392
|
-
active: true,
|
|
393
428
|
house,
|
|
394
429
|
position: read.position,
|
|
395
430
|
log_epoch: read.log_epoch,
|
|
@@ -399,6 +434,7 @@ export class LocalCopies {
|
|
|
399
434
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
400
435
|
moves: [],
|
|
401
436
|
});
|
|
437
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
402
438
|
}
|
|
403
439
|
async received(copy) {
|
|
404
440
|
const read = await this.read(copy.room_handle);
|
|
@@ -422,29 +458,52 @@ export class LocalCopies {
|
|
|
422
458
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
423
459
|
return next;
|
|
424
460
|
}
|
|
425
|
-
async
|
|
426
|
-
|
|
427
|
-
|
|
461
|
+
async refreshed(copies) {
|
|
462
|
+
const discovery = await this.house.post('/kit/door/discover', {
|
|
463
|
+
prefixes: copies.map((copy) => rootOf(copy.room_handle)),
|
|
464
|
+
});
|
|
465
|
+
const regranted = new Map();
|
|
466
|
+
for (const copy of copies) {
|
|
467
|
+
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
468
|
+
if (authority !== undefined &&
|
|
469
|
+
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
470
|
+
regranted.set(copy.key, await this.save(await this.received(copy)));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const caught = await this.caughtUp(copies.filter((copy) => !regranted.has(copy.key)), false);
|
|
474
|
+
return copies.map((copy) => regranted.get(copy.key) ?? caught.find((held) => held.key === copy.key));
|
|
475
|
+
}
|
|
476
|
+
async caughtUp(copies, whole) {
|
|
477
|
+
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
478
|
+
let asking = [...copies];
|
|
479
|
+
while (asking.length > 0) {
|
|
428
480
|
let answer;
|
|
429
481
|
try {
|
|
430
482
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
431
|
-
prefixes:
|
|
432
|
-
positions:
|
|
483
|
+
prefixes: asking.map((copy) => rootOf(copy.room_handle)),
|
|
484
|
+
positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
|
|
433
485
|
});
|
|
434
486
|
}
|
|
435
487
|
catch (error) {
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
438
|
-
return this.received(copy);
|
|
439
|
-
if (code !== 'replica_behind')
|
|
488
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
489
|
+
if (expired === undefined)
|
|
440
490
|
throw error;
|
|
441
|
-
|
|
491
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
492
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const advanced = [];
|
|
496
|
+
for (const copy of asking) {
|
|
497
|
+
const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
|
|
498
|
+
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
499
|
+
continue;
|
|
500
|
+
const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
501
|
+
caught.set(copy.key, next);
|
|
502
|
+
advanced.push(next);
|
|
442
503
|
}
|
|
443
|
-
|
|
444
|
-
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
445
|
-
return copy;
|
|
446
|
-
copy = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
504
|
+
asking = whole ? advanced : [];
|
|
447
505
|
}
|
|
506
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
448
507
|
}
|
|
449
508
|
async incorporated(copy, changes, position, logEpoch) {
|
|
450
509
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -494,6 +553,8 @@ export class LocalCopies {
|
|
|
494
553
|
}
|
|
495
554
|
async pushed(key, worktree, selector, caller) {
|
|
496
555
|
let copy = this.copies.get(key);
|
|
556
|
+
if (copy === undefined || copy.erasing)
|
|
557
|
+
return outside(worktree);
|
|
497
558
|
if (copy.push?.state === 'submitted')
|
|
498
559
|
return this.settle(copy, caller);
|
|
499
560
|
const selected = await run(worktree, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${selector}^{commit}`]);
|
|
@@ -507,9 +568,6 @@ export class LocalCopies {
|
|
|
507
568
|
}
|
|
508
569
|
copy = await this.save(without(copy));
|
|
509
570
|
}
|
|
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
571
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
514
572
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
515
573
|
if (base === undefined) {
|
|
@@ -520,7 +578,7 @@ export class LocalCopies {
|
|
|
520
578
|
changes = await this.derived(copy, base, commit);
|
|
521
579
|
}
|
|
522
580
|
catch (error) {
|
|
523
|
-
return { refused: true, text:
|
|
581
|
+
return { refused: true, text: houseCause(error) };
|
|
524
582
|
}
|
|
525
583
|
if (changes.length === 0)
|
|
526
584
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -547,7 +605,18 @@ export class LocalCopies {
|
|
|
547
605
|
const after = await tree(copy.repository, commit);
|
|
548
606
|
const moved = movedPaths(copy.moves, before, after);
|
|
549
607
|
const arrived = new Set(moved.values());
|
|
550
|
-
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);
|
|
551
620
|
const changes = [];
|
|
552
621
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
553
622
|
if (arrived.has(path))
|
|
@@ -560,7 +629,7 @@ export class LocalCopies {
|
|
|
560
629
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
561
630
|
const moving = after.get(to);
|
|
562
631
|
if (moving !== prior) {
|
|
563
|
-
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) });
|
|
564
633
|
}
|
|
565
634
|
continue;
|
|
566
635
|
}
|
|
@@ -569,9 +638,9 @@ export class LocalCopies {
|
|
|
569
638
|
if (next === undefined)
|
|
570
639
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
571
640
|
else if (prior === undefined)
|
|
572
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
641
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
573
642
|
else
|
|
574
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
643
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
575
644
|
}
|
|
576
645
|
return changes;
|
|
577
646
|
}
|
|
@@ -598,12 +667,12 @@ export class LocalCopies {
|
|
|
598
667
|
const { upload, ...unstaged } = copy.push;
|
|
599
668
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
600
669
|
}
|
|
601
|
-
if (outcome.refused.code === '
|
|
670
|
+
if (outcome.refused.code === 'expired')
|
|
602
671
|
return this.recovered(copy);
|
|
603
672
|
const rebased = rebaseFixes(outcome.refused);
|
|
604
673
|
let settled = await this.save(without(copy));
|
|
605
674
|
try {
|
|
606
|
-
settled =
|
|
675
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
607
676
|
}
|
|
608
677
|
catch {
|
|
609
678
|
return {
|
|
@@ -622,7 +691,7 @@ export class LocalCopies {
|
|
|
622
691
|
}
|
|
623
692
|
async recovered(copy) {
|
|
624
693
|
const push = copy.push;
|
|
625
|
-
const current =
|
|
694
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
626
695
|
const house = await tree(copy.repository, current.house);
|
|
627
696
|
const root = rootOf(current.room_handle);
|
|
628
697
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
@@ -638,7 +707,7 @@ export class LocalCopies {
|
|
|
638
707
|
written.push({
|
|
639
708
|
path: `${root}/${path}`,
|
|
640
709
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
641
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
710
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
642
711
|
});
|
|
643
712
|
}
|
|
644
713
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -666,7 +735,7 @@ export class LocalCopies {
|
|
|
666
735
|
const push = copy.push;
|
|
667
736
|
let written = push.written;
|
|
668
737
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
669
|
-
copy =
|
|
738
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
670
739
|
const house = await tree(copy.repository, copy.house);
|
|
671
740
|
const sent = await tree(copy.repository, push.house);
|
|
672
741
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -682,7 +751,7 @@ export class LocalCopies {
|
|
|
682
751
|
{
|
|
683
752
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
684
753
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
685
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
754
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
686
755
|
},
|
|
687
756
|
];
|
|
688
757
|
}
|
|
@@ -727,7 +796,7 @@ export class LocalCopies {
|
|
|
727
796
|
async landed(repository, changes, house, selected) {
|
|
728
797
|
const text = async (path) => {
|
|
729
798
|
const entry = house.get(relative(path));
|
|
730
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
799
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
731
800
|
};
|
|
732
801
|
for (const change of changes) {
|
|
733
802
|
const current = await text(change.path);
|
package/dist/end.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { forgetRejected } from './home.js';
|
|
2
|
+
import { installCommand } from './placement.js';
|
|
3
|
+
import { killDescendants } from './scope.js';
|
|
2
4
|
export const REPLACED = 'another Kit started for this Environment';
|
|
3
|
-
|
|
4
|
-
export function end(reason) {
|
|
5
|
+
function exit(reason, status) {
|
|
5
6
|
process.stderr.write(`kit: ${reason}\n`);
|
|
6
7
|
killDescendants();
|
|
7
|
-
process.exit(
|
|
8
|
+
process.exit(status);
|
|
9
|
+
}
|
|
10
|
+
export function end(reason) {
|
|
11
|
+
exit(reason, 0);
|
|
12
|
+
}
|
|
13
|
+
export function restart(reason) {
|
|
14
|
+
exit(reason, 1);
|
|
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
|
+
}
|
|
20
|
+
export async function signInAgain(rejected) {
|
|
21
|
+
await forgetRejected(rejected);
|
|
22
|
+
restart('sign in again');
|
|
8
23
|
}
|
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
|
+
});
|