@agentshouse/kit 0.2.1-c3cf4b145dcda → 0.2.1-c3db1f18aed8f
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 +24 -23
- package/dist/acp.js +7 -97
- package/dist/agents.js +27 -6
- package/dist/api.js +9 -5
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +11 -37
- package/dist/chatgpt.js +26 -0
- package/dist/clis.js +86 -14
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +56 -55
- package/dist/copies.js +145 -110
- package/dist/end.js +18 -4
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +10 -76
- package/dist/files.js +7 -1
- package/dist/git.js +11 -8
- package/dist/home.js +78 -6
- package/dist/house-main.js +183 -56
- package/dist/instructions.js +9 -32
- package/dist/kit-main.js +53 -8
- package/dist/login.js +93 -107
- package/dist/logout.js +14 -14
- package/dist/parts.js +34 -16
- package/dist/placement.js +48 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +35 -24
- package/dist/scope.js +203 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +33 -0
- package/dist/sign-in.js +1 -1
- package/dist/skills.js +80 -12
- package/dist/stream.js +9 -4
- package/dist/transcription.js +3 -3
- package/dist/update.js +28 -6
- package/dist/wire.js +6 -0
- package/dist/work.js +348 -0
- package/how-we-work.md +11 -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/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
|
@@ -3,7 +3,7 @@ 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
5
|
import { finalRefusal, HouseRefusal, retrying } from './api.js';
|
|
6
|
-
import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.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';
|
|
@@ -29,9 +29,29 @@ const REBASED = new Set([
|
|
|
29
29
|
function rebaseFixes(refusal) {
|
|
30
30
|
return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
|
|
31
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
|
+
}
|
|
32
36
|
function short(commit) {
|
|
33
37
|
return commit.slice(0, 12);
|
|
34
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
|
+
}
|
|
35
55
|
function covers(path, prefix) {
|
|
36
56
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
37
57
|
}
|
|
@@ -152,8 +172,7 @@ export class LocalCopies {
|
|
|
152
172
|
house;
|
|
153
173
|
copies = new Map();
|
|
154
174
|
queues = new Map();
|
|
155
|
-
|
|
156
|
-
selecting = Promise.resolve();
|
|
175
|
+
answering = Promise.resolve();
|
|
157
176
|
constructor(house) {
|
|
158
177
|
this.house = house;
|
|
159
178
|
this.owner = {
|
|
@@ -185,42 +204,68 @@ export class LocalCopies {
|
|
|
185
204
|
}
|
|
186
205
|
}
|
|
187
206
|
async mapping() {
|
|
188
|
-
await this.
|
|
207
|
+
await this.answering;
|
|
189
208
|
return [...this.copies.values()]
|
|
190
209
|
.filter((copy) => copy.active)
|
|
191
210
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
192
211
|
.sort();
|
|
193
212
|
}
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
async reselect() {
|
|
200
|
-
const { local_copy: chosen } = await this.house.post('/kit/local-copy/selection', {});
|
|
201
|
-
const selected = (chosen?.rooms ?? []).flatMap((room) => room.room_handle === null ? [] : [{ key: `rooms/${room.room_ref}`, handle: room.room_handle }]);
|
|
202
|
-
const keys = new Set(selected.map((scope) => scope.key));
|
|
203
|
-
const work = [];
|
|
204
|
-
for (const key of this.copies.keys()) {
|
|
205
|
-
if (keys.has(key))
|
|
206
|
-
continue;
|
|
207
|
-
work.push(this.exclusive(key, async () => {
|
|
208
|
-
const copy = this.copies.get(key);
|
|
209
|
-
if (copy.active)
|
|
210
|
-
await this.save({ ...copy, active: false });
|
|
211
|
-
}));
|
|
212
|
-
}
|
|
213
|
-
for (const scope of selected)
|
|
214
|
-
work.push(this.exclusive(scope.key, () => this.apply(scope)).catch(logged));
|
|
215
|
-
await Promise.all(work);
|
|
213
|
+
async connected() {
|
|
214
|
+
const copied = await this.answered(() => this.fetched(false));
|
|
215
|
+
if (copied.refused)
|
|
216
|
+
logged(copied.text);
|
|
216
217
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
+
});
|
|
224
269
|
}
|
|
225
270
|
async observed(report) {
|
|
226
271
|
const key = await this.keyAt(dirname(report.common));
|
|
@@ -270,16 +315,9 @@ export class LocalCopies {
|
|
|
270
315
|
this.queues.set(key, queued.catch(() => undefined));
|
|
271
316
|
return queued;
|
|
272
317
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
this.delivering.add(key);
|
|
277
|
-
void this.exclusive(key, async () => {
|
|
278
|
-
this.delivering.delete(key);
|
|
279
|
-
const copy = this.copies.get(key);
|
|
280
|
-
if (copy.active)
|
|
281
|
-
await this.save(await this.caughtUp(copy));
|
|
282
|
-
}).catch(logged);
|
|
318
|
+
holding(keys, work) {
|
|
319
|
+
const [key, ...rest] = keys;
|
|
320
|
+
return key === undefined ? work() : this.exclusive(key, () => this.holding(rest, work));
|
|
283
321
|
}
|
|
284
322
|
async save(copy) {
|
|
285
323
|
const path = join(kitHome(), 'local-copies', `${copy.key}.json`);
|
|
@@ -325,46 +363,13 @@ export class LocalCopies {
|
|
|
325
363
|
}
|
|
326
364
|
return read;
|
|
327
365
|
}
|
|
328
|
-
async
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (copy.room_handle !== scope.handle)
|
|
335
|
-
copy = await this.save({ ...copy, room_handle: scope.handle });
|
|
336
|
-
if (!copy.active) {
|
|
337
|
-
await this.save(await this.received({ ...copy, active: true }));
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const discovery = await this.house.post('/kit/door/discover', {
|
|
341
|
-
prefixes: [rootOf(copy.room_handle)],
|
|
342
|
-
});
|
|
343
|
-
const authority = discovery.authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
344
|
-
if (authority === undefined)
|
|
345
|
-
return;
|
|
346
|
-
if (authority.managed_replication !== 'admitted') {
|
|
347
|
-
logged(`operation_denied: ${rootOf(copy.room_handle)} is not synchronized to this Environment`);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
if (JSON.stringify([authority.operations.write, authority.protected_paths]) !==
|
|
351
|
-
JSON.stringify([copy.write, copy.protected])) {
|
|
352
|
-
await this.save(await this.received(copy));
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
await this.save(await this.caughtUp(copy));
|
|
356
|
-
}
|
|
357
|
-
async materialize(scope) {
|
|
358
|
-
const read = await this.read(scope.handle);
|
|
359
|
-
if (read === null) {
|
|
360
|
-
logged(`House serves nothing of ${rootOf(scope.handle)} to copy`);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
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');
|
|
363
370
|
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
364
|
-
if (await exists(repository))
|
|
365
|
-
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
371
|
+
if (await exists(repository))
|
|
372
|
+
throw new Error(`${repository} already exists and House Kit leaves it as it is`);
|
|
368
373
|
const staging = join(dirname(repository), `.${randomUUID()}`);
|
|
369
374
|
await mkdir(staging, { recursive: true });
|
|
370
375
|
let house;
|
|
@@ -372,7 +377,7 @@ export class LocalCopies {
|
|
|
372
377
|
try {
|
|
373
378
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
374
379
|
house = await importCommits(staging, [
|
|
375
|
-
{ message: `House ${rootOf(
|
|
380
|
+
{ message: `House ${rootOf(handle)}`, from: null, files: read.writable },
|
|
376
381
|
]);
|
|
377
382
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
378
383
|
await git(staging, ['reset', '-q', '--hard']);
|
|
@@ -385,9 +390,9 @@ export class LocalCopies {
|
|
|
385
390
|
throw error;
|
|
386
391
|
}
|
|
387
392
|
await this.save({
|
|
388
|
-
key
|
|
393
|
+
key,
|
|
389
394
|
room_ref: read.anchor,
|
|
390
|
-
room_handle:
|
|
395
|
+
room_handle: handle,
|
|
391
396
|
repository,
|
|
392
397
|
active: true,
|
|
393
398
|
house,
|
|
@@ -399,6 +404,7 @@ export class LocalCopies {
|
|
|
399
404
|
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
400
405
|
moves: [],
|
|
401
406
|
});
|
|
407
|
+
return `${rootOf(handle)}: copied to ${repository}`;
|
|
402
408
|
}
|
|
403
409
|
async received(copy) {
|
|
404
410
|
const read = await this.read(copy.room_handle);
|
|
@@ -422,29 +428,52 @@ export class LocalCopies {
|
|
|
422
428
|
await ignoreRules(copy.repository, [...read.writable.keys()], next);
|
|
423
429
|
return next;
|
|
424
430
|
}
|
|
425
|
-
async
|
|
426
|
-
|
|
427
|
-
|
|
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) {
|
|
428
450
|
let answer;
|
|
429
451
|
try {
|
|
430
452
|
answer = await this.house.post('/kit/door/enumerate', {
|
|
431
|
-
prefixes:
|
|
432
|
-
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 })),
|
|
433
455
|
});
|
|
434
456
|
}
|
|
435
457
|
catch (error) {
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
438
|
-
return this.received(copy);
|
|
439
|
-
if (code !== 'replica_behind')
|
|
458
|
+
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
459
|
+
if (expired === undefined)
|
|
440
460
|
throw error;
|
|
441
|
-
|
|
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);
|
|
442
473
|
}
|
|
443
|
-
|
|
444
|
-
if (view === undefined || (view.position === copy.position && view.log_epoch === copy.log_epoch))
|
|
445
|
-
return copy;
|
|
446
|
-
copy = await this.save(await this.incorporated(copy, view.changes, view.position, view.log_epoch));
|
|
474
|
+
asking = whole ? advanced : [];
|
|
447
475
|
}
|
|
476
|
+
return copies.map((copy) => caught.get(copy.key));
|
|
448
477
|
}
|
|
449
478
|
async incorporated(copy, changes, position, logEpoch) {
|
|
450
479
|
const houseTree = await tree(copy.repository, copy.house);
|
|
@@ -547,7 +576,13 @@ export class LocalCopies {
|
|
|
547
576
|
const after = await tree(copy.repository, commit);
|
|
548
577
|
const moved = movedPaths(copy.moves, before, after);
|
|
549
578
|
const arrived = new Set(moved.values());
|
|
550
|
-
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);
|
|
551
586
|
const changes = [];
|
|
552
587
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
553
588
|
if (arrived.has(path))
|
|
@@ -560,7 +595,7 @@ export class LocalCopies {
|
|
|
560
595
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
561
596
|
const moving = after.get(to);
|
|
562
597
|
if (moving !== prior) {
|
|
563
|
-
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) });
|
|
564
599
|
}
|
|
565
600
|
continue;
|
|
566
601
|
}
|
|
@@ -569,9 +604,9 @@ export class LocalCopies {
|
|
|
569
604
|
if (next === undefined)
|
|
570
605
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
571
606
|
else if (prior === undefined)
|
|
572
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
607
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
573
608
|
else
|
|
574
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
609
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
575
610
|
}
|
|
576
611
|
return changes;
|
|
577
612
|
}
|
|
@@ -598,12 +633,12 @@ export class LocalCopies {
|
|
|
598
633
|
const { upload, ...unstaged } = copy.push;
|
|
599
634
|
return this.settle(await this.save({ ...copy, push: unstaged }), caller, true);
|
|
600
635
|
}
|
|
601
|
-
if (outcome.refused.code === '
|
|
636
|
+
if (outcome.refused.code === 'expired')
|
|
602
637
|
return this.recovered(copy);
|
|
603
638
|
const rebased = rebaseFixes(outcome.refused);
|
|
604
639
|
let settled = await this.save(without(copy));
|
|
605
640
|
try {
|
|
606
|
-
settled =
|
|
641
|
+
settled = (await this.caughtUp([settled], true))[0];
|
|
607
642
|
}
|
|
608
643
|
catch {
|
|
609
644
|
return {
|
|
@@ -622,7 +657,7 @@ export class LocalCopies {
|
|
|
622
657
|
}
|
|
623
658
|
async recovered(copy) {
|
|
624
659
|
const push = copy.push;
|
|
625
|
-
const current =
|
|
660
|
+
const current = (await this.caughtUp([copy], true))[0];
|
|
626
661
|
const house = await tree(copy.repository, current.house);
|
|
627
662
|
const root = rootOf(current.room_handle);
|
|
628
663
|
if (!(await this.landed(copy.repository, push.changes, house, await tree(copy.repository, push.commit)))) {
|
|
@@ -638,7 +673,7 @@ export class LocalCopies {
|
|
|
638
673
|
written.push({
|
|
639
674
|
path: `${root}/${path}`,
|
|
640
675
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
641
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
676
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
642
677
|
});
|
|
643
678
|
}
|
|
644
679
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -666,7 +701,7 @@ export class LocalCopies {
|
|
|
666
701
|
const push = copy.push;
|
|
667
702
|
let written = push.written;
|
|
668
703
|
if (push.changes.some((change) => change.op === 'rename')) {
|
|
669
|
-
copy =
|
|
704
|
+
copy = (await this.caughtUp([copy], true))[0];
|
|
670
705
|
const house = await tree(copy.repository, copy.house);
|
|
671
706
|
const sent = await tree(copy.repository, push.house);
|
|
672
707
|
if (push.changes.some((change) => change.op === 'rename' && !house.has(relative(change.to)))) {
|
|
@@ -682,7 +717,7 @@ export class LocalCopies {
|
|
|
682
717
|
{
|
|
683
718
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
684
719
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
685
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
720
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
686
721
|
},
|
|
687
722
|
];
|
|
688
723
|
}
|
|
@@ -727,7 +762,7 @@ export class LocalCopies {
|
|
|
727
762
|
async landed(repository, changes, house, selected) {
|
|
728
763
|
const text = async (path) => {
|
|
729
764
|
const entry = house.get(relative(path));
|
|
730
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
765
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
731
766
|
};
|
|
732
767
|
for (const change of changes) {
|
|
733
768
|
const current = await text(change.path);
|
package/dist/end.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { forgetRejected } from './home.js';
|
|
2
|
+
import { installCommand } from './placement.js';
|
|
3
|
+
import { killDescendants } from './scope.js';
|
|
2
4
|
export const REPLACED = 'another Kit started for this Environment';
|
|
3
|
-
|
|
4
|
-
export function end(reason) {
|
|
5
|
+
function exit(reason, status) {
|
|
5
6
|
process.stderr.write(`kit: ${reason}\n`);
|
|
6
7
|
killDescendants();
|
|
7
|
-
process.exit(
|
|
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');
|
|
8
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
|
+
});
|
package/dist/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const COMMAND = { shown: 'command' };
|
|
2
2
|
const HIDDEN = { shown: 'hidden' };
|
|
3
3
|
const FINISHED = new Set(['completed', 'failed']);
|
|
4
|
-
const RUNNING_JOB = new Set(['running', 'paused']);
|
|
4
|
+
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
5
|
const ROUTINE_TARGETS = { send_routine_request: 'access', cancel_routine_request: 'request' };
|
|
6
6
|
function words(line) {
|
|
7
7
|
const found = [];
|
|
@@ -113,9 +113,6 @@ class Reader {
|
|
|
113
113
|
refused(option) {
|
|
114
114
|
this.requestedOptions.delete(option);
|
|
115
115
|
}
|
|
116
|
-
turnStarted() {
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
116
|
closeTurn() {
|
|
120
117
|
const events = [];
|
|
121
118
|
for (const [id, call] of this.calls) {
|
|
@@ -187,10 +184,7 @@ class Reader {
|
|
|
187
184
|
const label = this.jobs.get(id) ?? '';
|
|
188
185
|
if (state !== 'running')
|
|
189
186
|
this.jobs.delete(id);
|
|
190
|
-
return [
|
|
191
|
-
{ kind: 'busy', key: `job:${id}`, running: state === 'running' },
|
|
192
|
-
{ kind: 'marker', marker: 'job', text: `${label}: ${state}` },
|
|
193
|
-
];
|
|
187
|
+
return [{ kind: 'marker', marker: 'job', text: `${label}: ${state}` }];
|
|
194
188
|
}
|
|
195
189
|
optionsChanged(options) {
|
|
196
190
|
const events = [];
|
|
@@ -275,7 +269,6 @@ class Reader {
|
|
|
275
269
|
class ClaudeReader extends Reader {
|
|
276
270
|
watchedOptions = { model: 'model' };
|
|
277
271
|
tasks = new Map();
|
|
278
|
-
scheduled = new Map();
|
|
279
272
|
own(message) {
|
|
280
273
|
if (message.params?.update?._meta?.claudeCode?.parentToolUseId !== undefined)
|
|
281
274
|
return [];
|
|
@@ -295,10 +288,6 @@ class ClaudeReader extends Reader {
|
|
|
295
288
|
return [];
|
|
296
289
|
const sdk = message.params.message;
|
|
297
290
|
switch (sdk.subtype) {
|
|
298
|
-
case 'background_tasks_changed':
|
|
299
|
-
return [{ kind: 'busy', key: 'tasks', running: sdk.tasks.length > 0 }];
|
|
300
|
-
case 'session_state_changed':
|
|
301
|
-
return [{ kind: 'busy', key: 'state', running: sdk.state === 'running' }];
|
|
302
291
|
case 'task_started':
|
|
303
292
|
if (sdk.tool_use_id !== undefined)
|
|
304
293
|
this.tasks.set(sdk.task_id, sdk.tool_use_id);
|
|
@@ -309,16 +298,6 @@ class ClaudeReader extends Reader {
|
|
|
309
298
|
return [];
|
|
310
299
|
}
|
|
311
300
|
}
|
|
312
|
-
turnStarted() {
|
|
313
|
-
const events = [];
|
|
314
|
-
for (const [id, entry] of this.scheduled) {
|
|
315
|
-
if (entry.recurring)
|
|
316
|
-
continue;
|
|
317
|
-
this.scheduled.delete(id);
|
|
318
|
-
events.push({ kind: 'busy', key: `scheduled:${id}`, running: false });
|
|
319
|
-
}
|
|
320
|
-
return events;
|
|
321
|
-
}
|
|
322
301
|
decide(call, final) {
|
|
323
302
|
const tool = call.meta.claudeCode?.toolName;
|
|
324
303
|
if (tool === 'Agent' || tool === 'Task' || call.meta.jetbrains?.air?.subagent === true) {
|
|
@@ -350,22 +329,7 @@ class ClaudeReader extends Reader {
|
|
|
350
329
|
if (call.status !== 'completed')
|
|
351
330
|
return [];
|
|
352
331
|
if (tool === 'CronCreate' || (tool === 'ScheduleWakeup' && call.rawInput?.stop !== true)) {
|
|
353
|
-
|
|
354
|
-
recurring: tool === 'CronCreate' && call.rawInput?.recurring !== false,
|
|
355
|
-
text: contentText(call.content) ?? '',
|
|
356
|
-
});
|
|
357
|
-
return [
|
|
358
|
-
{ kind: 'busy', key: `scheduled:${call.id}`, running: true },
|
|
359
|
-
{ kind: 'marker', marker: 'job', text: `${call.rawInput?.prompt ?? call.title ?? ''}: scheduled` },
|
|
360
|
-
];
|
|
361
|
-
}
|
|
362
|
-
if (tool === 'CronDelete') {
|
|
363
|
-
for (const [id, entry] of this.scheduled) {
|
|
364
|
-
if (!entry.text.includes(String(call.rawInput?.id)))
|
|
365
|
-
continue;
|
|
366
|
-
this.scheduled.delete(id);
|
|
367
|
-
return [{ kind: 'busy', key: `scheduled:${id}`, running: false }];
|
|
368
|
-
}
|
|
332
|
+
return [{ kind: 'marker', marker: 'job', text: `${call.rawInput?.prompt ?? call.title ?? ''}: scheduled` }];
|
|
369
333
|
}
|
|
370
334
|
return [];
|
|
371
335
|
}
|
|
@@ -402,15 +366,9 @@ class CodexReader extends Reader {
|
|
|
402
366
|
case 'subagent_spawned':
|
|
403
367
|
this.children.set(update.subagentSessionId, { id: null, text: '' });
|
|
404
368
|
this.subagents.add(update.subagentSessionId);
|
|
405
|
-
return [
|
|
406
|
-
{ kind: 'busy', key: `subagent:${update.subagentSessionId}`, running: true },
|
|
407
|
-
{ kind: 'subagent', id: update.subagentSessionId, description: update.name },
|
|
408
|
-
];
|
|
369
|
+
return [{ kind: 'subagent', id: update.subagentSessionId, description: update.name }];
|
|
409
370
|
case 'subagent_state_update':
|
|
410
|
-
return
|
|
411
|
-
{ kind: 'busy', key: `subagent:${update.subagentSessionId}`, running: false },
|
|
412
|
-
...this.finish(update.subagentSessionId, this.children.get(update.subagentSessionId)?.text || null),
|
|
413
|
-
];
|
|
371
|
+
return this.finish(update.subagentSessionId, this.children.get(update.subagentSessionId)?.text || null);
|
|
414
372
|
default:
|
|
415
373
|
return super.update(update);
|
|
416
374
|
}
|
|
@@ -427,10 +385,6 @@ class CodexReader extends Reader {
|
|
|
427
385
|
child.id = id;
|
|
428
386
|
child.text += update.content.text;
|
|
429
387
|
}
|
|
430
|
-
const status = update._meta?.codex?.threadStatus?.type;
|
|
431
|
-
if (update.sessionUpdate === 'session_info_update' && status !== undefined) {
|
|
432
|
-
return [{ kind: 'busy', key: `thread:${session}`, running: status === 'active' }];
|
|
433
|
-
}
|
|
434
388
|
return [];
|
|
435
389
|
}
|
|
436
390
|
decide(call) {
|
|
@@ -460,7 +414,6 @@ class CodexReader extends Reader {
|
|
|
460
414
|
class GrokReader extends Reader {
|
|
461
415
|
watchedOptions = {};
|
|
462
416
|
children = new Map();
|
|
463
|
-
queues = new Map();
|
|
464
417
|
models = [];
|
|
465
418
|
model = null;
|
|
466
419
|
answered(result) {
|
|
@@ -473,15 +426,10 @@ class GrokReader extends Reader {
|
|
|
473
426
|
if (this.session !== null && session !== undefined && session !== this.session && !this.children.has(session))
|
|
474
427
|
return [];
|
|
475
428
|
if (message.method === '_x.ai/queue/changed') {
|
|
476
|
-
|
|
477
|
-
this.queues.set(session, { running, entries: message.params.entries.length });
|
|
478
|
-
return [this.queued(session), ...(running !== null && session === this.session ? [{ kind: 'started' }] : [])];
|
|
429
|
+
return (message.params.runningPromptId ?? null) !== null && session === this.session ? [{ kind: 'started' }] : [];
|
|
479
430
|
}
|
|
480
431
|
if (message.params?.update?.sessionUpdate === 'turn_completed') {
|
|
481
|
-
|
|
482
|
-
if (queue !== undefined && queue.running === message.params.update.prompt_id)
|
|
483
|
-
queue.running = null;
|
|
484
|
-
return [this.queued(session), ...(session === this.session ? [{ kind: 'ended' }] : [])];
|
|
432
|
+
return session === this.session ? [{ kind: 'ended' }] : [];
|
|
485
433
|
}
|
|
486
434
|
if (message.method === '_x.ai/models/update') {
|
|
487
435
|
this.offered(message.params);
|
|
@@ -489,10 +437,6 @@ class GrokReader extends Reader {
|
|
|
489
437
|
}
|
|
490
438
|
return super.read(message);
|
|
491
439
|
}
|
|
492
|
-
queued(session) {
|
|
493
|
-
const queue = this.queues.get(session);
|
|
494
|
-
return { kind: 'busy', key: `queue:${session}`, running: queue !== undefined && (queue.running !== null || queue.entries > 0) };
|
|
495
|
-
}
|
|
496
440
|
offered(models) {
|
|
497
441
|
this.models = models.availableModels ?? this.models;
|
|
498
442
|
this.model = models.currentModelId ?? this.model;
|
|
@@ -508,16 +452,9 @@ class GrokReader extends Reader {
|
|
|
508
452
|
case '_x.ai/task_completed':
|
|
509
453
|
return this.job(update.task_snapshot.task_id, null, update.task_snapshot.exit_code === 0 ? 'completed' : 'failed');
|
|
510
454
|
case '_x.ai/scheduled_task_created':
|
|
511
|
-
return [
|
|
512
|
-
{ kind: 'busy', key: `scheduled:${update.task_id}`, running: true },
|
|
513
|
-
{ kind: 'marker', marker: 'job', text: `${update.prompt}: next run ${update.next_fire_at}` },
|
|
514
|
-
];
|
|
515
|
-
case '_x.ai/scheduled_task_deleted':
|
|
516
|
-
return [{ kind: 'busy', key: `scheduled:${update.task_id}`, running: false }];
|
|
455
|
+
return [{ kind: 'marker', marker: 'job', text: `${update.prompt}: next run ${update.next_fire_at}` }];
|
|
517
456
|
case '_x.ai/session_notification':
|
|
518
457
|
return this.notification(update);
|
|
519
|
-
case '_x.ai/mcp_initialized':
|
|
520
|
-
return [{ kind: 'servers' }];
|
|
521
458
|
default:
|
|
522
459
|
return [];
|
|
523
460
|
}
|
|
@@ -538,14 +475,11 @@ class GrokReader extends Reader {
|
|
|
538
475
|
candidate.decided.description === update.description) ??
|
|
539
476
|
[...this.calls.values()].find((candidate) => candidate.decided?.shown === 'subagent' && ![...this.children.values()].includes(candidate.id));
|
|
540
477
|
this.children.set(update.child_session_id, call?.id ?? null);
|
|
541
|
-
return [
|
|
478
|
+
return [];
|
|
542
479
|
}
|
|
543
480
|
case 'subagent_finished': {
|
|
544
481
|
const call = this.children.get(update.child_session_id);
|
|
545
|
-
return [
|
|
546
|
-
{ kind: 'busy', key: `subagent:${update.child_session_id}`, running: false },
|
|
547
|
-
...(typeof call === 'string' ? this.finish(call, update.output ?? null) : []),
|
|
548
|
-
];
|
|
482
|
+
return typeof call === 'string' ? this.finish(call, update.output ?? null) : [];
|
|
549
483
|
}
|
|
550
484
|
case 'auto_compact_completed':
|
|
551
485
|
return [{ kind: 'marker', marker: 'compaction', text: '' }];
|
package/dist/files.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { HouseRefusal, retrying } from './api.js';
|
|
4
5
|
import { relayed } from './relay.js';
|
|
6
|
+
export function verified(bytes, sha256, name) {
|
|
7
|
+
if (createHash('sha256').update(bytes).digest('hex') !== sha256)
|
|
8
|
+
throw new Error(`${name} did not arrive whole; download it again`);
|
|
9
|
+
return bytes;
|
|
10
|
+
}
|
|
5
11
|
export async function placeFiles(house, directory, files, signal) {
|
|
6
12
|
const paths = [];
|
|
7
13
|
for (const file of files) {
|
|
@@ -16,7 +22,7 @@ export async function placeFiles(house, directory, files, signal) {
|
|
|
16
22
|
});
|
|
17
23
|
await mkdir(folder, { recursive: true });
|
|
18
24
|
const path = join(folder, file.name);
|
|
19
|
-
await writeFile(path, Buffer.from(await answer.arrayBuffer()));
|
|
25
|
+
await writeFile(path, verified(Buffer.from(await answer.arrayBuffer()), file.sha256, file.name));
|
|
20
26
|
paths.push(path);
|
|
21
27
|
}
|
|
22
28
|
return paths;
|