@agentshouse/kit 0.2.1-ce145e9aadbf6 → 0.2.1-cf6326dfa5c3e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -31
- package/dist/acp.js +46 -103
- package/dist/agents.js +151 -41
- package/dist/api.js +34 -2
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +36 -57
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +126 -69
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +457 -277
- package/dist/copies.js +198 -124
- package/dist/end.js +23 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +22 -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 -159
- package/dist/inputs.js +63 -0
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +118 -103
- package/dist/logout.js +14 -13
- package/dist/parts.js +82 -0
- package/dist/placement.js +30 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +91 -35
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +3 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -28
- package/dist/skills.js +81 -12
- package/dist/stream.js +87 -11
- package/dist/transcription.js +57 -9
- package/dist/translate.js +2 -8
- package/dist/update.js +50 -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/dist/blocks.js +0 -51
- 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
|
@@ -2,8 +2,8 @@ import { parse } from '@agentshouse/mdmodel';
|
|
|
2
2
|
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
|
-
import { HouseRefusal } from './api.js';
|
|
6
|
-
import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
|
|
5
|
+
import { finalRefusal, HouseRefusal, retrying } from './api.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,11 +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 REPLICA_RETRY_MS = 30_000;
|
|
15
14
|
const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
|
|
16
15
|
function causeOf(error) {
|
|
17
16
|
return error instanceof Error ? error.message : String(error);
|
|
18
17
|
}
|
|
18
|
+
function houseCause(error) {
|
|
19
|
+
return error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
20
|
+
}
|
|
19
21
|
function logged(error) {
|
|
20
22
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
21
23
|
}
|
|
@@ -30,9 +32,35 @@ const REBASED = new Set([
|
|
|
30
32
|
function rebaseFixes(refusal) {
|
|
31
33
|
return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
|
|
32
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
|
+
}
|
|
33
42
|
function short(commit) {
|
|
34
43
|
return commit.slice(0, 12);
|
|
35
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
|
+
}
|
|
36
64
|
function covers(path, prefix) {
|
|
37
65
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
38
66
|
}
|
|
@@ -41,6 +69,9 @@ function sidecar(path) {
|
|
|
41
69
|
segments.splice(-1, 0, PROVENANCE);
|
|
42
70
|
return segments.join('/');
|
|
43
71
|
}
|
|
72
|
+
function recordOf(key) {
|
|
73
|
+
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
74
|
+
}
|
|
44
75
|
function rootOf(handle) {
|
|
45
76
|
return `/rooms/${handle}`;
|
|
46
77
|
}
|
|
@@ -153,19 +184,18 @@ export class LocalCopies {
|
|
|
153
184
|
house;
|
|
154
185
|
copies = new Map();
|
|
155
186
|
queues = new Map();
|
|
156
|
-
|
|
157
|
-
selecting = Promise.resolve();
|
|
187
|
+
answering = Promise.resolve();
|
|
158
188
|
constructor(house) {
|
|
159
189
|
this.house = house;
|
|
160
190
|
this.owner = {
|
|
161
191
|
key: 'owner',
|
|
162
192
|
submit: async (operation, changes) => {
|
|
163
193
|
try {
|
|
164
|
-
const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
|
|
194
|
+
const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
|
|
165
195
|
return { accepted: outcome.contents };
|
|
166
196
|
}
|
|
167
197
|
catch (error) {
|
|
168
|
-
if (!(error
|
|
198
|
+
if (!finalRefusal(error))
|
|
169
199
|
throw error;
|
|
170
200
|
return {
|
|
171
201
|
refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
|
|
@@ -186,42 +216,81 @@ export class LocalCopies {
|
|
|
186
216
|
}
|
|
187
217
|
}
|
|
188
218
|
async mapping() {
|
|
189
|
-
await this.
|
|
219
|
+
await this.answering;
|
|
190
220
|
return [...this.copies.values()]
|
|
191
|
-
.filter((copy) => copy.
|
|
221
|
+
.filter((copy) => copy.erasing === undefined)
|
|
192
222
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
193
223
|
.sort();
|
|
194
224
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.selecting = selected.catch(() => undefined);
|
|
198
|
-
return selected;
|
|
199
|
-
}
|
|
200
|
-
async reselect() {
|
|
201
|
-
const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
|
|
202
|
-
const selected = (chosen?.rooms ?? []).flatMap((room) => room.room_handle === null ? [] : [{ key: `rooms/${room.room_ref}`, handle: room.room_handle }]);
|
|
203
|
-
const keys = new Set(selected.map((scope) => scope.key));
|
|
204
|
-
const work = [];
|
|
205
|
-
for (const key of this.copies.keys()) {
|
|
206
|
-
if (keys.has(key))
|
|
207
|
-
continue;
|
|
208
|
-
work.push(this.exclusive(key, async () => {
|
|
209
|
-
const copy = this.copies.get(key);
|
|
210
|
-
if (copy.active)
|
|
211
|
-
await this.save({ ...copy, active: false });
|
|
212
|
-
}));
|
|
213
|
-
}
|
|
214
|
-
for (const scope of selected)
|
|
215
|
-
work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
|
|
216
|
-
await Promise.all(work);
|
|
225
|
+
connected() {
|
|
226
|
+
return this.selected();
|
|
217
227
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
for (const copy of held()) {
|
|
254
|
+
const returned = synced.has(copy.key);
|
|
255
|
+
if (returned && copy.erasing === undefined)
|
|
256
|
+
continue;
|
|
257
|
+
try {
|
|
258
|
+
await this.erase(copy);
|
|
259
|
+
said.push(`${rootOf(copy.room_handle)}: ${returned ? '' : 'no longer synced to this Environment; '}erased from the Local copy`);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
refused = true;
|
|
263
|
+
said.push(`${rootOf(copy.room_handle)}: ${causeOf(error)}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const current = [];
|
|
267
|
+
for (const [key, handle] of synced) {
|
|
268
|
+
try {
|
|
269
|
+
const held = this.copies.get(key);
|
|
270
|
+
if (held?.erasing)
|
|
271
|
+
continue;
|
|
272
|
+
if (held === undefined || !(await exists(held.repository))) {
|
|
273
|
+
said.push(await this.materialize(key, handle));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
current.push(held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle }));
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
refused = true;
|
|
280
|
+
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (refreshing && current.length > 0) {
|
|
284
|
+
const failed = await this.refreshed(current).then(() => null, houseCause);
|
|
285
|
+
for (const copy of current)
|
|
286
|
+
said.push(...moved(copy, this.copies.get(copy.key)));
|
|
287
|
+
if (failed !== null) {
|
|
288
|
+
refused = true;
|
|
289
|
+
said.push(failed);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
293
|
+
});
|
|
225
294
|
}
|
|
226
295
|
async observed(report) {
|
|
227
296
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -229,6 +298,8 @@ export class LocalCopies {
|
|
|
229
298
|
return;
|
|
230
299
|
await this.exclusive(key, async () => {
|
|
231
300
|
const copy = this.copies.get(key);
|
|
301
|
+
if (copy === undefined || copy.erasing)
|
|
302
|
+
return;
|
|
232
303
|
let moves = copy.moves;
|
|
233
304
|
for (const { from, to } of report.moves) {
|
|
234
305
|
const held = moves.find((move) => move.worktree === report.worktree && move.to === from);
|
|
@@ -251,11 +322,11 @@ export class LocalCopies {
|
|
|
251
322
|
async push(cwd, selector, caller) {
|
|
252
323
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
253
324
|
if (located.status !== 0)
|
|
254
|
-
return
|
|
325
|
+
return outside(cwd);
|
|
255
326
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
256
327
|
const key = await this.keyAt(dirname(common));
|
|
257
328
|
if (key === undefined)
|
|
258
|
-
return
|
|
329
|
+
return outside(worktree);
|
|
259
330
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
260
331
|
}
|
|
261
332
|
async keyAt(repository) {
|
|
@@ -271,19 +342,12 @@ export class LocalCopies {
|
|
|
271
342
|
this.queues.set(key, queued.catch(() => undefined));
|
|
272
343
|
return queued;
|
|
273
344
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
this.delivering.add(key);
|
|
278
|
-
void this.exclusive(key, async () => {
|
|
279
|
-
this.delivering.delete(key);
|
|
280
|
-
const copy = this.copies.get(key);
|
|
281
|
-
if (copy.active)
|
|
282
|
-
await this.save(await this.caughtUp(copy));
|
|
283
|
-
}).catch(logged);
|
|
345
|
+
holding(keys, work) {
|
|
346
|
+
const [key, ...rest] = keys;
|
|
347
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
284
348
|
}
|
|
285
349
|
async save(copy) {
|
|
286
|
-
const path =
|
|
350
|
+
const path = recordOf(copy.key);
|
|
287
351
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
288
352
|
const temporary = `${path}.${process.pid}`;
|
|
289
353
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -291,6 +355,12 @@ export class LocalCopies {
|
|
|
291
355
|
this.copies.set(copy.key, copy);
|
|
292
356
|
return copy;
|
|
293
357
|
}
|
|
358
|
+
async erase(copy) {
|
|
359
|
+
await this.save({ ...copy, erasing: true });
|
|
360
|
+
await rm(copy.repository, { recursive: true, force: true });
|
|
361
|
+
await rm(recordOf(copy.key), { force: true });
|
|
362
|
+
this.copies.delete(copy.key);
|
|
363
|
+
}
|
|
294
364
|
async read(handle) {
|
|
295
365
|
const prefixes = [rootOf(handle)];
|
|
296
366
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
@@ -326,46 +396,13 @@ export class LocalCopies {
|
|
|
326
396
|
}
|
|
327
397
|
return read;
|
|
328
398
|
}
|
|
329
|
-
async
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (copy.room_handle !== scope.handle)
|
|
336
|
-
copy = await this.save({ ...copy, room_handle: scope.handle });
|
|
337
|
-
if (!copy.active) {
|
|
338
|
-
await this.save(await this.received({ ...copy, active: true }));
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
const discovery = await this.house.post('/kit/door/discover', {
|
|
342
|
-
prefixes: [rootOf(copy.room_handle)],
|
|
343
|
-
});
|
|
344
|
-
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
345
|
-
if (authority === undefined)
|
|
346
|
-
return;
|
|
347
|
-
if (authority.managed_replication !== 'admitted') {
|
|
348
|
-
logged(`operation_denied: ${rootOf(copy.room_handle)} is not synchronized to this Environment`);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
|
|
352
|
-
JSON.stringify([copy.write, copy.protected])) {
|
|
353
|
-
await this.save(await this.received(copy));
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
await this.save(await this.caughtUp(copy));
|
|
357
|
-
}
|
|
358
|
-
async materialize(scope) {
|
|
359
|
-
const read = await this.read(scope.handle);
|
|
360
|
-
if (read === null) {
|
|
361
|
-
logged(`House serves nothing of ${rootOf(scope.handle)} to copy`);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
399
|
+
async materialize(key, handle) {
|
|
400
|
+
const read = await this.read(handle);
|
|
401
|
+
if (read === null)
|
|
402
|
+
throw new Error('House serves nothing of it to copy');
|
|
364
403
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
365
|
-
if (await exists(repository))
|
|
366
|
-
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
404
|
+
if (await exists(repository))
|
|
405
|
+
throw new Error(`${repository} already exists and House Kit leaves it as it is`);
|
|
369
406
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
370
407
|
await mkdir(staging, { recursive: true });
|
|
371
408
|
let house;
|
|
@@ -373,7 +410,7 @@ export class LocalCopies {
|
|
|
373
410
|
try {
|
|
374
411
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
375
412
|
house = await importCommits(staging, [
|
|
376
|
-
{ message: `House ${rootOf(
|
|
413
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
377
414
|
]);
|
|
378
415
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
379
416
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -386,11 +423,10 @@ export class LocalCopies {
|
|
|
386
423
|
throw error;
|
|
387
424
|
}
|
|
388
425
|
await this.save({
|
|
389
|
-
key
|
|
426
|
+
key,
|
|
390
427
|
room_ref: read.anchor,
|
|
391
|
-
room_handle:
|
|
428
|
+
room_handle: handle,
|
|
392
429
|
repository,
|
|
393
|
-
active: true,
|
|
394
430
|
house,
|
|
395
431
|
position: read.position,
|
|
396
432
|
log_epoch: read.log_epoch,
|
|
@@ -400,6 +436,7 @@ export class LocalCopies {
|
|
|
400
436
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
401
437
|
moves: [],
|
|
402
438
|
});
|
|
439
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
403
440
|
}
|
|
404
441
|
async received(copy) {
|
|
405
442
|
const read = await this.read(copy.room_handle);
|
|
@@ -423,30 +460,57 @@ export class LocalCopies {
|
|
|
423
460
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
424
461
|
return next;
|
|
425
462
|
}
|
|
426
|
-
async
|
|
427
|
-
|
|
428
|
-
|
|
463
|
+
async refreshed(copies) {
|
|
464
|
+
const discover = (group) => this.house.post('/kit/door/discover', { prefixes: group.map((copy) => rootOf(copy.room_handle)) });
|
|
465
|
+
const [first, ...rest] = copies;
|
|
466
|
+
const { bounds, authorities } = await discover([first]);
|
|
467
|
+
for (const group of grouped(rest, bounds.max_prefixes))
|
|
468
|
+
authorities.push(...(await discover(group)).authorities);
|
|
469
|
+
const unchanged = [];
|
|
470
|
+
for (const copy of copies) {
|
|
471
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
472
|
+
if (authority !== undefined &&
|
|
473
|
+
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
474
|
+
await this.save(await this.received(copy));
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
unchanged.push(copy);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
481
|
+
await this.caughtUp(group, false);
|
|
482
|
+
}
|
|
483
|
+
async caughtUp(copies, whole) {
|
|
484
|
+
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
485
|
+
let asking = [...copies];
|
|
486
|
+
while (asking.length > 0) {
|
|
429
487
|
let answer;
|
|
430
488
|
try {
|
|
431
489
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
432
|
-
prefixes:
|
|
433
|
-
positions:
|
|
490
|
+
prefixes: asking.map((copy) => rootOf(copy.room_handle)),
|
|
491
|
+
positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
|
|
434
492
|
});
|
|
435
493
|
}
|
|
436
494
|
catch (error) {
|
|
437
|
-
const
|
|
438
|
-
if (
|
|
439
|
-
return this.received(copy);
|
|
440
|
-
if (code !== 'replica_behind')
|
|
495
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
496
|
+
if (expired === undefined)
|
|
441
497
|
throw error;
|
|
442
|
-
|
|
443
|
-
|
|
498
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
499
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const advanced = [];
|
|
503
|
+
for (const copy of asking) {
|
|
504
|
+
const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
|
|
505
|
+
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
506
|
+
continue;
|
|
507
|
+
const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
508
|
+
caught.set(copy.key, next);
|
|
509
|
+
advanced.push(next);
|
|
444
510
|
}
|
|
445
|
-
|
|
446
|
-
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
447
|
-
return copy;
|
|
448
|
-
copy = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
511
|
+
asking = whole ? advanced : [];
|
|
449
512
|
}
|
|
513
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
450
514
|
}
|
|
451
515
|
async incorporated(copy, changes, position, logEpoch) {
|
|
452
516
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -496,6 +560,8 @@ export class LocalCopies {
|
|
|
496
560
|
}
|
|
497
561
|
async pushed(key, worktree, selector, caller) {
|
|
498
562
|
let copy = this.copies.get(key);
|
|
563
|
+
if (copy === undefined || copy.erasing)
|
|
564
|
+
return outside(worktree);
|
|
499
565
|
if (copy.push?.state === 'submitted')
|
|
500
566
|
return this.settle(copy, caller);
|
|
501
567
|
const selected = await run(worktree, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${selector}^{commit}`]);
|
|
@@ -509,9 +575,6 @@ export class LocalCopies {
|
|
|
509
575
|
}
|
|
510
576
|
copy = await this.save(without(copy));
|
|
511
577
|
}
|
|
512
|
-
if (!copy.active) {
|
|
513
|
-
return { refused: true, text: `${rootOf(copy.room_handle)} is no longer synced to this Environment, so House takes no push from it` };
|
|
514
|
-
}
|
|
515
578
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
516
579
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
517
580
|
if (base === undefined) {
|
|
@@ -522,7 +585,7 @@ export class LocalCopies {
|
|
|
522
585
|
changes = await this.derived(copy, base, commit);
|
|
523
586
|
}
|
|
524
587
|
catch (error) {
|
|
525
|
-
return { refused: true, text:
|
|
588
|
+
return { refused: true, text: houseCause(error) };
|
|
526
589
|
}
|
|
527
590
|
if (changes.length === 0)
|
|
528
591
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -549,7 +612,18 @@ export class LocalCopies {
|
|
|
549
612
|
const after = await tree(copy.repository, commit);
|
|
550
613
|
const moved = movedPaths(copy.moves, before, after);
|
|
551
614
|
const arrived = new Set(moved.values());
|
|
552
|
-
const
|
|
615
|
+
const arriving = [...after].filter(([path, entry]) => before.get(path) !== entry);
|
|
616
|
+
if (arriving.length > 0) {
|
|
617
|
+
const { bounds } = await this.house.post('/kit/door/discover', { prefixes: [root] });
|
|
618
|
+
const binary = [];
|
|
619
|
+
for (const [path, entry] of arriving) {
|
|
620
|
+
if (!(await isText(copy.repository, entry, bounds.max_authored_file_bytes)))
|
|
621
|
+
binary.push(path);
|
|
622
|
+
}
|
|
623
|
+
if (binary.length > 0)
|
|
624
|
+
throw new Error(textOnly(binary));
|
|
625
|
+
}
|
|
626
|
+
const text = (entry) => blobText(copy.repository, entry);
|
|
553
627
|
const changes = [];
|
|
554
628
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
555
629
|
if (arrived.has(path))
|
|
@@ -562,7 +636,7 @@ export class LocalCopies {
|
|
|
562
636
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
563
637
|
const moving = after.get(to);
|
|
564
638
|
if (moving !== prior) {
|
|
565
|
-
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving
|
|
639
|
+
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
|
|
566
640
|
}
|
|
567
641
|
continue;
|
|
568
642
|
}
|
|
@@ -571,9 +645,9 @@ export class LocalCopies {
|
|
|
571
645
|
if (next === undefined)
|
|
572
646
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
573
647
|
else if (prior === undefined)
|
|
574
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
648
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
575
649
|
else
|
|
576
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
650
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
577
651
|
}
|
|
578
652
|
return changes;
|
|
579
653
|
}
|
|
@@ -600,12 +674,12 @@ export class LocalCopies {
|
|
|
600
674
|
const { upload, ...unstaged } = copy.push;
|
|
601
675
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
602
676
|
}
|
|
603
|
-
if (outcome.refused.code === '
|
|
677
|
+
if (outcome.refused.code === 'expired')
|
|
604
678
|
return this.recovered(copy);
|
|
605
679
|
const rebased = rebaseFixes(outcome.refused);
|
|
606
680
|
let settled = await this.save(without(copy));
|
|
607
681
|
try {
|
|
608
|
-
settled =
|
|
682
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
609
683
|
}
|
|
610
684
|
catch {
|
|
611
685
|
return {
|
|
@@ -624,7 +698,7 @@ export class LocalCopies {
|
|
|
624
698
|
}
|
|
625
699
|
async recovered(copy) {
|
|
626
700
|
const push = copy.push;
|
|
627
|
-
const current =
|
|
701
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
628
702
|
const house = await tree(copy.repository, current.house);
|
|
629
703
|
const root = rootOf(current.room_handle);
|
|
630
704
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
@@ -640,7 +714,7 @@ export class LocalCopies {
|
|
|
640
714
|
written.push({
|
|
641
715
|
path: `${root}/${path}`,
|
|
642
716
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
643
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
717
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
644
718
|
});
|
|
645
719
|
}
|
|
646
720
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -668,7 +742,7 @@ export class LocalCopies {
|
|
|
668
742
|
const push = copy.push;
|
|
669
743
|
let written = push.written;
|
|
670
744
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
671
|
-
copy =
|
|
745
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
672
746
|
const house = await tree(copy.repository, copy.house);
|
|
673
747
|
const sent = await tree(copy.repository, push.house);
|
|
674
748
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -684,7 +758,7 @@ export class LocalCopies {
|
|
|
684
758
|
{
|
|
685
759
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
686
760
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
687
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
761
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
688
762
|
},
|
|
689
763
|
];
|
|
690
764
|
}
|
|
@@ -729,7 +803,7 @@ export class LocalCopies {
|
|
|
729
803
|
async landed(repository, changes, house, selected) {
|
|
730
804
|
const text = async (path) => {
|
|
731
805
|
const entry = house.get(relative(path));
|
|
732
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
806
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
733
807
|
};
|
|
734
808
|
for (const change of changes) {
|
|
735
809
|
const current = await text(change.path);
|
package/dist/end.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { forgetRejected } from './home.js';
|
|
2
|
+
import { installCommand } from './placement.js';
|
|
3
|
+
import { killDescendants } from './scope.js';
|
|
4
|
+
export const REPLACED = 'another Kit started for this Environment';
|
|
5
|
+
function exit(reason, status) {
|
|
6
|
+
process.stderr.write(`kit: ${reason}\n`);
|
|
7
|
+
killDescendants();
|
|
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');
|
|
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
|
+
});
|