@agentshouse/kit 0.2.1-cb6679d048e60 → 0.2.1-cb66cb2fd0696
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 +47 -46
- package/dist/acp.js +102 -108
- package/dist/agents.js +214 -62
- package/dist/api.js +36 -3
- package/dist/app-git.js +48 -0
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +40 -59
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +161 -95
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +105 -0
- package/dist/conversations.js +732 -330
- package/dist/copies.js +205 -150
- package/dist/end.js +33 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +546 -0
- package/dist/files.js +140 -17
- package/dist/git.js +13 -11
- package/dist/home.js +89 -7
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -159
- package/dist/instructions.js +8 -10
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +119 -104
- package/dist/logout.js +14 -13
- package/dist/mcp.js +8 -3
- package/dist/parts.js +82 -0
- package/dist/placement.js +31 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +41 -0
- package/dist/resident.js +114 -51
- package/dist/scope.js +214 -0
- package/dist/scripts.js +255 -0
- package/dist/secret-input.js +4 -1
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +40 -32
- package/dist/silence.js +28 -0
- package/dist/skills.js +49 -35
- package/dist/transcription.js +129 -41
- package/dist/translate.js +2 -8
- package/dist/update.js +91 -104
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/package.json +20 -20
- package/dist/blocks.js +0 -51
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/stream.js +0 -46
- 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
|
@@ -2,19 +2,22 @@ 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';
|
|
10
10
|
import { changed } from './translate.js';
|
|
11
|
+
import { relayed } from './relay.js';
|
|
11
12
|
const BRANCH = 'main';
|
|
12
13
|
const PROVENANCE = '_provenance';
|
|
13
|
-
const
|
|
14
|
-
const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
|
|
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,11 +69,14 @@ function sidecar(path) {
|
|
|
40
69
|
segments.splice(-1, 0, PROVENANCE);
|
|
41
70
|
return segments.join('/');
|
|
42
71
|
}
|
|
43
|
-
function
|
|
44
|
-
return
|
|
72
|
+
function recordOf(key) {
|
|
73
|
+
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
74
|
+
}
|
|
75
|
+
function rootOf(handle) {
|
|
76
|
+
return `/rooms/${handle}`;
|
|
45
77
|
}
|
|
46
78
|
function relative(path) {
|
|
47
|
-
return path.replace(/^\/
|
|
79
|
+
return path.replace(/^\/rooms\/[^/]+\//, '');
|
|
48
80
|
}
|
|
49
81
|
function declaredWritable(path, copy) {
|
|
50
82
|
const deepest = (scopes) => Math.max(-1, ...scopes.filter((scope) => covers(path, scope)).map((scope) => scope.length));
|
|
@@ -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,19 +184,18 @@ 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 = {
|
|
160
191
|
key: 'owner',
|
|
161
192
|
submit: async (operation, changes) => {
|
|
162
193
|
try {
|
|
163
|
-
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 }));
|
|
164
195
|
return { accepted: outcome.contents };
|
|
165
196
|
}
|
|
166
197
|
catch (error) {
|
|
167
|
-
if (!(error
|
|
198
|
+
if (!finalRefusal(error))
|
|
168
199
|
throw error;
|
|
169
200
|
return {
|
|
170
201
|
refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
|
|
@@ -176,7 +207,7 @@ export class LocalCopies {
|
|
|
176
207
|
async load() {
|
|
177
208
|
const home = join(kitHome(), 'local-copies');
|
|
178
209
|
const names = (await readdir(join(home, 'rooms')).catch(() => [])).filter((name) => name.endsWith('.json'));
|
|
179
|
-
for (const path of
|
|
210
|
+
for (const path of names.map((name) => join(home, 'rooms', name))) {
|
|
180
211
|
const text = await readFile(path, 'utf8').catch(() => null);
|
|
181
212
|
if (text === null)
|
|
182
213
|
continue;
|
|
@@ -185,47 +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.
|
|
191
|
-
.map((copy) => `${rootOf(copy)}: ${copy.repository}`)
|
|
221
|
+
.filter((copy) => copy.unfollowed === undefined)
|
|
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 = [
|
|
202
|
-
...(chosen?.rooms ?? [])
|
|
203
|
-
.filter((room) => room.room_handle !== null)
|
|
204
|
-
.map((room) => ({ key: `rooms/${room.room_ref}`, private: false, handle: room.room_handle })),
|
|
205
|
-
...(chosen?.private === true ? [{ key: 'private', private: true, handle: null }] : []),
|
|
206
|
-
];
|
|
207
|
-
const keys = new Set(selected.map((scope) => scope.key));
|
|
208
|
-
const work = [];
|
|
209
|
-
for (const key of this.copies.keys()) {
|
|
210
|
-
if (keys.has(key))
|
|
211
|
-
continue;
|
|
212
|
-
work.push(this.exclusive(key, async () => {
|
|
213
|
-
const copy = this.copies.get(key);
|
|
214
|
-
if (copy.active)
|
|
215
|
-
await this.save({ ...copy, active: false });
|
|
216
|
-
}));
|
|
217
|
-
}
|
|
218
|
-
for (const scope of selected)
|
|
219
|
-
work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
|
|
220
|
-
await Promise.all(work);
|
|
225
|
+
connected() {
|
|
226
|
+
return this.selected();
|
|
221
227
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
});
|
|
229
288
|
}
|
|
230
289
|
async observed(report) {
|
|
231
290
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -255,11 +314,11 @@ export class LocalCopies {
|
|
|
255
314
|
async push(cwd, selector, caller) {
|
|
256
315
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
257
316
|
if (located.status !== 0)
|
|
258
|
-
return
|
|
317
|
+
return outside(cwd);
|
|
259
318
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
260
319
|
const key = await this.keyAt(dirname(common));
|
|
261
320
|
if (key === undefined)
|
|
262
|
-
return
|
|
321
|
+
return outside(worktree);
|
|
263
322
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
264
323
|
}
|
|
265
324
|
async keyAt(repository) {
|
|
@@ -275,19 +334,12 @@ export class LocalCopies {
|
|
|
275
334
|
this.queues.set(key, queued.catch(() => undefined));
|
|
276
335
|
return queued;
|
|
277
336
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.delivering.add(key);
|
|
282
|
-
void this.exclusive(key, async () => {
|
|
283
|
-
this.delivering.delete(key);
|
|
284
|
-
const copy = this.copies.get(key);
|
|
285
|
-
if (copy.active)
|
|
286
|
-
await this.save(await this.caughtUp(copy));
|
|
287
|
-
}).catch(logged);
|
|
337
|
+
holding(keys, work) {
|
|
338
|
+
const [key, ...rest] = keys;
|
|
339
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
288
340
|
}
|
|
289
341
|
async save(copy) {
|
|
290
|
-
const path =
|
|
342
|
+
const path = recordOf(copy.key);
|
|
291
343
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
292
344
|
const temporary = `${path}.${process.pid}`;
|
|
293
345
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -295,12 +347,12 @@ export class LocalCopies {
|
|
|
295
347
|
this.copies.set(copy.key, copy);
|
|
296
348
|
return copy;
|
|
297
349
|
}
|
|
298
|
-
async read(
|
|
299
|
-
const prefixes = [rootOf(
|
|
350
|
+
async read(handle) {
|
|
351
|
+
const prefixes = [rootOf(handle)];
|
|
300
352
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
301
353
|
let bundle = await this.house.post('/kit/door/bootstrap', { prefixes });
|
|
302
354
|
if ('transfer' in bundle)
|
|
303
|
-
bundle = (await (await fetch(bundle.transfer.url)).json());
|
|
355
|
+
bundle = (await (await fetch(relayed(bundle.transfer.url))).json());
|
|
304
356
|
const [view] = bundle.scopes;
|
|
305
357
|
if (view === undefined)
|
|
306
358
|
return null;
|
|
@@ -330,46 +382,13 @@ export class LocalCopies {
|
|
|
330
382
|
}
|
|
331
383
|
return read;
|
|
332
384
|
}
|
|
333
|
-
async
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
copy = await this.save({ ...copy, room_handle: scope.handle });
|
|
341
|
-
if (!copy.active) {
|
|
342
|
-
await this.save(await this.received({ ...copy, active: true }));
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
const discovery = await this.house.post('/kit/door/discover', {
|
|
346
|
-
prefixes: [rootOf(copy)],
|
|
347
|
-
});
|
|
348
|
-
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
349
|
-
if (authority === undefined)
|
|
350
|
-
return;
|
|
351
|
-
if (authority.managed_replication !== 'admitted') {
|
|
352
|
-
logged(`operation_denied: ${rootOf(copy)} is not synchronized to this Environment`);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
|
|
356
|
-
JSON.stringify([copy.write, copy.protected])) {
|
|
357
|
-
await this.save(await this.received(copy));
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
await this.save(await this.caughtUp(copy));
|
|
361
|
-
}
|
|
362
|
-
async materialize(scope) {
|
|
363
|
-
const read = await this.read({ private: scope.private, room_handle: scope.handle });
|
|
364
|
-
if (read === null) {
|
|
365
|
-
logged(`House serves nothing of ${rootOf({ private: scope.private, room_handle: scope.handle })} to copy`);
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
const repository = scope.private ? join(localCopies(), 'private') : join(localCopies(), 'rooms', read.anchor);
|
|
369
|
-
if (await exists(repository)) {
|
|
370
|
-
logged(`${repository} already exists and House Kit leaves it as it is`);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
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');
|
|
389
|
+
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
390
|
+
if (await exists(repository))
|
|
391
|
+
throw new Error(`${repository} already exists and Kit leaves it as it is`);
|
|
373
392
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
374
393
|
await mkdir(staging, { recursive: true });
|
|
375
394
|
let house;
|
|
@@ -377,7 +396,7 @@ export class LocalCopies {
|
|
|
377
396
|
try {
|
|
378
397
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
379
398
|
house = await importCommits(staging, [
|
|
380
|
-
{ message: `House ${rootOf(
|
|
399
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
381
400
|
]);
|
|
382
401
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
383
402
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -390,12 +409,10 @@ export class LocalCopies {
|
|
|
390
409
|
throw error;
|
|
391
410
|
}
|
|
392
411
|
await this.save({
|
|
393
|
-
key
|
|
394
|
-
private: scope.private,
|
|
412
|
+
key,
|
|
395
413
|
room_ref: read.anchor,
|
|
396
|
-
room_handle:
|
|
414
|
+
room_handle: handle,
|
|
397
415
|
repository,
|
|
398
|
-
active: true,
|
|
399
416
|
house,
|
|
400
417
|
position: read.position,
|
|
401
418
|
log_epoch: read.log_epoch,
|
|
@@ -405,13 +422,14 @@ export class LocalCopies {
|
|
|
405
422
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
406
423
|
moves: [],
|
|
407
424
|
});
|
|
425
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
408
426
|
}
|
|
409
427
|
async received(copy) {
|
|
410
|
-
const read = await this.read(copy);
|
|
428
|
+
const read = await this.read(copy.room_handle);
|
|
411
429
|
if (read === null)
|
|
412
430
|
return copy;
|
|
413
431
|
const house = await importCommits(copy.repository, [
|
|
414
|
-
{ message: `House ${rootOf(copy)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
|
|
432
|
+
{ message: `House ${rootOf(copy.room_handle)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
|
|
415
433
|
]);
|
|
416
434
|
const removed = copy.read_only.filter((path) => !read.readOnly.has(path));
|
|
417
435
|
const readOnly = await readOnlyPlaced(copy.repository, copy.read_only, read.readOnly, removed);
|
|
@@ -428,30 +446,57 @@ export class LocalCopies {
|
|
|
428
446
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
429
447
|
return next;
|
|
430
448
|
}
|
|
431
|
-
async
|
|
432
|
-
|
|
433
|
-
|
|
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) {
|
|
434
473
|
let answer;
|
|
435
474
|
try {
|
|
436
475
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
437
|
-
prefixes:
|
|
438
|
-
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 })),
|
|
439
478
|
});
|
|
440
479
|
}
|
|
441
480
|
catch (error) {
|
|
442
|
-
const
|
|
443
|
-
if (
|
|
444
|
-
return this.received(copy);
|
|
445
|
-
if (code !== 'replica_behind')
|
|
481
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
482
|
+
if (expired === undefined)
|
|
446
483
|
throw error;
|
|
447
|
-
|
|
448
|
-
|
|
484
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
485
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
486
|
+
continue;
|
|
449
487
|
}
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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 : [];
|
|
454
498
|
}
|
|
499
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
455
500
|
}
|
|
456
501
|
async incorporated(copy, changes, position, logEpoch) {
|
|
457
502
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -484,7 +529,7 @@ export class LocalCopies {
|
|
|
484
529
|
let { house, manifests } = copy;
|
|
485
530
|
if (files.size > 0) {
|
|
486
531
|
house = await importCommits(copy.repository, [
|
|
487
|
-
{ message: `House ${rootOf(copy)} at position ${position}`, from: copy.house, files },
|
|
532
|
+
{ message: `House ${rootOf(copy.room_handle)} at position ${position}`, from: copy.house, files },
|
|
488
533
|
]);
|
|
489
534
|
manifests = { ...manifests, [house]: { parent: copy.house, files: revisions } };
|
|
490
535
|
}
|
|
@@ -514,9 +559,6 @@ export class LocalCopies {
|
|
|
514
559
|
}
|
|
515
560
|
copy = await this.save(without(copy));
|
|
516
561
|
}
|
|
517
|
-
if (!copy.active) {
|
|
518
|
-
return { refused: true, text: `${rootOf(copy)} is no longer synced to this Environment, so House takes no push from it` };
|
|
519
|
-
}
|
|
520
562
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
521
563
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
522
564
|
if (base === undefined) {
|
|
@@ -527,7 +569,7 @@ export class LocalCopies {
|
|
|
527
569
|
changes = await this.derived(copy, base, commit);
|
|
528
570
|
}
|
|
529
571
|
catch (error) {
|
|
530
|
-
return { refused: true, text:
|
|
572
|
+
return { refused: true, text: houseCause(error) };
|
|
531
573
|
}
|
|
532
574
|
if (changes.length === 0)
|
|
533
575
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -549,12 +591,23 @@ export class LocalCopies {
|
|
|
549
591
|
return this.settle(submitted, caller);
|
|
550
592
|
}
|
|
551
593
|
async derived(copy, base, commit) {
|
|
552
|
-
const root = rootOf(copy);
|
|
594
|
+
const root = rootOf(copy.room_handle);
|
|
553
595
|
const before = await tree(copy.repository, base);
|
|
554
596
|
const after = await tree(copy.repository, commit);
|
|
555
597
|
const moved = movedPaths(copy.moves, before, after);
|
|
556
598
|
const arrived = new Set(moved.values());
|
|
557
|
-
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);
|
|
558
611
|
const changes = [];
|
|
559
612
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
560
613
|
if (arrived.has(path))
|
|
@@ -567,7 +620,7 @@ export class LocalCopies {
|
|
|
567
620
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
568
621
|
const moving = after.get(to);
|
|
569
622
|
if (moving !== prior) {
|
|
570
|
-
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) });
|
|
571
624
|
}
|
|
572
625
|
continue;
|
|
573
626
|
}
|
|
@@ -576,9 +629,9 @@ export class LocalCopies {
|
|
|
576
629
|
if (next === undefined)
|
|
577
630
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
578
631
|
else if (prior === undefined)
|
|
579
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
632
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
580
633
|
else
|
|
581
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
634
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
582
635
|
}
|
|
583
636
|
return changes;
|
|
584
637
|
}
|
|
@@ -605,12 +658,14 @@ export class LocalCopies {
|
|
|
605
658
|
const { upload, ...unstaged } = copy.push;
|
|
606
659
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
607
660
|
}
|
|
608
|
-
if (outcome.refused.code === '
|
|
661
|
+
if (outcome.refused.code === 'expired')
|
|
609
662
|
return this.recovered(copy);
|
|
610
|
-
const rebased = rebaseFixes(outcome.refused);
|
|
611
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);
|
|
612
667
|
try {
|
|
613
|
-
settled =
|
|
668
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
614
669
|
}
|
|
615
670
|
catch {
|
|
616
671
|
return {
|
|
@@ -629,9 +684,9 @@ export class LocalCopies {
|
|
|
629
684
|
}
|
|
630
685
|
async recovered(copy) {
|
|
631
686
|
const push = copy.push;
|
|
632
|
-
const current =
|
|
687
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
633
688
|
const house = await tree(copy.repository, current.house);
|
|
634
|
-
const root = rootOf(current);
|
|
689
|
+
const root = rootOf(current.room_handle);
|
|
635
690
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
636
691
|
await this.save(without(current));
|
|
637
692
|
return {
|
|
@@ -645,7 +700,7 @@ export class LocalCopies {
|
|
|
645
700
|
written.push({
|
|
646
701
|
path: `${root}/${path}`,
|
|
647
702
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
648
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
703
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
649
704
|
});
|
|
650
705
|
}
|
|
651
706
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -673,7 +728,7 @@ export class LocalCopies {
|
|
|
673
728
|
const push = copy.push;
|
|
674
729
|
let written = push.written;
|
|
675
730
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
676
|
-
copy =
|
|
731
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
677
732
|
const house = await tree(copy.repository, copy.house);
|
|
678
733
|
const sent = await tree(copy.repository, push.house);
|
|
679
734
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -687,9 +742,9 @@ export class LocalCopies {
|
|
|
687
742
|
written = [
|
|
688
743
|
...written,
|
|
689
744
|
{
|
|
690
|
-
path: `${rootOf(copy)}/${path}`,
|
|
745
|
+
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
691
746
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
692
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
747
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
693
748
|
},
|
|
694
749
|
];
|
|
695
750
|
}
|
|
@@ -707,7 +762,7 @@ export class LocalCopies {
|
|
|
707
762
|
}
|
|
708
763
|
const edits = new Map([...files].filter(([path, content]) => (content === null ? selected.has(path) : selected.get(path) !== blobId(content))));
|
|
709
764
|
const received = [...files.keys()].filter((path) => revisionAt(copy, copy.house, path) === revisionAt(copy, push.house, path));
|
|
710
|
-
const accepted = `House ${rootOf(copy)} with ${push.commit} accepted`;
|
|
765
|
+
const accepted = `House ${rootOf(copy.room_handle)} with ${push.commit} accepted`;
|
|
711
766
|
const house = await importCommits(copy.repository, [
|
|
712
767
|
...(edits.size === 0 ? [] : [{ message: accepted, from: push.commit, files: edits }]),
|
|
713
768
|
{
|
|
@@ -734,7 +789,7 @@ export class LocalCopies {
|
|
|
734
789
|
async landed(repository, changes, house, selected) {
|
|
735
790
|
const text = async (path) => {
|
|
736
791
|
const entry = house.get(relative(path));
|
|
737
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
792
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
738
793
|
};
|
|
739
794
|
for (const change of changes) {
|
|
740
795
|
const current = await text(change.path);
|
package/dist/end.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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) {
|
|
7
|
+
process.stderr.write(`kit: ${reason}\n`);
|
|
8
|
+
killDescendants();
|
|
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
|
+
function updateLine(lead) {
|
|
18
|
+
const command = installCommand();
|
|
19
|
+
return command === null ? `${lead}; install it again.` : `${lead}. Update it: ${command}`;
|
|
20
|
+
}
|
|
21
|
+
export function updateRequired() {
|
|
22
|
+
end(updateLine('This Kit is too old for House'));
|
|
23
|
+
}
|
|
24
|
+
export function unknownMethod(kind) {
|
|
25
|
+
return updateLine(`This Kit does not know ${kind}`);
|
|
26
|
+
}
|
|
27
|
+
export function newerThanHouse(release, command) {
|
|
28
|
+
end(`Kit ${KIT_RELEASE} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|
|
29
|
+
}
|
|
30
|
+
export async function signInAgain(rejected) {
|
|
31
|
+
await forgetRejected(rejected);
|
|
32
|
+
restart('sign in again');
|
|
33
|
+
}
|
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
|
+
});
|