@agentshouse/kit 0.2.1-ce145e9aadbf6 → 0.2.1-ce3d2bb67177c
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 +40 -39
- package/dist/acp.js +48 -105
- package/dist/agents.js +153 -43
- 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/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +127 -70
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +125 -0
- package/dist/conversations.js +620 -323
- package/dist/copies.js +188 -128
- package/dist/end.js +27 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +123 -17
- package/dist/git.js +11 -9
- package/dist/home.js +96 -7
- package/dist/house-cli.js +327 -0
- package/dist/house-main.js +7 -159
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +117 -104
- package/dist/logout.js +14 -13
- package/dist/mcp.js +4 -2
- package/dist/parts.js +82 -0
- package/dist/placement.js +39 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +106 -50
- package/dist/scope.js +206 -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 +37 -31
- package/dist/skills.js +84 -15
- package/dist/transcription.js +100 -21
- package/dist/translate.js +2 -8
- package/dist/update.js +71 -50
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/how-we-work.md +13 -3
- package/package.json +4 -3
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/dist/blocks.js +0 -51
- package/dist/stream.js +0 -47
- 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
|
|
15
|
-
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' };
|
|
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
|
}
|
|
@@ -102,7 +133,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
102
133
|
].map(pattern);
|
|
103
134
|
const granted = readOnly.filter((path) => !protectedPath(path)).map(pattern);
|
|
104
135
|
await ruled(join(repository, '.gitignore'), [
|
|
105
|
-
'#
|
|
136
|
+
'# Kit writes this file and .ignore. The paths below are read-only House',
|
|
106
137
|
'# content: Git does not track them, and House refuses a change to them even',
|
|
107
138
|
'# when this file is edited or a path is force-added.',
|
|
108
139
|
'/.gitignore',
|
|
@@ -117,7 +148,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
117
148
|
'',
|
|
118
149
|
].join('\n'));
|
|
119
150
|
await ruled(join(repository, '.ignore'), [
|
|
120
|
-
'#
|
|
151
|
+
'# Kit writes this file so native search still reads the read-only House',
|
|
121
152
|
'# content that .gitignore keeps out of Git.',
|
|
122
153
|
`!${PROVENANCE}`,
|
|
123
154
|
...[...house, ...granted].map((rule) => `!${rule}`),
|
|
@@ -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,75 @@ 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.unfollowed === 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
|
+
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
|
+
});
|
|
225
288
|
}
|
|
226
289
|
async observed(report) {
|
|
227
290
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -251,11 +314,11 @@ export class LocalCopies {
|
|
|
251
314
|
async push(cwd, selector, caller) {
|
|
252
315
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
253
316
|
if (located.status !== 0)
|
|
254
|
-
return
|
|
317
|
+
return outside(cwd);
|
|
255
318
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
256
319
|
const key = await this.keyAt(dirname(common));
|
|
257
320
|
if (key === undefined)
|
|
258
|
-
return
|
|
321
|
+
return outside(worktree);
|
|
259
322
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
260
323
|
}
|
|
261
324
|
async keyAt(repository) {
|
|
@@ -271,19 +334,12 @@ export class LocalCopies {
|
|
|
271
334
|
this.queues.set(key, queued.catch(() => undefined));
|
|
272
335
|
return queued;
|
|
273
336
|
}
|
|
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);
|
|
337
|
+
holding(keys, work) {
|
|
338
|
+
const [key, ...rest] = keys;
|
|
339
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
284
340
|
}
|
|
285
341
|
async save(copy) {
|
|
286
|
-
const path =
|
|
342
|
+
const path = recordOf(copy.key);
|
|
287
343
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
288
344
|
const temporary = `${path}.${process.pid}`;
|
|
289
345
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -326,46 +382,13 @@ export class LocalCopies {
|
|
|
326
382
|
}
|
|
327
383
|
return read;
|
|
328
384
|
}
|
|
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
|
-
}
|
|
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');
|
|
364
389
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
365
|
-
if (await exists(repository))
|
|
366
|
-
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
390
|
+
if (await exists(repository))
|
|
391
|
+
throw new Error(`${repository} already exists and Kit leaves it as it is`);
|
|
369
392
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
370
393
|
await mkdir(staging, { recursive: true });
|
|
371
394
|
let house;
|
|
@@ -373,7 +396,7 @@ export class LocalCopies {
|
|
|
373
396
|
try {
|
|
374
397
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
375
398
|
house = await importCommits(staging, [
|
|
376
|
-
{ message: `House ${rootOf(
|
|
399
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
377
400
|
]);
|
|
378
401
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
379
402
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -386,11 +409,10 @@ export class LocalCopies {
|
|
|
386
409
|
throw error;
|
|
387
410
|
}
|
|
388
411
|
await this.save({
|
|
389
|
-
key
|
|
412
|
+
key,
|
|
390
413
|
room_ref: read.anchor,
|
|
391
|
-
room_handle:
|
|
414
|
+
room_handle: handle,
|
|
392
415
|
repository,
|
|
393
|
-
active: true,
|
|
394
416
|
house,
|
|
395
417
|
position: read.position,
|
|
396
418
|
log_epoch: read.log_epoch,
|
|
@@ -400,6 +422,7 @@ export class LocalCopies {
|
|
|
400
422
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
401
423
|
moves: [],
|
|
402
424
|
});
|
|
425
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
403
426
|
}
|
|
404
427
|
async received(copy) {
|
|
405
428
|
const read = await this.read(copy.room_handle);
|
|
@@ -423,30 +446,57 @@ export class LocalCopies {
|
|
|
423
446
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
424
447
|
return next;
|
|
425
448
|
}
|
|
426
|
-
async
|
|
427
|
-
|
|
428
|
-
|
|
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) {
|
|
429
473
|
let answer;
|
|
430
474
|
try {
|
|
431
475
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
432
|
-
prefixes:
|
|
433
|
-
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 })),
|
|
434
478
|
});
|
|
435
479
|
}
|
|
436
480
|
catch (error) {
|
|
437
|
-
const
|
|
438
|
-
if (
|
|
439
|
-
return this.received(copy);
|
|
440
|
-
if (code !== 'replica_behind')
|
|
481
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
482
|
+
if (expired === undefined)
|
|
441
483
|
throw error;
|
|
442
|
-
|
|
443
|
-
|
|
484
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
485
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
486
|
+
continue;
|
|
444
487
|
}
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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 : [];
|
|
449
498
|
}
|
|
499
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
450
500
|
}
|
|
451
501
|
async incorporated(copy, changes, position, logEpoch) {
|
|
452
502
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -509,9 +559,6 @@ export class LocalCopies {
|
|
|
509
559
|
}
|
|
510
560
|
copy = await this.save(without(copy));
|
|
511
561
|
}
|
|
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
562
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
516
563
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
517
564
|
if (base === undefined) {
|
|
@@ -522,7 +569,7 @@ export class LocalCopies {
|
|
|
522
569
|
changes = await this.derived(copy, base, commit);
|
|
523
570
|
}
|
|
524
571
|
catch (error) {
|
|
525
|
-
return { refused: true, text:
|
|
572
|
+
return { refused: true, text: houseCause(error) };
|
|
526
573
|
}
|
|
527
574
|
if (changes.length === 0)
|
|
528
575
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -549,7 +596,18 @@ export class LocalCopies {
|
|
|
549
596
|
const after = await tree(copy.repository, commit);
|
|
550
597
|
const moved = movedPaths(copy.moves, before, after);
|
|
551
598
|
const arrived = new Set(moved.values());
|
|
552
|
-
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);
|
|
553
611
|
const changes = [];
|
|
554
612
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
555
613
|
if (arrived.has(path))
|
|
@@ -562,7 +620,7 @@ export class LocalCopies {
|
|
|
562
620
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
563
621
|
const moving = after.get(to);
|
|
564
622
|
if (moving !== prior) {
|
|
565
|
-
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) });
|
|
566
624
|
}
|
|
567
625
|
continue;
|
|
568
626
|
}
|
|
@@ -571,9 +629,9 @@ export class LocalCopies {
|
|
|
571
629
|
if (next === undefined)
|
|
572
630
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
573
631
|
else if (prior === undefined)
|
|
574
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
632
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
575
633
|
else
|
|
576
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
634
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
577
635
|
}
|
|
578
636
|
return changes;
|
|
579
637
|
}
|
|
@@ -600,12 +658,14 @@ export class LocalCopies {
|
|
|
600
658
|
const { upload, ...unstaged } = copy.push;
|
|
601
659
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
602
660
|
}
|
|
603
|
-
if (outcome.refused.code === '
|
|
661
|
+
if (outcome.refused.code === 'expired')
|
|
604
662
|
return this.recovered(copy);
|
|
605
|
-
const rebased = rebaseFixes(outcome.refused);
|
|
606
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);
|
|
607
667
|
try {
|
|
608
|
-
settled =
|
|
668
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
609
669
|
}
|
|
610
670
|
catch {
|
|
611
671
|
return {
|
|
@@ -624,7 +684,7 @@ export class LocalCopies {
|
|
|
624
684
|
}
|
|
625
685
|
async recovered(copy) {
|
|
626
686
|
const push = copy.push;
|
|
627
|
-
const current =
|
|
687
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
628
688
|
const house = await tree(copy.repository, current.house);
|
|
629
689
|
const root = rootOf(current.room_handle);
|
|
630
690
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
@@ -640,7 +700,7 @@ export class LocalCopies {
|
|
|
640
700
|
written.push({
|
|
641
701
|
path: `${root}/${path}`,
|
|
642
702
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
643
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
703
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
644
704
|
});
|
|
645
705
|
}
|
|
646
706
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -668,7 +728,7 @@ export class LocalCopies {
|
|
|
668
728
|
const push = copy.push;
|
|
669
729
|
let written = push.written;
|
|
670
730
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
671
|
-
copy =
|
|
731
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
672
732
|
const house = await tree(copy.repository, copy.house);
|
|
673
733
|
const sent = await tree(copy.repository, push.house);
|
|
674
734
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -684,7 +744,7 @@ export class LocalCopies {
|
|
|
684
744
|
{
|
|
685
745
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
686
746
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
687
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
747
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
688
748
|
},
|
|
689
749
|
];
|
|
690
750
|
}
|
|
@@ -729,7 +789,7 @@ export class LocalCopies {
|
|
|
729
789
|
async landed(repository, changes, house, selected) {
|
|
730
790
|
const text = async (path) => {
|
|
731
791
|
const entry = house.get(relative(path));
|
|
732
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
792
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
733
793
|
};
|
|
734
794
|
for (const change of changes) {
|
|
735
795
|
const current = await text(change.path);
|
package/dist/end.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
export function updateRequired() {
|
|
18
|
+
const command = installCommand();
|
|
19
|
+
end(command === null ? 'This Kit is too old for House; install it again.' : `This Kit is too old for House. Update it: ${command}`);
|
|
20
|
+
}
|
|
21
|
+
export function newerThanHouse(release, command) {
|
|
22
|
+
end(`Kit ${KIT_RELEASE} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|
|
23
|
+
}
|
|
24
|
+
export async function signInAgain(rejected) {
|
|
25
|
+
await forgetRejected(rejected);
|
|
26
|
+
restart('sign in again');
|
|
27
|
+
}
|
package/dist/enrol-main.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { writeEnrolment } from './home.js';
|
|
3
3
|
const [house, environment] = process.argv.slice(2);
|
|
4
|
-
|
|
4
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
5
|
+
await writeEnrolment({
|
|
6
|
+
house: house,
|
|
7
|
+
...(relay === undefined ? {} : { relay }),
|
|
8
|
+
environment: environment,
|
|
9
|
+
credential: readFileSync(0, 'utf8').trim(),
|
|
10
|
+
});
|