@agentshouse/kit 0.2.1-cf110d15f69c8 → 0.2.1-cf3a50c90b895
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 +31 -30
- package/dist/acp.js +36 -16
- package/dist/agents.js +110 -23
- package/dist/api.js +5 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +3 -6
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +20 -4
- package/dist/connection.js +125 -0
- package/dist/conversations.js +307 -120
- package/dist/copies.js +89 -52
- package/dist/end.js +11 -2
- package/dist/events.js +6 -2
- package/dist/files.js +8 -2
- package/dist/git.js +11 -9
- package/dist/home.js +56 -5
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -206
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +46 -28
- package/dist/logout.js +0 -2
- package/dist/mcp.js +2 -2
- package/dist/placement.js +39 -0
- package/dist/relay.js +1 -1
- package/dist/resident.js +82 -33
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +19 -37
- package/dist/skills.js +84 -15
- package/dist/transcription.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +69 -50
- package/dist/wire.js +22 -0
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +4 -3
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/dist/stream.js +0 -74
- 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/calls.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { kitHome } from './home.js';
|
|
4
|
+
function logged(error) {
|
|
5
|
+
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
6
|
+
}
|
|
7
|
+
export class Calls {
|
|
8
|
+
house;
|
|
9
|
+
changed;
|
|
10
|
+
carried = new Map();
|
|
11
|
+
path = join(kitHome(), 'call-results.json');
|
|
12
|
+
results;
|
|
13
|
+
returned = new Set();
|
|
14
|
+
open = 0;
|
|
15
|
+
constructor(house, changed) {
|
|
16
|
+
this.house = house;
|
|
17
|
+
this.changed = changed;
|
|
18
|
+
this.results = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
|
|
19
|
+
}
|
|
20
|
+
keepResult(callId, result) {
|
|
21
|
+
if (this.returned.has(callId))
|
|
22
|
+
return;
|
|
23
|
+
this.results.set(callId, result);
|
|
24
|
+
this.save();
|
|
25
|
+
}
|
|
26
|
+
idle() {
|
|
27
|
+
return this.open === 0;
|
|
28
|
+
}
|
|
29
|
+
carry(callId, work) {
|
|
30
|
+
const known = this.carried.get(callId);
|
|
31
|
+
const result = this.results.get(callId);
|
|
32
|
+
const carried = known ?? (result === undefined ? work() : Promise.resolve({ result }));
|
|
33
|
+
if (known === undefined) {
|
|
34
|
+
this.carried.set(callId, carried);
|
|
35
|
+
carried.catch(() => this.carried.delete(callId));
|
|
36
|
+
}
|
|
37
|
+
this.open++;
|
|
38
|
+
this.changed();
|
|
39
|
+
return carried
|
|
40
|
+
.then(async ({ result, returned }) => {
|
|
41
|
+
try {
|
|
42
|
+
await this.house.deliver(`/kit/inputs/${callId}/ack`, result);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.returned.add(callId);
|
|
46
|
+
if (this.results.delete(callId))
|
|
47
|
+
this.save();
|
|
48
|
+
}
|
|
49
|
+
if (known === undefined)
|
|
50
|
+
await returned?.();
|
|
51
|
+
})
|
|
52
|
+
.catch(logged)
|
|
53
|
+
.finally(() => {
|
|
54
|
+
this.open--;
|
|
55
|
+
this.changed();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
save() {
|
|
59
|
+
const temporary = `${this.path}.${process.pid}`;
|
|
60
|
+
writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.results)), { mode: 0o600 });
|
|
61
|
+
renameSync(temporary, this.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/chatgpt.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { placeAppGit } from './app-git.js';
|
|
3
|
+
import { kitHome, readOwnAgent, writeOwnAgent } from './home.js';
|
|
4
|
+
import { accepted, DEFAULT_HOUSE } from './login.js';
|
|
5
|
+
const KEY_SOURCE = 'New key for a script on Connections';
|
|
6
|
+
const USAGE = `usage: kit chatgpt --key <key> [--house <origin>]
|
|
7
|
+
--key <key> a key from ${KEY_SOURCE}
|
|
8
|
+
--house <origin> the House of the key, ${DEFAULT_HOUSE} unless one is stored
|
|
9
|
+
`;
|
|
10
|
+
export async function chatgpt(argv) {
|
|
11
|
+
const { values } = parseArgs({
|
|
12
|
+
args: argv,
|
|
13
|
+
options: { key: { type: 'string' }, house: { type: 'string' }, help: { type: 'boolean', default: false } },
|
|
14
|
+
});
|
|
15
|
+
if (values.help) {
|
|
16
|
+
process.stdout.write(USAGE);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (values.key === undefined)
|
|
20
|
+
throw new Error(`pass --key with a key from ${KEY_SOURCE}`);
|
|
21
|
+
const before = await readOwnAgent();
|
|
22
|
+
const house = values.house ?? before?.house ?? DEFAULT_HOUSE;
|
|
23
|
+
if (!(await accepted(house, values.key)))
|
|
24
|
+
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
25
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
26
|
+
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
27
|
+
await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
28
|
+
if (before !== null && before.credential !== values.key) {
|
|
29
|
+
process.stdout.write('This replaced the key this Kit home held; give each session its own HOUSE_KIT_HOME to keep its own key.\n');
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
32
|
+
}
|
package/dist/clis.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { constants, readFileSync } from 'node:fs';
|
|
3
4
|
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { delimiter, isAbsolute, join } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
7
|
import { kitHome } from './home.js';
|
|
7
8
|
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
8
9
|
function houseRule(houses) {
|
|
@@ -54,11 +55,14 @@ export async function allowHouseInCodex(path) {
|
|
|
54
55
|
houses.push(house);
|
|
55
56
|
}
|
|
56
57
|
await mkdir(rules, { recursive: true });
|
|
57
|
-
const temporary = `${file}.${
|
|
58
|
+
const temporary = `${file}.${randomUUID()}`;
|
|
58
59
|
await writeFile(temporary, houseRule(houses));
|
|
59
60
|
await rename(temporary, file);
|
|
60
61
|
}
|
|
61
62
|
async function nothing() { }
|
|
63
|
+
function answered(error) {
|
|
64
|
+
return typeof error === 'object' && error !== null ? error : {};
|
|
65
|
+
}
|
|
62
66
|
function bridgeInClaudeSandbox(bridge) {
|
|
63
67
|
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
64
68
|
}
|
|
@@ -76,6 +80,10 @@ export const CLIS = {
|
|
|
76
80
|
args: [],
|
|
77
81
|
fullAccess: 'agent-full-access',
|
|
78
82
|
sessionMeta: () => ({}),
|
|
83
|
+
unknownSession: (error) => {
|
|
84
|
+
const { code, data } = answered(error);
|
|
85
|
+
return code === -32603 && typeof data?.details === 'string' && data.details.startsWith('no rollout found');
|
|
86
|
+
},
|
|
79
87
|
renames: true,
|
|
80
88
|
allowHouse: allowHouseInCodex,
|
|
81
89
|
signedIn: { command: ['login', 'status'] },
|
|
@@ -105,6 +113,7 @@ export const CLIS = {
|
|
|
105
113
|
emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
|
|
106
114
|
},
|
|
107
115
|
}),
|
|
116
|
+
unknownSession: (error) => answered(error).code === -32002,
|
|
108
117
|
renames: false,
|
|
109
118
|
allowHouse: nothing,
|
|
110
119
|
signedIn: { command: ['auth', 'status'] },
|
|
@@ -119,6 +128,7 @@ export const CLIS = {
|
|
|
119
128
|
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
120
129
|
fullAccess: null,
|
|
121
130
|
sessionMeta: () => ({}),
|
|
131
|
+
unknownSession: (error) => answered(error).data?.code === 'FS_NOT_FOUND',
|
|
122
132
|
renames: false,
|
|
123
133
|
allowHouse: nothing,
|
|
124
134
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
@@ -127,7 +137,7 @@ export const CLIS = {
|
|
|
127
137
|
},
|
|
128
138
|
};
|
|
129
139
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
130
|
-
export const
|
|
140
|
+
export const KIT_RELEASE = KIT_PACKAGE.version;
|
|
131
141
|
const PROXY = /^https?_proxy$/i;
|
|
132
142
|
const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
|
|
133
143
|
// An install still running after fifteen minutes is ended and reported as failed, so a hung download cannot hold its CLI's report.
|
|
@@ -199,7 +209,7 @@ export async function installAdapter(kind, signal) {
|
|
|
199
209
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
200
210
|
if ((await installedAdapter(kind)) === release)
|
|
201
211
|
return null;
|
|
202
|
-
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
212
|
+
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--cache', join(kitHome(), 'npm-cache'), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
203
213
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
204
214
|
}
|
|
205
215
|
export async function installCli(kind, signal) {
|
|
@@ -224,6 +234,12 @@ export async function loginPath() {
|
|
|
224
234
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
225
235
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
226
236
|
}
|
|
237
|
+
export async function writeCodexRule() {
|
|
238
|
+
const shell = await loginPath().catch(() => '');
|
|
239
|
+
await allowHouseInCodex([dirname(process.execPath), process.env.PATH ?? '', shell].join(delimiter)).catch((error) => {
|
|
240
|
+
throw new Error(`Codex's rule for house could not be written: ${error.message}`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
227
243
|
export async function versionOf(path, signal) {
|
|
228
244
|
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
229
245
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { subscribe } from 'node:diagnostics_channel';
|
|
2
|
+
import { HouseRefusal, retryDelay } from './api.js';
|
|
3
|
+
import { end, REPLACED, signInAgain } from './end.js';
|
|
4
|
+
import { readEnrolment } from './home.js';
|
|
5
|
+
import { relayed } from './relay.js';
|
|
6
|
+
import { endUnserved, KIT_WIRE } from './wire.js';
|
|
7
|
+
const PATH = '/kit/stream';
|
|
8
|
+
const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
|
|
9
|
+
const REFUSAL_HEADER = 'x-house-refusal';
|
|
10
|
+
// House beats each Kit connection every thirty seconds, so two silent beats mean this Kit process cannot hear House.
|
|
11
|
+
export const CONNECTION_SILENCE_MS = 60_000;
|
|
12
|
+
const SYSTEM_CONNECTION_CLOCK = {
|
|
13
|
+
later: (delay, callback) => setTimeout(callback, delay),
|
|
14
|
+
clear: (timer) => clearTimeout(timer),
|
|
15
|
+
};
|
|
16
|
+
export class ConnectionSilence {
|
|
17
|
+
timer = null;
|
|
18
|
+
silent;
|
|
19
|
+
clock;
|
|
20
|
+
interval;
|
|
21
|
+
constructor(silent, clock = SYSTEM_CONNECTION_CLOCK, interval = CONNECTION_SILENCE_MS) {
|
|
22
|
+
this.silent = silent;
|
|
23
|
+
this.clock = clock;
|
|
24
|
+
this.interval = interval;
|
|
25
|
+
}
|
|
26
|
+
heard() {
|
|
27
|
+
this.stop();
|
|
28
|
+
this.timer = this.clock.later(this.interval, () => {
|
|
29
|
+
this.timer = null;
|
|
30
|
+
this.silent();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
stop() {
|
|
34
|
+
if (this.timer === null)
|
|
35
|
+
return;
|
|
36
|
+
this.clock.clear(this.timer);
|
|
37
|
+
this.timer = null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function connectionUrl(house) {
|
|
41
|
+
const url = new URL(PATH, house);
|
|
42
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
function header(headers, name) {
|
|
46
|
+
for (let index = 0; index < headers.length; index += 2) {
|
|
47
|
+
if (headers[index].toString('latin1').toLowerCase() === name)
|
|
48
|
+
return headers[index + 1].toString('latin1');
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
let refused = null;
|
|
53
|
+
subscribe('undici:request:headers', (message) => {
|
|
54
|
+
const { request, response } = message;
|
|
55
|
+
if (!request.path.endsWith(PATH))
|
|
56
|
+
return;
|
|
57
|
+
refused = new HouseRefusal(PATH, response.statusCode, header(response.headers, REFUSAL_HEADER) || response.statusText);
|
|
58
|
+
});
|
|
59
|
+
export function holdConnection(handlers) {
|
|
60
|
+
let connection = null;
|
|
61
|
+
let failures = 0;
|
|
62
|
+
const connect = async () => {
|
|
63
|
+
const { house, credential } = (await readEnrolment());
|
|
64
|
+
refused = null;
|
|
65
|
+
const opening = new WebSocket(relayed(connectionUrl(house)), {
|
|
66
|
+
protocols: [SUBPROTOCOL],
|
|
67
|
+
headers: { authorization: `Bearer ${credential}` },
|
|
68
|
+
});
|
|
69
|
+
let opened = false;
|
|
70
|
+
let silence = null;
|
|
71
|
+
const closed = (event) => {
|
|
72
|
+
silence?.stop();
|
|
73
|
+
silence = null;
|
|
74
|
+
if (connection === opening)
|
|
75
|
+
connection = null;
|
|
76
|
+
process.stderr.write(`kit: connection closed ${event.code} ${event.reason}\n`);
|
|
77
|
+
if (event.reason === 'replaced')
|
|
78
|
+
end(REPLACED);
|
|
79
|
+
if (event.reason === 'credential_rejected' || refused?.status === 401) {
|
|
80
|
+
void signInAgain(credential);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (refused !== null)
|
|
84
|
+
endUnserved(refused.text);
|
|
85
|
+
if (!opened && refused !== null)
|
|
86
|
+
process.stderr.write(`kit: ${refused.message}\n`);
|
|
87
|
+
// A closed connection reopens in one to three seconds, spread so Kit processes one restart closed return apart; an opening House refused or left unanswered backs off on the jittered curve, retryable or not, since a resident outlives a House deploy.
|
|
88
|
+
setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
|
|
89
|
+
};
|
|
90
|
+
opening.onopen = () => {
|
|
91
|
+
opened = true;
|
|
92
|
+
failures = 0;
|
|
93
|
+
connection = opening;
|
|
94
|
+
silence = new ConnectionSilence(() => {
|
|
95
|
+
if (connection !== opening)
|
|
96
|
+
return;
|
|
97
|
+
connection = null;
|
|
98
|
+
opening.onclose = null;
|
|
99
|
+
opening.close();
|
|
100
|
+
closed(new CloseEvent('close', { code: 1006, reason: 'heartbeat_missed' }));
|
|
101
|
+
});
|
|
102
|
+
silence.heard();
|
|
103
|
+
handlers.opened();
|
|
104
|
+
};
|
|
105
|
+
opening.onmessage = (event) => {
|
|
106
|
+
silence?.heard();
|
|
107
|
+
const arrived = JSON.parse(String(event.data));
|
|
108
|
+
if (arrived.type === 'input')
|
|
109
|
+
handlers.call(arrived);
|
|
110
|
+
else if (arrived.type !== 'beat')
|
|
111
|
+
handlers.notification(arrived);
|
|
112
|
+
};
|
|
113
|
+
opening.onerror = () => undefined;
|
|
114
|
+
opening.onclose = closed;
|
|
115
|
+
};
|
|
116
|
+
void connect();
|
|
117
|
+
return {
|
|
118
|
+
send(notification) {
|
|
119
|
+
if (connection === null || connection.readyState !== WebSocket.OPEN)
|
|
120
|
+
return false;
|
|
121
|
+
connection.send(JSON.stringify(notification));
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|