@agentshouse/kit 0.2.1-c063e835a50ea → 0.2.1-c0b0ffa6c025b
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 +27 -26
- package/dist/acp.js +16 -93
- package/dist/agents.js +138 -42
- package/dist/api.js +34 -2
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +33 -51
- package/dist/chatgpt.js +29 -0
- package/dist/clis.js +128 -65
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +298 -237
- package/dist/copies.js +165 -138
- package/dist/end.js +22 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +521 -0
- package/dist/files.js +21 -1
- package/dist/git.js +11 -8
- package/dist/home.js +81 -6
- package/dist/house-main.js +186 -56
- package/dist/instructions.js +9 -32
- package/dist/kit-main.js +54 -6
- package/dist/login.js +109 -103
- package/dist/logout.js +16 -13
- package/dist/parts.js +82 -0
- package/dist/placement.js +48 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +38 -27
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +4 -1
- package/dist/service-main.js +9 -0
- package/dist/service.js +33 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -9
- package/dist/skills.js +81 -12
- package/dist/stream.js +87 -11
- package/dist/transcription.js +57 -9
- package/dist/update.js +30 -6
- package/dist/wire.js +6 -0
- package/dist/work.js +348 -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,7 +11,6 @@ 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);
|
|
@@ -30,9 +29,29 @@ const REBASED = new Set([
|
|
|
30
29
|
function rebaseFixes(refusal) {
|
|
31
30
|
return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
|
|
32
31
|
}
|
|
32
|
+
function textOnly(paths) {
|
|
33
|
+
const [named, them] = paths.length === 1 ? [`${paths[0]} is`, 'it'] : [`${paths.join(', ')} are`, 'them'];
|
|
34
|
+
return `${named} not text, and house push sends text only; remove ${them} from the commit and place ${them} with house upload`;
|
|
35
|
+
}
|
|
33
36
|
function short(commit) {
|
|
34
37
|
return commit.slice(0, 12);
|
|
35
38
|
}
|
|
39
|
+
function moved(before, after) {
|
|
40
|
+
if (before.house !== after.house)
|
|
41
|
+
return [`${rootOf(after.room_handle)}: ${HOUSE_REF} is at ${short(after.house)}`];
|
|
42
|
+
return before.position === after.position ? [] : [`${rootOf(after.room_handle)}: read-only files refreshed`];
|
|
43
|
+
}
|
|
44
|
+
function expiredAuthority(error) {
|
|
45
|
+
if (!(error instanceof HouseRefusal))
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
const { error: refused } = JSON.parse(error.text);
|
|
49
|
+
return refused?.code === 'expired' && typeof refused.authority === 'string' ? refused.authority : undefined;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
36
55
|
function covers(path, prefix) {
|
|
37
56
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
38
57
|
}
|
|
@@ -41,11 +60,11 @@ function sidecar(path) {
|
|
|
41
60
|
segments.splice(-1, 0, PROVENANCE);
|
|
42
61
|
return segments.join('/');
|
|
43
62
|
}
|
|
44
|
-
function rootOf(
|
|
45
|
-
return
|
|
63
|
+
function rootOf(handle) {
|
|
64
|
+
return `/rooms/${handle}`;
|
|
46
65
|
}
|
|
47
66
|
function relative(path) {
|
|
48
|
-
return path.replace(/^\/
|
|
67
|
+
return path.replace(/^\/rooms\/[^/]+\//, '');
|
|
49
68
|
}
|
|
50
69
|
function declaredWritable(path, copy) {
|
|
51
70
|
const deepest = (scopes) => Math.max(-1, ...scopes.filter((scope) => covers(path, scope)).map((scope) => scope.length));
|
|
@@ -153,19 +172,18 @@ export class LocalCopies {
|
|
|
153
172
|
house;
|
|
154
173
|
copies = new Map();
|
|
155
174
|
queues = new Map();
|
|
156
|
-
|
|
157
|
-
selecting = Promise.resolve();
|
|
175
|
+
answering = Promise.resolve();
|
|
158
176
|
constructor(house) {
|
|
159
177
|
this.house = house;
|
|
160
178
|
this.owner = {
|
|
161
179
|
key: 'owner',
|
|
162
180
|
submit: async (operation, changes) => {
|
|
163
181
|
try {
|
|
164
|
-
const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
|
|
182
|
+
const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
|
|
165
183
|
return { accepted: outcome.contents };
|
|
166
184
|
}
|
|
167
185
|
catch (error) {
|
|
168
|
-
if (!(error
|
|
186
|
+
if (!finalRefusal(error))
|
|
169
187
|
throw error;
|
|
170
188
|
return {
|
|
171
189
|
refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
|
|
@@ -177,7 +195,7 @@ export class LocalCopies {
|
|
|
177
195
|
async load() {
|
|
178
196
|
const home = join(kitHome(), 'local-copies');
|
|
179
197
|
const names = (await readdir(join(home, 'rooms')).catch(() => [])).filter((name) => name.endsWith('.json'));
|
|
180
|
-
for (const path of
|
|
198
|
+
for (const path of names.map((name) => join(home, 'rooms', name))) {
|
|
181
199
|
const text = await readFile(path, 'utf8').catch(() => null);
|
|
182
200
|
if (text === null)
|
|
183
201
|
continue;
|
|
@@ -186,47 +204,68 @@ export class LocalCopies {
|
|
|
186
204
|
}
|
|
187
205
|
}
|
|
188
206
|
async mapping() {
|
|
189
|
-
await this.
|
|
207
|
+
await this.answering;
|
|
190
208
|
return [...this.copies.values()]
|
|
191
209
|
.filter((copy) => copy.active)
|
|
192
|
-
.map((copy) => `${rootOf(copy)}: ${copy.repository}`)
|
|
210
|
+
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
193
211
|
.sort();
|
|
194
212
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
async reselect() {
|
|
201
|
-
const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
|
|
202
|
-
const selected = [
|
|
203
|
-
...(chosen?.rooms ?? [])
|
|
204
|
-
.filter((room) => room.room_handle !== null)
|
|
205
|
-
.map((room) => ({ key: `rooms/${room.room_ref}`, private: false, handle: room.room_handle })),
|
|
206
|
-
...(chosen?.private === true ? [{ key: 'private', private: true, handle: null }] : []),
|
|
207
|
-
];
|
|
208
|
-
const keys = new Set(selected.map((scope) => scope.key));
|
|
209
|
-
const work = [];
|
|
210
|
-
for (const key of this.copies.keys()) {
|
|
211
|
-
if (keys.has(key))
|
|
212
|
-
continue;
|
|
213
|
-
work.push(this.exclusive(key, async () => {
|
|
214
|
-
const copy = this.copies.get(key);
|
|
215
|
-
if (copy.active)
|
|
216
|
-
await this.save({ ...copy, active: false });
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
219
|
-
for (const scope of selected)
|
|
220
|
-
work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
|
|
221
|
-
await Promise.all(work);
|
|
213
|
+
async connected() {
|
|
214
|
+
const copied = await this.answered(() => this.fetched(false));
|
|
215
|
+
if (copied.refused)
|
|
216
|
+
logged(copied.text);
|
|
222
217
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
fetch() {
|
|
219
|
+
return this.answered(() => this.fetched(true));
|
|
220
|
+
}
|
|
221
|
+
answered(work) {
|
|
222
|
+
const answered = this.answering.then(work);
|
|
223
|
+
this.answering = answered.catch(() => undefined);
|
|
224
|
+
return answered;
|
|
225
|
+
}
|
|
226
|
+
async fetched(refreshing) {
|
|
227
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
|
|
228
|
+
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
229
|
+
return this.holding([...new Set([...this.copies.keys(), ...synced.keys()])].sort(), async () => {
|
|
230
|
+
const said = [];
|
|
231
|
+
let refused = false;
|
|
232
|
+
for (const copy of this.copies.values()) {
|
|
233
|
+
if (synced.has(copy.key) || !copy.active)
|
|
234
|
+
continue;
|
|
235
|
+
await this.save({ ...copy, active: false });
|
|
236
|
+
said.push(`${rootOf(copy.room_handle)}: no longer synced to this Environment; its files stay`);
|
|
237
|
+
}
|
|
238
|
+
const current = [];
|
|
239
|
+
for (const [key, handle] of synced) {
|
|
240
|
+
try {
|
|
241
|
+
const held = this.copies.get(key);
|
|
242
|
+
if (held === undefined || !(await exists(held.repository))) {
|
|
243
|
+
said.push(await this.materialize(key, handle));
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const copy = held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle });
|
|
247
|
+
if (copy.active)
|
|
248
|
+
current.push(copy);
|
|
249
|
+
else
|
|
250
|
+
said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
refused = true;
|
|
254
|
+
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (refreshing && current.length > 0) {
|
|
258
|
+
try {
|
|
259
|
+
const refreshed = await this.refreshed(current);
|
|
260
|
+
current.forEach((copy, index) => said.push(...moved(copy, refreshed[index])));
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
refused = true;
|
|
264
|
+
said.push(error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
268
|
+
});
|
|
230
269
|
}
|
|
231
270
|
async observed(report) {
|
|
232
271
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -276,19 +315,12 @@ export class LocalCopies {
|
|
|
276
315
|
this.queues.set(key, queued.catch(() => undefined));
|
|
277
316
|
return queued;
|
|
278
317
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.delivering.add(key);
|
|
283
|
-
void this.exclusive(key, async () => {
|
|
284
|
-
this.delivering.delete(key);
|
|
285
|
-
const copy = this.copies.get(key);
|
|
286
|
-
if (copy.active)
|
|
287
|
-
await this.save(await this.caughtUp(copy));
|
|
288
|
-
}).catch(logged);
|
|
318
|
+
holding(keys, work) {
|
|
319
|
+
const [key, ...rest] = keys;
|
|
320
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
289
321
|
}
|
|
290
322
|
async save(copy) {
|
|
291
|
-
const path = join(kitHome(), 'local-copies',
|
|
323
|
+
const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
|
|
292
324
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
293
325
|
const temporary = `${path}.${process.pid}`;
|
|
294
326
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -296,8 +328,8 @@ export class LocalCopies {
|
|
|
296
328
|
this.copies.set(copy.key, copy);
|
|
297
329
|
return copy;
|
|
298
330
|
}
|
|
299
|
-
async read(
|
|
300
|
-
const prefixes = [rootOf(
|
|
331
|
+
async read(handle) {
|
|
332
|
+
const prefixes = [rootOf(handle)];
|
|
301
333
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
302
334
|
let bundle = await this.house.post('/kit/door/bootstrap', { prefixes });
|
|
303
335
|
if ('transfer' in bundle)
|
|
@@ -331,46 +363,13 @@ export class LocalCopies {
|
|
|
331
363
|
}
|
|
332
364
|
return read;
|
|
333
365
|
}
|
|
334
|
-
async
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
copy = await this.save({ ...copy, room_handle: scope.handle });
|
|
342
|
-
if (!copy.active) {
|
|
343
|
-
await this.save(await this.received({ ...copy, active: true }));
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const discovery = await this.house.post('/kit/door/discover', {
|
|
347
|
-
prefixes: [rootOf(copy)],
|
|
348
|
-
});
|
|
349
|
-
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
350
|
-
if (authority === undefined)
|
|
351
|
-
return;
|
|
352
|
-
if (authority.managed_replication !== 'admitted') {
|
|
353
|
-
logged(`operation_denied: ${rootOf(copy)} is not synchronized to this Environment`);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
|
|
357
|
-
JSON.stringify([copy.write, copy.protected])) {
|
|
358
|
-
await this.save(await this.received(copy));
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
await this.save(await this.caughtUp(copy));
|
|
362
|
-
}
|
|
363
|
-
async materialize(scope) {
|
|
364
|
-
const read = await this.read({ private: scope.private, room_handle: scope.handle });
|
|
365
|
-
if (read === null) {
|
|
366
|
-
logged(`House serves nothing of ${rootOf({ private: scope.private, room_handle: scope.handle })} to copy`);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const repository = scope.private ? join(localCopies(), 'private') : join(localCopies(), 'rooms', read.anchor);
|
|
370
|
-
if (await exists(repository)) {
|
|
371
|
-
logged(`${repository} already exists and House Kit leaves it as it is`);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
366
|
+
async materialize(key, handle) {
|
|
367
|
+
const read = await this.read(handle);
|
|
368
|
+
if (read === null)
|
|
369
|
+
throw new Error('House serves nothing of it to copy');
|
|
370
|
+
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
371
|
+
if (await exists(repository))
|
|
372
|
+
throw new Error(`${repository} already exists and House Kit leaves it as it is`);
|
|
374
373
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
375
374
|
await mkdir(staging, { recursive: true });
|
|
376
375
|
let house;
|
|
@@ -378,7 +377,7 @@ export class LocalCopies {
|
|
|
378
377
|
try {
|
|
379
378
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
380
379
|
house = await importCommits(staging, [
|
|
381
|
-
{ message: `House ${rootOf(
|
|
380
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
382
381
|
]);
|
|
383
382
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
384
383
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -391,10 +390,9 @@ export class LocalCopies {
|
|
|
391
390
|
throw error;
|
|
392
391
|
}
|
|
393
392
|
await this.save({
|
|
394
|
-
key
|
|
395
|
-
private: scope.private,
|
|
393
|
+
key,
|
|
396
394
|
room_ref: read.anchor,
|
|
397
|
-
room_handle:
|
|
395
|
+
room_handle: handle,
|
|
398
396
|
repository,
|
|
399
397
|
active: true,
|
|
400
398
|
house,
|
|
@@ -406,13 +404,14 @@ export class LocalCopies {
|
|
|
406
404
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
407
405
|
moves: [],
|
|
408
406
|
});
|
|
407
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
409
408
|
}
|
|
410
409
|
async received(copy) {
|
|
411
|
-
const read = await this.read(copy);
|
|
410
|
+
const read = await this.read(copy.room_handle);
|
|
412
411
|
if (read === null)
|
|
413
412
|
return copy;
|
|
414
413
|
const house = await importCommits(copy.repository, [
|
|
415
|
-
{ message: `House ${rootOf(copy)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
|
|
414
|
+
{ message: `House ${rootOf(copy.room_handle)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
|
|
416
415
|
]);
|
|
417
416
|
const removed = copy.read_only.filter((path) => !read.readOnly.has(path));
|
|
418
417
|
const readOnly = await readOnlyPlaced(copy.repository, copy.read_only, read.readOnly, removed);
|
|
@@ -429,30 +428,52 @@ export class LocalCopies {
|
|
|
429
428
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
430
429
|
return next;
|
|
431
430
|
}
|
|
432
|
-
async
|
|
433
|
-
|
|
434
|
-
|
|
431
|
+
async refreshed(copies) {
|
|
432
|
+
const discovery = await this.house.post('/kit/door/discover', {
|
|
433
|
+
prefixes: copies.map((copy) => rootOf(copy.room_handle)),
|
|
434
|
+
});
|
|
435
|
+
const regranted = new Map();
|
|
436
|
+
for (const copy of copies) {
|
|
437
|
+
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
438
|
+
if (authority !== undefined &&
|
|
439
|
+
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
440
|
+
regranted.set(copy.key, await this.save(await this.received(copy)));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const caught = await this.caughtUp(copies.filter((copy) => !regranted.has(copy.key)), false);
|
|
444
|
+
return copies.map((copy) => regranted.get(copy.key) ?? caught.find((held) => held.key === copy.key));
|
|
445
|
+
}
|
|
446
|
+
async caughtUp(copies, whole) {
|
|
447
|
+
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
448
|
+
let asking = [...copies];
|
|
449
|
+
while (asking.length > 0) {
|
|
435
450
|
let answer;
|
|
436
451
|
try {
|
|
437
452
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
438
|
-
prefixes:
|
|
439
|
-
positions:
|
|
453
|
+
prefixes: asking.map((copy) => rootOf(copy.room_handle)),
|
|
454
|
+
positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
|
|
440
455
|
});
|
|
441
456
|
}
|
|
442
457
|
catch (error) {
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
445
|
-
return this.received(copy);
|
|
446
|
-
if (code !== 'replica_behind')
|
|
458
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
459
|
+
if (expired === undefined)
|
|
447
460
|
throw error;
|
|
448
|
-
|
|
449
|
-
|
|
461
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
462
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const advanced = [];
|
|
466
|
+
for (const copy of asking) {
|
|
467
|
+
const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
|
|
468
|
+
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
469
|
+
continue;
|
|
470
|
+
const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
471
|
+
caught.set(copy.key, next);
|
|
472
|
+
advanced.push(next);
|
|
450
473
|
}
|
|
451
|
-
|
|
452
|
-
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
453
|
-
return copy;
|
|
454
|
-
copy = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
474
|
+
asking = whole ? advanced : [];
|
|
455
475
|
}
|
|
476
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
456
477
|
}
|
|
457
478
|
async incorporated(copy, changes, position, logEpoch) {
|
|
458
479
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -485,7 +506,7 @@ export class LocalCopies {
|
|
|
485
506
|
let { house, manifests } = copy;
|
|
486
507
|
if (files.size > 0) {
|
|
487
508
|
house = await importCommits(copy.repository, [
|
|
488
|
-
{ message: `House ${rootOf(copy)} at position ${position}`, from: copy.house, files },
|
|
509
|
+
{ message: `House ${rootOf(copy.room_handle)} at position ${position}`, from: copy.house, files },
|
|
489
510
|
]);
|
|
490
511
|
manifests = { ...manifests, [house]: { parent: copy.house, files: revisions } };
|
|
491
512
|
}
|
|
@@ -516,7 +537,7 @@ export class LocalCopies {
|
|
|
516
537
|
copy = await this.save(without(copy));
|
|
517
538
|
}
|
|
518
539
|
if (!copy.active) {
|
|
519
|
-
return { refused: true, text: `${rootOf(copy)} is no longer synced to this Environment, so House takes no push from it` };
|
|
540
|
+
return { refused: true, text: `${rootOf(copy.room_handle)} is no longer synced to this Environment, so House takes no push from it` };
|
|
520
541
|
}
|
|
521
542
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
522
543
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
@@ -550,12 +571,18 @@ export class LocalCopies {
|
|
|
550
571
|
return this.settle(submitted, caller);
|
|
551
572
|
}
|
|
552
573
|
async derived(copy, base, commit) {
|
|
553
|
-
const root = rootOf(copy);
|
|
574
|
+
const root = rootOf(copy.room_handle);
|
|
554
575
|
const before = await tree(copy.repository, base);
|
|
555
576
|
const after = await tree(copy.repository, commit);
|
|
556
577
|
const moved = movedPaths(copy.moves, before, after);
|
|
557
578
|
const arrived = new Set(moved.values());
|
|
558
|
-
const
|
|
579
|
+
const binary = [];
|
|
580
|
+
for (const [path, entry] of after)
|
|
581
|
+
if (before.get(path) !== entry && !(await isText(copy.repository, entry)))
|
|
582
|
+
binary.push(path);
|
|
583
|
+
if (binary.length > 0)
|
|
584
|
+
throw new Error(textOnly(binary));
|
|
585
|
+
const text = (entry) => blobText(copy.repository, entry);
|
|
559
586
|
const changes = [];
|
|
560
587
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
561
588
|
if (arrived.has(path))
|
|
@@ -568,7 +595,7 @@ export class LocalCopies {
|
|
|
568
595
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
569
596
|
const moving = after.get(to);
|
|
570
597
|
if (moving !== prior) {
|
|
571
|
-
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving
|
|
598
|
+
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
|
|
572
599
|
}
|
|
573
600
|
continue;
|
|
574
601
|
}
|
|
@@ -577,9 +604,9 @@ export class LocalCopies {
|
|
|
577
604
|
if (next === undefined)
|
|
578
605
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
579
606
|
else if (prior === undefined)
|
|
580
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
607
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
581
608
|
else
|
|
582
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
609
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
583
610
|
}
|
|
584
611
|
return changes;
|
|
585
612
|
}
|
|
@@ -606,12 +633,12 @@ export class LocalCopies {
|
|
|
606
633
|
const { upload, ...unstaged } = copy.push;
|
|
607
634
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
608
635
|
}
|
|
609
|
-
if (outcome.refused.code === '
|
|
636
|
+
if (outcome.refused.code === 'expired')
|
|
610
637
|
return this.recovered(copy);
|
|
611
638
|
const rebased = rebaseFixes(outcome.refused);
|
|
612
639
|
let settled = await this.save(without(copy));
|
|
613
640
|
try {
|
|
614
|
-
settled =
|
|
641
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
615
642
|
}
|
|
616
643
|
catch {
|
|
617
644
|
return {
|
|
@@ -630,9 +657,9 @@ export class LocalCopies {
|
|
|
630
657
|
}
|
|
631
658
|
async recovered(copy) {
|
|
632
659
|
const push = copy.push;
|
|
633
|
-
const current =
|
|
660
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
634
661
|
const house = await tree(copy.repository, current.house);
|
|
635
|
-
const root = rootOf(current);
|
|
662
|
+
const root = rootOf(current.room_handle);
|
|
636
663
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
637
664
|
await this.save(without(current));
|
|
638
665
|
return {
|
|
@@ -646,7 +673,7 @@ export class LocalCopies {
|
|
|
646
673
|
written.push({
|
|
647
674
|
path: `${root}/${path}`,
|
|
648
675
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
649
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
676
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
650
677
|
});
|
|
651
678
|
}
|
|
652
679
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -674,7 +701,7 @@ export class LocalCopies {
|
|
|
674
701
|
const push = copy.push;
|
|
675
702
|
let written = push.written;
|
|
676
703
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
677
|
-
copy =
|
|
704
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
678
705
|
const house = await tree(copy.repository, copy.house);
|
|
679
706
|
const sent = await tree(copy.repository, push.house);
|
|
680
707
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -688,9 +715,9 @@ export class LocalCopies {
|
|
|
688
715
|
written = [
|
|
689
716
|
...written,
|
|
690
717
|
{
|
|
691
|
-
path: `${rootOf(copy)}/${path}`,
|
|
718
|
+
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
692
719
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
693
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
720
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
694
721
|
},
|
|
695
722
|
];
|
|
696
723
|
}
|
|
@@ -708,7 +735,7 @@ export class LocalCopies {
|
|
|
708
735
|
}
|
|
709
736
|
const edits = new Map([...files].filter(([path, content]) => (content === null ? selected.has(path) : selected.get(path) !== blobId(content))));
|
|
710
737
|
const received = [...files.keys()].filter((path) => revisionAt(copy, copy.house, path) === revisionAt(copy, push.house, path));
|
|
711
|
-
const accepted = `House ${rootOf(copy)} with ${push.commit} accepted`;
|
|
738
|
+
const accepted = `House ${rootOf(copy.room_handle)} with ${push.commit} accepted`;
|
|
712
739
|
const house = await importCommits(copy.repository, [
|
|
713
740
|
...(edits.size === 0 ? [] : [{ message: accepted, from: push.commit, files: edits }]),
|
|
714
741
|
{
|
|
@@ -735,7 +762,7 @@ export class LocalCopies {
|
|
|
735
762
|
async landed(repository, changes, house, selected) {
|
|
736
763
|
const text = async (path) => {
|
|
737
764
|
const entry = house.get(relative(path));
|
|
738
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
765
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
739
766
|
};
|
|
740
767
|
for (const change of changes) {
|
|
741
768
|
const current = await text(change.path);
|
package/dist/end.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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(house) {
|
|
17
|
+
end(`This Kit is too old for House. Update it: ${installCommand(house)}`);
|
|
18
|
+
}
|
|
19
|
+
export async function signInAgain(rejected) {
|
|
20
|
+
await forgetRejected(rejected);
|
|
21
|
+
restart('sign in again');
|
|
22
|
+
}
|
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
|
+
});
|