@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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { connect } from 'node:net';
|
|
4
|
+
const KIT_ANSWER = '"house-kit-';
|
|
5
|
+
const SYNC = 'house-kit/sync';
|
|
6
|
+
function lines(each) {
|
|
7
|
+
let buffer = '';
|
|
8
|
+
return (chunk) => {
|
|
9
|
+
buffer += chunk;
|
|
10
|
+
for (let index = buffer.indexOf('\n'); index >= 0; index = buffer.indexOf('\n')) {
|
|
11
|
+
each(buffer.slice(0, index + 1));
|
|
12
|
+
buffer = buffer.slice(index + 1);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function answersKit(line) {
|
|
17
|
+
if (!line.includes(KIT_ANSWER))
|
|
18
|
+
return false;
|
|
19
|
+
try {
|
|
20
|
+
const message = JSON.parse(line);
|
|
21
|
+
return message.method === undefined && typeof message.id === 'string' && message.id.startsWith(KIT_ANSWER.slice(1));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const serving = args[0] === 'app-server';
|
|
29
|
+
if (process.env.HOUSE_KIT_SANDBOX === '1' && serving)
|
|
30
|
+
args.push('--disable', 'memories');
|
|
31
|
+
const codex = spawn(process.env.HOUSE_KIT_CODEX, args, { stdio: ['pipe', 'pipe', 'inherit'] });
|
|
32
|
+
const stream = serving ? connect(process.env.HOUSE_KIT_CODEX_STREAM) : null;
|
|
33
|
+
stream?.on('error', () => undefined);
|
|
34
|
+
process.stdin.setEncoding('utf8').on('data', lines((line) => codex.stdin.write(line)));
|
|
35
|
+
process.stdin.on('end', () => codex.stdin.end());
|
|
36
|
+
stream?.setEncoding('utf8').on('data', lines((line) => {
|
|
37
|
+
const message = JSON.parse(line);
|
|
38
|
+
if (message.method === SYNC)
|
|
39
|
+
stream.write(`${JSON.stringify({ jsonrpc: '2.0', id: message.id, result: {} })}\n`);
|
|
40
|
+
else
|
|
41
|
+
codex.stdin.write(line);
|
|
42
|
+
}));
|
|
43
|
+
codex.stdout.setEncoding('utf8').on('data', lines((line) => {
|
|
44
|
+
stream?.write(line);
|
|
45
|
+
if (answersKit(line) || process.stdout.write(line))
|
|
46
|
+
return;
|
|
47
|
+
codex.stdout.pause();
|
|
48
|
+
process.stdout.once('drain', () => codex.stdout.resume());
|
|
49
|
+
}));
|
|
50
|
+
codex.stdin.on('error', () => undefined);
|
|
51
|
+
for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP'])
|
|
52
|
+
process.on(signal, () => codex.kill(signal));
|
|
53
|
+
codex.on('exit', (code, signal) => {
|
|
54
|
+
if (signal === null)
|
|
55
|
+
process.exit(code ?? 1);
|
|
56
|
+
process.removeAllListeners(signal);
|
|
57
|
+
process.kill(process.pid, signal);
|
|
58
|
+
});
|
package/dist/conversations.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
|
-
import {
|
|
6
|
+
import { startAdapter } from './acp.js';
|
|
7
7
|
import { openBridge } from './bridge.js';
|
|
8
8
|
import { CLIS } from './clis.js';
|
|
9
9
|
import { readerFor } from './events.js';
|
|
@@ -11,12 +11,13 @@ import { placeFiles, uploadBytes } from './files.js';
|
|
|
11
11
|
import { agentBase } from './home.js';
|
|
12
12
|
import { instructions } from './instructions.js';
|
|
13
13
|
import { Parts } from './parts.js';
|
|
14
|
+
import { killMarked, Scope } from './scope.js';
|
|
14
15
|
import { holdSecretInput } from './secret-input.js';
|
|
15
16
|
import { createHowWeWork } from './skills.js';
|
|
16
17
|
import { transcribe } from './transcription.js';
|
|
18
|
+
import { workFor } from './work.js';
|
|
17
19
|
const KILLED = 'the conversation was killed';
|
|
18
|
-
|
|
19
|
-
const PROCESS_RECHECK_MS = 5000;
|
|
20
|
+
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
20
21
|
function causeOf(error) {
|
|
21
22
|
return error instanceof Error ? error.message : String(error);
|
|
22
23
|
}
|
|
@@ -29,9 +30,6 @@ function logged(error) {
|
|
|
29
30
|
function marker(bridge) {
|
|
30
31
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
31
32
|
}
|
|
32
|
-
function stop(bridge) {
|
|
33
|
-
killMarked(marker(bridge));
|
|
34
|
-
}
|
|
35
33
|
class Turn {
|
|
36
34
|
id = randomUUID();
|
|
37
35
|
parts;
|
|
@@ -53,7 +51,7 @@ class Conversation {
|
|
|
53
51
|
turn = null;
|
|
54
52
|
commands = null;
|
|
55
53
|
context = null;
|
|
56
|
-
|
|
54
|
+
leftovers = new Set();
|
|
57
55
|
waiting = [];
|
|
58
56
|
sending = null;
|
|
59
57
|
subagents = new Map();
|
|
@@ -86,9 +84,10 @@ export class Conversations {
|
|
|
86
84
|
carrying = 0;
|
|
87
85
|
reporting = 0;
|
|
88
86
|
watched = false;
|
|
89
|
-
recheck = null;
|
|
90
87
|
constructor(kit) {
|
|
91
88
|
this.kit = kit;
|
|
89
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
90
|
+
setInterval(() => this.look(), 2_000);
|
|
92
91
|
}
|
|
93
92
|
input(input) {
|
|
94
93
|
this.carrying++;
|
|
@@ -126,22 +125,27 @@ export class Conversations {
|
|
|
126
125
|
for (const conversation of this.conversations.values()) {
|
|
127
126
|
const turn = conversation.turn;
|
|
128
127
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
129
|
-
if (conversation.
|
|
128
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
130
129
|
return false;
|
|
131
130
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (running === null || running.baseline === null)
|
|
135
|
-
continue;
|
|
136
|
-
if ([...processes(marker(running.bridge))].every((found) => running.baseline.has(found)))
|
|
137
|
-
continue;
|
|
138
|
-
this.recheck ??= setTimeout(() => {
|
|
139
|
-
this.recheck = null;
|
|
140
|
-
this.kit.changed();
|
|
141
|
-
}, PROCESS_RECHECK_MS);
|
|
131
|
+
const conversations = [...this.conversations.values()];
|
|
132
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
142
133
|
return false;
|
|
134
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
135
|
+
}
|
|
136
|
+
quiet() {
|
|
137
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
138
|
+
}
|
|
139
|
+
look() {
|
|
140
|
+
for (const conversation of this.conversations.values()) {
|
|
141
|
+
for (const scope of conversation.leftovers) {
|
|
142
|
+
if (!scope.empty())
|
|
143
|
+
continue;
|
|
144
|
+
scope.close();
|
|
145
|
+
conversation.leftovers.delete(scope);
|
|
146
|
+
}
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
this.kit.changed();
|
|
145
149
|
}
|
|
146
150
|
opened() {
|
|
147
151
|
this.watched = false;
|
|
@@ -295,11 +299,13 @@ export class Conversations {
|
|
|
295
299
|
}
|
|
296
300
|
halt(conversation) {
|
|
297
301
|
if (conversation.opening !== null)
|
|
298
|
-
|
|
302
|
+
killMarked(marker(conversation.opening));
|
|
303
|
+
for (const scope of conversation.leftovers)
|
|
304
|
+
scope.kill();
|
|
299
305
|
if (conversation.running === null)
|
|
300
306
|
return;
|
|
301
307
|
conversation.running.killed = true;
|
|
302
|
-
|
|
308
|
+
conversation.running.scope.kill();
|
|
303
309
|
}
|
|
304
310
|
async kill(conversation) {
|
|
305
311
|
const running = conversation.running;
|
|
@@ -350,22 +356,30 @@ export class Conversations {
|
|
|
350
356
|
throw new Error(KILLED);
|
|
351
357
|
}
|
|
352
358
|
conversation.opening = bridge;
|
|
359
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
360
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
353
361
|
try {
|
|
354
362
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
355
|
-
await CLIS[route.kind].allowHouse();
|
|
356
|
-
|
|
357
|
-
|
|
363
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
364
|
+
const adapter = await startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
365
|
+
work.read(message);
|
|
366
|
+
this.read(conversation, reader, message);
|
|
367
|
+
}, { ...bridge.env, ...(await work.open(cli)) }, undefined, scope);
|
|
358
368
|
void adapter.exited.then(() => bridge.close());
|
|
359
369
|
const agent = adapter.connection.agent;
|
|
360
370
|
const cwd = route.working_directory;
|
|
361
|
-
const _meta = CLIS[route.kind].sessionMeta;
|
|
371
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
362
372
|
const opened = sessionId === null
|
|
363
373
|
? await agent.request('session/new', { cwd, mcpServers: [], _meta })
|
|
364
374
|
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
365
375
|
reader.session = opened.sessionId;
|
|
366
|
-
|
|
376
|
+
if (sessionId === null && CLIS[route.kind].renames) {
|
|
377
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
378
|
+
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
379
|
+
}
|
|
380
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
367
381
|
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
368
|
-
|
|
382
|
+
void work.settled().then(() => scope.record());
|
|
369
383
|
conversation.running = running;
|
|
370
384
|
conversation.session = running.sessionId;
|
|
371
385
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
@@ -376,7 +390,9 @@ export class Conversations {
|
|
|
376
390
|
return running.sessionId;
|
|
377
391
|
}
|
|
378
392
|
catch (error) {
|
|
379
|
-
|
|
393
|
+
scope.kill();
|
|
394
|
+
work.close();
|
|
395
|
+
conversation.leftovers.add(scope);
|
|
380
396
|
bridge.close();
|
|
381
397
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
382
398
|
}
|
|
@@ -409,17 +425,18 @@ export class Conversations {
|
|
|
409
425
|
const running = conversation.running;
|
|
410
426
|
if (running?.adapter !== adapter)
|
|
411
427
|
return;
|
|
412
|
-
this.detach(conversation);
|
|
428
|
+
this.detach(conversation, running);
|
|
413
429
|
if (conversation.turn !== null) {
|
|
414
430
|
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
415
431
|
}
|
|
416
432
|
this.next(conversation);
|
|
417
433
|
this.kit.changed();
|
|
418
434
|
}
|
|
419
|
-
detach(conversation) {
|
|
435
|
+
detach(conversation, running) {
|
|
436
|
+
running.work.close();
|
|
437
|
+
conversation.leftovers.add(running.scope);
|
|
420
438
|
conversation.running = null;
|
|
421
439
|
conversation.commands = null;
|
|
422
|
-
conversation.busy.clear();
|
|
423
440
|
for (const [id, question] of this.questions) {
|
|
424
441
|
if (question.conversation === conversation)
|
|
425
442
|
this.questions.delete(id);
|
|
@@ -471,8 +488,6 @@ export class Conversations {
|
|
|
471
488
|
turn.parts.measure(conversation.context);
|
|
472
489
|
for (const part of conversation.held.splice(0))
|
|
473
490
|
turn.parts.start(part);
|
|
474
|
-
for (const event of conversation.running?.reader.turnStarted() ?? [])
|
|
475
|
-
this.event(conversation, event);
|
|
476
491
|
this.kit.changed();
|
|
477
492
|
return turn;
|
|
478
493
|
}
|
|
@@ -488,7 +503,7 @@ export class Conversations {
|
|
|
488
503
|
if (conversation.turn === turn)
|
|
489
504
|
conversation.turn = null;
|
|
490
505
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
491
|
-
parts: turn.parts.
|
|
506
|
+
parts: turn.parts.saved(),
|
|
492
507
|
context: turn.parts.context,
|
|
493
508
|
...failed,
|
|
494
509
|
}));
|
|
@@ -506,8 +521,8 @@ export class Conversations {
|
|
|
506
521
|
}
|
|
507
522
|
else {
|
|
508
523
|
if (conversation.open === 1 && conversation.running === running) {
|
|
509
|
-
|
|
510
|
-
this.detach(conversation);
|
|
524
|
+
running.scope.kill();
|
|
525
|
+
this.detach(conversation, running);
|
|
511
526
|
}
|
|
512
527
|
this.end(conversation, turn, { failed: causeOf(error) });
|
|
513
528
|
}
|
|
@@ -592,20 +607,6 @@ export class Conversations {
|
|
|
592
607
|
case 'options':
|
|
593
608
|
this.options(conversation, event.options);
|
|
594
609
|
return;
|
|
595
|
-
case 'busy':
|
|
596
|
-
if (event.running)
|
|
597
|
-
conversation.busy.add(event.key);
|
|
598
|
-
else
|
|
599
|
-
conversation.busy.delete(event.key);
|
|
600
|
-
this.kit.changed();
|
|
601
|
-
return;
|
|
602
|
-
case 'servers': {
|
|
603
|
-
const running = conversation.running;
|
|
604
|
-
if (running?.baseline)
|
|
605
|
-
for (const found of processes(marker(running.bridge)))
|
|
606
|
-
running.baseline.add(found);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
610
|
case 'context':
|
|
610
611
|
conversation.context = { used: event.used, window: event.window };
|
|
611
612
|
conversation.turn?.parts.measure(conversation.context);
|
|
@@ -647,7 +648,7 @@ export class Conversations {
|
|
|
647
648
|
turn.parts.plan(event.entries);
|
|
648
649
|
return;
|
|
649
650
|
case 'image':
|
|
650
|
-
turn.uploads.push(this.image(conversation, turn, event.data, event.mediaType));
|
|
651
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
651
652
|
this.kit.changed();
|
|
652
653
|
return;
|
|
653
654
|
}
|
|
@@ -667,7 +668,7 @@ export class Conversations {
|
|
|
667
668
|
else
|
|
668
669
|
conversation.turn.parts.start(part);
|
|
669
670
|
}
|
|
670
|
-
async image(conversation, turn, data, mediaType) {
|
|
671
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
671
672
|
const bytes = Buffer.from(data, 'base64');
|
|
672
673
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
673
674
|
try {
|
|
@@ -680,10 +681,10 @@ export class Conversations {
|
|
|
680
681
|
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
681
682
|
if (saved.save.status !== 'saved')
|
|
682
683
|
throw new Error(saved.save.failure ?? saved.save.status);
|
|
683
|
-
turn.parts.
|
|
684
|
+
turn.parts.arrive({ type: 'image', file: saved.version, name, media_type: mediaType }, reserved);
|
|
684
685
|
}
|
|
685
686
|
catch (error) {
|
|
686
|
-
turn.parts.
|
|
687
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` }, reserved);
|
|
687
688
|
}
|
|
688
689
|
}
|
|
689
690
|
}
|