@agentshouse/kit 0.2.1-alpha.9 → 0.2.1-c073f4e9391b2
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 +44 -26
- package/dist/abort.js +9 -0
- package/dist/acp.js +18 -75
- package/dist/agents.js +266 -53
- package/dist/api.js +40 -4
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +10 -14
- package/dist/bridge.js +89 -71
- package/dist/chatgpt.js +25 -0
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +203 -71
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +328 -199
- package/dist/copies.js +188 -161
- package/dist/end.js +22 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +521 -0
- package/dist/files.js +16 -1
- package/dist/git-main.js +2 -2
- package/dist/git.js +6 -2
- package/dist/home.js +72 -5
- package/dist/house-main.js +181 -57
- package/dist/instructions.js +16 -13
- package/dist/kit-main.js +55 -4
- package/dist/login.js +108 -97
- package/dist/logout.js +21 -0
- package/dist/mcp.js +39 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +82 -0
- package/dist/placement.js +48 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +20 -0
- package/dist/resident.js +70 -34
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +7 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +33 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +48 -11
- package/dist/skills.js +5 -10
- package/dist/stream.js +37 -7
- package/dist/transcription.js +170 -0
- package/dist/update.js +140 -0
- package/dist/wire.js +6 -0
- package/dist/work.js +348 -0
- package/how-we-work.md +10 -12
- package/package.json +4 -7
- package/dist/blocks.js +0 -51
package/dist/copies.js
CHANGED
|
@@ -2,15 +2,15 @@ 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, revision, run, succeeded, tree,
|
|
5
|
+
import { finalRefusal, HouseRefusal, retrying } from './api.js';
|
|
6
|
+
import { blobId, blobText, git, 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
|
+
import { refusalOf } from './refusals.js';
|
|
9
10
|
import { changed } from './translate.js';
|
|
11
|
+
import { relayed } from './relay.js';
|
|
10
12
|
const BRANCH = 'main';
|
|
11
|
-
const ROOM_HOMES = ['capture', 'library', 'collaboration'];
|
|
12
13
|
const PROVENANCE = '_provenance';
|
|
13
|
-
const REPLICA_RETRY_MS = 30_000;
|
|
14
14
|
const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
|
|
15
15
|
function causeOf(error) {
|
|
16
16
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -18,17 +18,36 @@ function causeOf(error) {
|
|
|
18
18
|
function logged(error) {
|
|
19
19
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
const REBASED = new Set([
|
|
22
|
+
'field_base_stale',
|
|
23
|
+
'section_base_stale',
|
|
24
|
+
'preamble_base_stale',
|
|
25
|
+
'base_revision_stale',
|
|
26
|
+
'path_exists',
|
|
27
|
+
'path_absent',
|
|
28
|
+
]);
|
|
29
|
+
function rebaseFixes(refusal) {
|
|
30
|
+
return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
|
|
31
|
+
}
|
|
32
|
+
function short(commit) {
|
|
33
|
+
return commit.slice(0, 12);
|
|
34
|
+
}
|
|
35
|
+
function moved(before, after) {
|
|
36
|
+
if (before.house !== after.house)
|
|
37
|
+
return [`${rootOf(after.room_handle)}: ${HOUSE_REF} is at ${short(after.house)}`];
|
|
38
|
+
return before.position === after.position ? [] : [`${rootOf(after.room_handle)}: read-only files refreshed`];
|
|
39
|
+
}
|
|
40
|
+
function expiredAuthority(error) {
|
|
41
|
+
if (!(error instanceof HouseRefusal))
|
|
42
|
+
return undefined;
|
|
22
43
|
try {
|
|
23
|
-
|
|
44
|
+
const { error: refused } = JSON.parse(error.text);
|
|
45
|
+
return refused?.code === 'expired' && typeof refused.authority === 'string' ? refused.authority : undefined;
|
|
24
46
|
}
|
|
25
47
|
catch {
|
|
26
|
-
return
|
|
48
|
+
return undefined;
|
|
27
49
|
}
|
|
28
50
|
}
|
|
29
|
-
function short(commit) {
|
|
30
|
-
return commit.slice(0, 12);
|
|
31
|
-
}
|
|
32
51
|
function covers(path, prefix) {
|
|
33
52
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
34
53
|
}
|
|
@@ -37,14 +56,11 @@ function sidecar(path) {
|
|
|
37
56
|
segments.splice(-1, 0, PROVENANCE);
|
|
38
57
|
return segments.join('/');
|
|
39
58
|
}
|
|
40
|
-
function rootOf(
|
|
41
|
-
return
|
|
59
|
+
function rootOf(handle) {
|
|
60
|
+
return `/rooms/${handle}`;
|
|
42
61
|
}
|
|
43
62
|
function relative(path) {
|
|
44
|
-
return path.replace(/^\/
|
|
45
|
-
}
|
|
46
|
-
function prefixesOf(selected) {
|
|
47
|
-
return selected.private ? ['/private'] : ROOM_HOMES.map((home) => `${rootOf(selected)}/${home}`);
|
|
63
|
+
return path.replace(/^\/rooms\/[^/]+\//, '');
|
|
48
64
|
}
|
|
49
65
|
function declaredWritable(path, copy) {
|
|
50
66
|
const deepest = (scopes) => Math.max(-1, ...scopes.filter((scope) => covers(path, scope)).map((scope) => scope.length));
|
|
@@ -147,34 +163,35 @@ function movedPaths(moves, before, after) {
|
|
|
147
163
|
.filter((pair) => pairs.filter((other) => other.from === pair.from || other.to === pair.to).length === 1)
|
|
148
164
|
.map(({ from, to }) => [from, to]));
|
|
149
165
|
}
|
|
150
|
-
export class
|
|
166
|
+
export class LocalCopies {
|
|
151
167
|
owner;
|
|
152
168
|
house;
|
|
153
169
|
copies = new Map();
|
|
154
170
|
queues = new Map();
|
|
155
|
-
|
|
156
|
-
selecting = Promise.resolve();
|
|
171
|
+
answering = Promise.resolve();
|
|
157
172
|
constructor(house) {
|
|
158
173
|
this.house = house;
|
|
159
174
|
this.owner = {
|
|
160
175
|
key: 'owner',
|
|
161
176
|
submit: async (operation, changes) => {
|
|
162
177
|
try {
|
|
163
|
-
const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
|
|
178
|
+
const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
|
|
164
179
|
return { accepted: outcome.contents };
|
|
165
180
|
}
|
|
166
181
|
catch (error) {
|
|
167
|
-
if (!(error
|
|
182
|
+
if (!finalRefusal(error))
|
|
168
183
|
throw error;
|
|
169
|
-
return {
|
|
184
|
+
return {
|
|
185
|
+
refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
|
|
186
|
+
};
|
|
170
187
|
}
|
|
171
188
|
},
|
|
172
189
|
};
|
|
173
190
|
}
|
|
174
191
|
async load() {
|
|
175
|
-
const home = join(kitHome(), '
|
|
192
|
+
const home = join(kitHome(), 'local-copies');
|
|
176
193
|
const names = (await readdir(join(home, 'rooms')).catch(() => [])).filter((name) => name.endsWith('.json'));
|
|
177
|
-
for (const path of
|
|
194
|
+
for (const path of names.map((name) => join(home, 'rooms', name))) {
|
|
178
195
|
const text = await readFile(path, 'utf8').catch(() => null);
|
|
179
196
|
if (text === null)
|
|
180
197
|
continue;
|
|
@@ -183,47 +200,68 @@ export class WorkingCopies {
|
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
202
|
async mapping() {
|
|
186
|
-
await this.
|
|
203
|
+
await this.answering;
|
|
187
204
|
return [...this.copies.values()]
|
|
188
205
|
.filter((copy) => copy.active)
|
|
189
|
-
.map((copy) => `${rootOf(copy)}: ${copy.repository}`)
|
|
206
|
+
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
190
207
|
.sort();
|
|
191
208
|
}
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (copy.active)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
209
|
+
async connected() {
|
|
210
|
+
const copied = await this.answered(() => this.fetched(false));
|
|
211
|
+
if (copied.refused)
|
|
212
|
+
logged(copied.text);
|
|
213
|
+
}
|
|
214
|
+
fetch() {
|
|
215
|
+
return this.answered(() => this.fetched(true));
|
|
216
|
+
}
|
|
217
|
+
answered(work) {
|
|
218
|
+
const answered = this.answering.then(work);
|
|
219
|
+
this.answering = answered.catch(() => undefined);
|
|
220
|
+
return answered;
|
|
221
|
+
}
|
|
222
|
+
async fetched(refreshing) {
|
|
223
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
|
|
224
|
+
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
225
|
+
return this.holding([...new Set([...this.copies.keys(), ...synced.keys()])].sort(), async () => {
|
|
226
|
+
const said = [];
|
|
227
|
+
let refused = false;
|
|
228
|
+
for (const copy of this.copies.values()) {
|
|
229
|
+
if (synced.has(copy.key) || !copy.active)
|
|
230
|
+
continue;
|
|
231
|
+
await this.save({ ...copy, active: false });
|
|
232
|
+
said.push(`${rootOf(copy.room_handle)}: no longer synced to this Environment; its files stay`);
|
|
233
|
+
}
|
|
234
|
+
const current = [];
|
|
235
|
+
for (const [key, handle] of synced) {
|
|
236
|
+
try {
|
|
237
|
+
const held = this.copies.get(key);
|
|
238
|
+
if (held === undefined || !(await exists(held.repository))) {
|
|
239
|
+
said.push(await this.materialize(key, handle));
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const copy = held.room_handle === handle ? held : await this.save({ ...held, room_handle: handle });
|
|
243
|
+
if (copy.active)
|
|
244
|
+
current.push(copy);
|
|
245
|
+
else
|
|
246
|
+
said.push(...moved(copy, await this.save(await this.received({ ...copy, active: true }))));
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
refused = true;
|
|
250
|
+
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (refreshing && current.length > 0) {
|
|
254
|
+
try {
|
|
255
|
+
const refreshed = await this.refreshed(current);
|
|
256
|
+
current.forEach((copy, index) => said.push(...moved(copy, refreshed[index])));
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
refused = true;
|
|
260
|
+
said.push(error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
264
|
+
});
|
|
227
265
|
}
|
|
228
266
|
async observed(report) {
|
|
229
267
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -253,11 +291,11 @@ export class WorkingCopies {
|
|
|
253
291
|
async push(cwd, selector, caller) {
|
|
254
292
|
const located = await run(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
255
293
|
if (located.status !== 0)
|
|
256
|
-
return { refused: true, text: `${cwd} is not
|
|
294
|
+
return { refused: true, text: `${cwd} is not a House Local copy; run house push inside one` };
|
|
257
295
|
const [common, worktree] = located.stdout.toString('utf8').split('\n');
|
|
258
296
|
const key = await this.keyAt(dirname(common));
|
|
259
297
|
if (key === undefined)
|
|
260
|
-
return { refused: true, text: `${worktree} is not a House
|
|
298
|
+
return { refused: true, text: `${worktree} is not a House Local copy; run house push inside one` };
|
|
261
299
|
return this.exclusive(key, () => this.pushed(key, worktree, selector, caller));
|
|
262
300
|
}
|
|
263
301
|
async keyAt(repository) {
|
|
@@ -273,19 +311,12 @@ export class WorkingCopies {
|
|
|
273
311
|
this.queues.set(key, queued.catch(() => undefined));
|
|
274
312
|
return queued;
|
|
275
313
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.delivering.add(key);
|
|
280
|
-
void this.exclusive(key, async () => {
|
|
281
|
-
this.delivering.delete(key);
|
|
282
|
-
const copy = this.copies.get(key);
|
|
283
|
-
if (copy.active)
|
|
284
|
-
await this.save(await this.caughtUp(copy));
|
|
285
|
-
}).catch(logged);
|
|
314
|
+
holding(keys, work) {
|
|
315
|
+
const [key, ...rest] = keys;
|
|
316
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
286
317
|
}
|
|
287
318
|
async save(copy) {
|
|
288
|
-
const path = join(kitHome(), '
|
|
319
|
+
const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
|
|
289
320
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
290
321
|
const temporary = `${path}.${process.pid}`;
|
|
291
322
|
await writeFile(temporary, JSON.stringify(copy), { mode: 0o600 });
|
|
@@ -293,12 +324,12 @@ export class WorkingCopies {
|
|
|
293
324
|
this.copies.set(copy.key, copy);
|
|
294
325
|
return copy;
|
|
295
326
|
}
|
|
296
|
-
async read(
|
|
297
|
-
const prefixes =
|
|
327
|
+
async read(handle) {
|
|
328
|
+
const prefixes = [rootOf(handle)];
|
|
298
329
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
299
330
|
let bundle = await this.house.post('/kit/door/bootstrap', { prefixes });
|
|
300
331
|
if ('transfer' in bundle)
|
|
301
|
-
bundle = (await (await fetch(bundle.transfer.url)).json());
|
|
332
|
+
bundle = (await (await fetch(relayed(bundle.transfer.url))).json());
|
|
302
333
|
const [view] = bundle.scopes;
|
|
303
334
|
if (view === undefined)
|
|
304
335
|
return null;
|
|
@@ -328,46 +359,13 @@ export class WorkingCopies {
|
|
|
328
359
|
}
|
|
329
360
|
return read;
|
|
330
361
|
}
|
|
331
|
-
async
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
copy = await this.save({ ...copy, room_handle: scope.handle });
|
|
339
|
-
if (!copy.active) {
|
|
340
|
-
await this.save(await this.received({ ...copy, active: true }));
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
const discovery = await this.house.post('/kit/door/discover', {
|
|
344
|
-
prefixes: prefixesOf(copy),
|
|
345
|
-
});
|
|
346
|
-
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
347
|
-
if (authority === undefined)
|
|
348
|
-
return;
|
|
349
|
-
if (authority.managed_replication !== 'admitted') {
|
|
350
|
-
logged(`operation_denied: ${rootOf(copy)} is not synchronized to this Environment`);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
|
|
354
|
-
JSON.stringify([copy.write, copy.protected])) {
|
|
355
|
-
await this.save(await this.received(copy));
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
await this.save(await this.caughtUp(copy));
|
|
359
|
-
}
|
|
360
|
-
async materialize(scope) {
|
|
361
|
-
const read = await this.read({ private: scope.private, room_handle: scope.handle });
|
|
362
|
-
if (read === null) {
|
|
363
|
-
logged(`House serves nothing of ${rootOf({ private: scope.private, room_handle: scope.handle })} to copy`);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const repository = scope.private ? join(WORKING_COPIES, 'private') : join(WORKING_COPIES, 'rooms', read.anchor);
|
|
367
|
-
if (await exists(repository)) {
|
|
368
|
-
logged(`${repository} already exists and House Kit leaves it as it is`);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
362
|
+
async materialize(key, handle) {
|
|
363
|
+
const read = await this.read(handle);
|
|
364
|
+
if (read === null)
|
|
365
|
+
throw new Error('House serves nothing of it to copy');
|
|
366
|
+
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
367
|
+
if (await exists(repository))
|
|
368
|
+
throw new Error(`${repository} already exists and House Kit leaves it as it is`);
|
|
371
369
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
372
370
|
await mkdir(staging, { recursive: true });
|
|
373
371
|
let house;
|
|
@@ -375,7 +373,7 @@ export class WorkingCopies {
|
|
|
375
373
|
try {
|
|
376
374
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
377
375
|
house = await importCommits(staging, [
|
|
378
|
-
{ message: `House ${rootOf(
|
|
376
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
379
377
|
]);
|
|
380
378
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
381
379
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -388,10 +386,9 @@ export class WorkingCopies {
|
|
|
388
386
|
throw error;
|
|
389
387
|
}
|
|
390
388
|
await this.save({
|
|
391
|
-
key
|
|
392
|
-
private: scope.private,
|
|
389
|
+
key,
|
|
393
390
|
room_ref: read.anchor,
|
|
394
|
-
room_handle:
|
|
391
|
+
room_handle: handle,
|
|
395
392
|
repository,
|
|
396
393
|
active: true,
|
|
397
394
|
house,
|
|
@@ -403,13 +400,14 @@ export class WorkingCopies {
|
|
|
403
400
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
404
401
|
moves: [],
|
|
405
402
|
});
|
|
403
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
406
404
|
}
|
|
407
405
|
async received(copy) {
|
|
408
|
-
const read = await this.read(copy);
|
|
406
|
+
const read = await this.read(copy.room_handle);
|
|
409
407
|
if (read === null)
|
|
410
408
|
return copy;
|
|
411
409
|
const house = await importCommits(copy.repository, [
|
|
412
|
-
{ message: `House ${rootOf(copy)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
|
|
410
|
+
{ message: `House ${rootOf(copy.room_handle)} at position ${read.position}`, from: copy.house, replaces: true, files: read.writable },
|
|
413
411
|
]);
|
|
414
412
|
const removed = copy.read_only.filter((path) => !read.readOnly.has(path));
|
|
415
413
|
const readOnly = await readOnlyPlaced(copy.repository, copy.read_only, read.readOnly, removed);
|
|
@@ -426,30 +424,52 @@ export class WorkingCopies {
|
|
|
426
424
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
427
425
|
return next;
|
|
428
426
|
}
|
|
429
|
-
async
|
|
430
|
-
|
|
431
|
-
|
|
427
|
+
async refreshed(copies) {
|
|
428
|
+
const discovery = await this.house.post('/kit/door/discover', {
|
|
429
|
+
prefixes: copies.map((copy) => rootOf(copy.room_handle)),
|
|
430
|
+
});
|
|
431
|
+
const regranted = new Map();
|
|
432
|
+
for (const copy of copies) {
|
|
433
|
+
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
434
|
+
if (authority !== undefined &&
|
|
435
|
+
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
436
|
+
regranted.set(copy.key, await this.save(await this.received(copy)));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const caught = await this.caughtUp(copies.filter((copy) => !regranted.has(copy.key)), false);
|
|
440
|
+
return copies.map((copy) => regranted.get(copy.key) ?? caught.find((held) => held.key === copy.key));
|
|
441
|
+
}
|
|
442
|
+
async caughtUp(copies, whole) {
|
|
443
|
+
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
444
|
+
let asking = [...copies];
|
|
445
|
+
while (asking.length > 0) {
|
|
432
446
|
let answer;
|
|
433
447
|
try {
|
|
434
448
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
435
|
-
prefixes:
|
|
436
|
-
positions:
|
|
449
|
+
prefixes: asking.map((copy) => rootOf(copy.room_handle)),
|
|
450
|
+
positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
|
|
437
451
|
});
|
|
438
452
|
}
|
|
439
453
|
catch (error) {
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
442
|
-
return this.received(copy);
|
|
443
|
-
if (code !== 'replica_behind')
|
|
454
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
455
|
+
if (expired === undefined)
|
|
444
456
|
throw error;
|
|
445
|
-
|
|
446
|
-
|
|
457
|
+
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
458
|
+
asking = asking.filter((copy) => copy !== expired);
|
|
459
|
+
continue;
|
|
447
460
|
}
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
461
|
+
const advanced = [];
|
|
462
|
+
for (const copy of asking) {
|
|
463
|
+
const view = answer.authorities.find((entry) => entry.authority === copy.room_ref);
|
|
464
|
+
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
465
|
+
continue;
|
|
466
|
+
const next = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
467
|
+
caught.set(copy.key, next);
|
|
468
|
+
advanced.push(next);
|
|
469
|
+
}
|
|
470
|
+
asking = whole ? advanced : [];
|
|
452
471
|
}
|
|
472
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
453
473
|
}
|
|
454
474
|
async incorporated(copy, changes, position, logEpoch) {
|
|
455
475
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -482,7 +502,7 @@ export class WorkingCopies {
|
|
|
482
502
|
let { house, manifests } = copy;
|
|
483
503
|
if (files.size > 0) {
|
|
484
504
|
house = await importCommits(copy.repository, [
|
|
485
|
-
{ message: `House ${rootOf(copy)} at position ${position}`, from: copy.house, files },
|
|
505
|
+
{ message: `House ${rootOf(copy.room_handle)} at position ${position}`, from: copy.house, files },
|
|
486
506
|
]);
|
|
487
507
|
manifests = { ...manifests, [house]: { parent: copy.house, files: revisions } };
|
|
488
508
|
}
|
|
@@ -512,8 +532,9 @@ export class WorkingCopies {
|
|
|
512
532
|
}
|
|
513
533
|
copy = await this.save(without(copy));
|
|
514
534
|
}
|
|
515
|
-
if (!copy.active)
|
|
516
|
-
return { refused: true, text: `${rootOf(copy)} is no longer
|
|
535
|
+
if (!copy.active) {
|
|
536
|
+
return { refused: true, text: `${rootOf(copy.room_handle)} is no longer synced to this Environment, so House takes no push from it` };
|
|
537
|
+
}
|
|
517
538
|
const listed = (await git(copy.repository, ['rev-list', '--topo-order', commit])).split('\n');
|
|
518
539
|
const base = listed.find((ancestor) => ancestor in copy.manifests);
|
|
519
540
|
if (base === undefined) {
|
|
@@ -546,7 +567,7 @@ export class WorkingCopies {
|
|
|
546
567
|
return this.settle(submitted, caller);
|
|
547
568
|
}
|
|
548
569
|
async derived(copy, base, commit) {
|
|
549
|
-
const root = rootOf(copy);
|
|
570
|
+
const root = rootOf(copy.room_handle);
|
|
550
571
|
const before = await tree(copy.repository, base);
|
|
551
572
|
const after = await tree(copy.repository, commit);
|
|
552
573
|
const moved = movedPaths(copy.moves, before, after);
|
|
@@ -582,9 +603,8 @@ export class WorkingCopies {
|
|
|
582
603
|
async settle(start, caller, restaged = false) {
|
|
583
604
|
let copy = start;
|
|
584
605
|
const push = copy.push;
|
|
585
|
-
if (push.caller !== caller.key)
|
|
586
|
-
return this.recovered(copy
|
|
587
|
-
}
|
|
606
|
+
if (push.caller !== caller.key)
|
|
607
|
+
return this.recovered(copy);
|
|
588
608
|
let outcome;
|
|
589
609
|
try {
|
|
590
610
|
outcome = await caller.submit(push.operation_id, push.changes, push.upload, async (upload) => {
|
|
@@ -592,9 +612,10 @@ export class WorkingCopies {
|
|
|
592
612
|
});
|
|
593
613
|
}
|
|
594
614
|
catch (error) {
|
|
615
|
+
const refusal = error instanceof HouseRefusal ? refusalOf(error.text) : null;
|
|
595
616
|
return {
|
|
596
617
|
refused: true,
|
|
597
|
-
text: `House's answer to ${short(push.commit)} did not arrive
|
|
618
|
+
text: refusal?.text ?? `House's answer to ${short(push.commit)} did not arrive; run house push again`,
|
|
598
619
|
};
|
|
599
620
|
}
|
|
600
621
|
if ('refused' in outcome) {
|
|
@@ -602,32 +623,38 @@ export class WorkingCopies {
|
|
|
602
623
|
const { upload, ...unstaged } = copy.push;
|
|
603
624
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
604
625
|
}
|
|
605
|
-
if (outcome.refused.code === '
|
|
606
|
-
return this.recovered(copy
|
|
626
|
+
if (outcome.refused.code === 'expired')
|
|
627
|
+
return this.recovered(copy);
|
|
628
|
+
const rebased = rebaseFixes(outcome.refused);
|
|
607
629
|
let settled = await this.save(without(copy));
|
|
608
630
|
try {
|
|
609
|
-
settled =
|
|
631
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
610
632
|
}
|
|
611
|
-
catch
|
|
612
|
-
return {
|
|
633
|
+
catch {
|
|
634
|
+
return {
|
|
635
|
+
refused: true,
|
|
636
|
+
text: rebased ? `${outcome.refused.text}\nHouse's current state did not arrive; push again later` : outcome.refused.text,
|
|
637
|
+
};
|
|
613
638
|
}
|
|
614
639
|
return {
|
|
615
640
|
refused: true,
|
|
616
|
-
text:
|
|
641
|
+
text: rebased
|
|
642
|
+
? `${outcome.refused.text}\n${HOUSE_REF} holds House's current state; rebase onto it and push again`
|
|
643
|
+
: outcome.refused.text,
|
|
617
644
|
};
|
|
618
645
|
}
|
|
619
646
|
return this.finished(await this.save({ ...copy, push: { ...copy.push, state: 'accepted', written: outcome.accepted } }));
|
|
620
647
|
}
|
|
621
|
-
async recovered(copy
|
|
648
|
+
async recovered(copy) {
|
|
622
649
|
const push = copy.push;
|
|
623
|
-
const current =
|
|
650
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
624
651
|
const house = await tree(copy.repository, current.house);
|
|
625
|
-
const root = rootOf(current);
|
|
652
|
+
const root = rootOf(current.room_handle);
|
|
626
653
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
627
654
|
await this.save(without(current));
|
|
628
655
|
return {
|
|
629
656
|
refused: true,
|
|
630
|
-
text:
|
|
657
|
+
text: `House does not hold ${short(push.commit)}; run house push again`,
|
|
631
658
|
};
|
|
632
659
|
}
|
|
633
660
|
const written = [];
|
|
@@ -652,10 +679,10 @@ export class WorkingCopies {
|
|
|
652
679
|
await this.save(without(copy));
|
|
653
680
|
return { refused: false, text: integrated.text };
|
|
654
681
|
}
|
|
655
|
-
catch
|
|
682
|
+
catch {
|
|
656
683
|
return {
|
|
657
684
|
refused: false,
|
|
658
|
-
text: `House accepted ${short(commit)};
|
|
685
|
+
text: `House accepted ${short(commit)}; run house push again to finish integrating it here`,
|
|
659
686
|
};
|
|
660
687
|
}
|
|
661
688
|
}
|
|
@@ -664,7 +691,7 @@ export class WorkingCopies {
|
|
|
664
691
|
const push = copy.push;
|
|
665
692
|
let written = push.written;
|
|
666
693
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
667
|
-
copy =
|
|
694
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
668
695
|
const house = await tree(copy.repository, copy.house);
|
|
669
696
|
const sent = await tree(copy.repository, push.house);
|
|
670
697
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -678,7 +705,7 @@ export class WorkingCopies {
|
|
|
678
705
|
written = [
|
|
679
706
|
...written,
|
|
680
707
|
{
|
|
681
|
-
path: `${rootOf(copy)}/${path}`,
|
|
708
|
+
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
682
709
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
683
710
|
content: entry === undefined ? null : await blobText(copy.repository, entry, path),
|
|
684
711
|
},
|
|
@@ -698,7 +725,7 @@ export class WorkingCopies {
|
|
|
698
725
|
}
|
|
699
726
|
const edits = new Map([...files].filter(([path, content]) => (content === null ? selected.has(path) : selected.get(path) !== blobId(content))));
|
|
700
727
|
const received = [...files.keys()].filter((path) => revisionAt(copy, copy.house, path) === revisionAt(copy, push.house, path));
|
|
701
|
-
const accepted = `House ${rootOf(copy)} with ${push.commit} accepted`;
|
|
728
|
+
const accepted = `House ${rootOf(copy.room_handle)} with ${push.commit} accepted`;
|
|
702
729
|
const house = await importCommits(copy.repository, [
|
|
703
730
|
...(edits.size === 0 ? [] : [{ message: accepted, from: push.commit, files: edits }]),
|
|
704
731
|
{
|
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
|
+
});
|