@agentshouse/kit 0.2.1-c5262a8cc063d → 0.2.1-c5883630417e1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -26
- package/dist/acp.js +16 -93
- package/dist/agents.js +104 -42
- package/dist/api.js +36 -3
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +36 -52
- package/dist/chatgpt.js +26 -0
- package/dist/clis.js +128 -65
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +288 -236
- package/dist/copies.js +167 -139
- package/dist/end.js +22 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +521 -0
- package/dist/files.js +23 -2
- package/dist/git.js +11 -8
- package/dist/home.js +81 -6
- package/dist/house-main.js +186 -56
- package/dist/instructions.js +9 -32
- package/dist/kit-main.js +54 -6
- package/dist/login.js +109 -103
- package/dist/logout.js +16 -13
- package/dist/mcp.js +2 -1
- package/dist/parts.js +82 -0
- package/dist/placement.js +48 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +20 -0
- package/dist/resident.js +38 -27
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +4 -1
- package/dist/service-main.js +9 -0
- package/dist/service.js +33 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -9
- package/dist/skills.js +81 -12
- package/dist/stream.js +37 -6
- package/dist/transcription.js +57 -9
- package/dist/update.js +30 -6
- package/dist/wire.js +6 -0
- package/dist/work.js +348 -0
- package/how-we-work.md +13 -3
- package/package.json +3 -2
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/dist/blocks.js +0 -51
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/bootstrap.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-c5883630417e1",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"macos": {
|
|
6
6
|
"asset": {
|
|
7
7
|
"name": "connect-linux.sh",
|
|
8
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
8
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c5883630417e1/connect-linux.sh"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"linux": {
|
|
12
12
|
"asset": {
|
|
13
13
|
"name": "connect-linux.sh",
|
|
14
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
14
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c5883630417e1/connect-linux.sh"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"windows": {
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c5883630417e1/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/bridge.js
CHANGED
|
@@ -6,15 +6,15 @@ import { createServer, request as httpRequest } from 'node:http';
|
|
|
6
6
|
import { request as httpsRequest } from 'node:https';
|
|
7
7
|
import { delimiter, dirname, join } from 'node:path';
|
|
8
8
|
import { parse } from 'yaml';
|
|
9
|
-
import { HouseRefusal, retryDelay } from './api.js';
|
|
9
|
+
import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
|
|
10
10
|
import { loginPath } from './clis.js';
|
|
11
11
|
import { changedPaths, } from './copies.js';
|
|
12
12
|
import { kitHome, readEnrolment } from './home.js';
|
|
13
13
|
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
14
|
-
import { operationId
|
|
15
|
-
import { refusalOf } from './refusals.js';
|
|
14
|
+
import { operationId } from './operation.js';
|
|
15
|
+
import { refusalOf, retryable } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
16
17
|
const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
17
|
-
const ATTACHMENTS = '/kit/attachments/upload';
|
|
18
18
|
export async function received(request) {
|
|
19
19
|
let text = '';
|
|
20
20
|
request.setEncoding('utf8');
|
|
@@ -22,7 +22,8 @@ export async function received(request) {
|
|
|
22
22
|
text += chunk;
|
|
23
23
|
return text;
|
|
24
24
|
}
|
|
25
|
-
function relay(request, response,
|
|
25
|
+
function relay(request, response, house, authorization) {
|
|
26
|
+
const target = relayed(house);
|
|
26
27
|
const send = target.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
27
28
|
const forwarded = send(target, {
|
|
28
29
|
method: request.method,
|
|
@@ -38,8 +39,6 @@ function refusalIn(text, status) {
|
|
|
38
39
|
return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
|
|
39
40
|
}
|
|
40
41
|
function answered(forwarded) {
|
|
41
|
-
if (forwarded.status >= 500)
|
|
42
|
-
throw new HouseRefusal('/', forwarded.status, forwarded.text);
|
|
43
42
|
if (forwarded.status !== 200)
|
|
44
43
|
return { refused: refusalIn(forwarded.text, forwarded.status) };
|
|
45
44
|
const answer = JSON.parse(forwarded.text);
|
|
@@ -63,16 +62,11 @@ function pendingSeconds(forwarded) {
|
|
|
63
62
|
function resendAfter(forwarded, attempt) {
|
|
64
63
|
if (forwarded === null)
|
|
65
64
|
return retryDelay(attempt);
|
|
66
|
-
if (forwarded.status
|
|
67
|
-
return
|
|
65
|
+
if (forwarded.status !== 200)
|
|
66
|
+
return retryable(forwarded.text) === false ? null : retryDelay(attempt);
|
|
68
67
|
const seconds = pendingSeconds(forwarded);
|
|
69
68
|
return seconds === null ? null : seconds * 1000;
|
|
70
69
|
}
|
|
71
|
-
function versioned(conversationId, text) {
|
|
72
|
-
const file = JSON.parse(text);
|
|
73
|
-
const identity = JSON.stringify([conversationId, file.room_ref ?? null, file.name, file.sha256]);
|
|
74
|
-
return JSON.stringify({ ...file, version: uuidOf(createHash('sha256').update(identity).digest(), 8) });
|
|
75
|
-
}
|
|
76
70
|
function editAnswer(forwarded) {
|
|
77
71
|
const answer = answered(forwarded);
|
|
78
72
|
if ('refused' in answer)
|
|
@@ -103,6 +97,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
103
97
|
const after = resendAfter(forwarded, attempt);
|
|
104
98
|
if (after === null)
|
|
105
99
|
return forwarded;
|
|
100
|
+
// A Tool call House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
|
|
106
101
|
await delay(after, undefined, { signal: closed.signal });
|
|
107
102
|
}
|
|
108
103
|
};
|
|
@@ -117,46 +112,32 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
117
112
|
if ('refused' in prepared)
|
|
118
113
|
return prepared;
|
|
119
114
|
const { transfer } = parse(prepared.text);
|
|
120
|
-
|
|
121
|
-
method: 'POST',
|
|
122
|
-
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
123
|
-
body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
|
|
124
|
-
signal: closed.signal,
|
|
125
|
-
});
|
|
126
|
-
const text = await posted.text();
|
|
127
|
-
if (posted.status >= 500)
|
|
128
|
-
throw new HouseRefusal('/', posted.status, text);
|
|
129
|
-
if (!posted.ok)
|
|
130
|
-
return { refused: refusalIn(text, posted.status) };
|
|
131
|
-
const held = JSON.parse(text).transfer;
|
|
132
|
-
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
133
|
-
};
|
|
134
|
-
let mutations = null;
|
|
135
|
-
const writes = async (tool) => {
|
|
136
|
-
mutations ??= house
|
|
137
|
-
.post('/kit/tools/mutations', {}, closed.signal)
|
|
138
|
-
.then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
|
|
115
|
+
let text;
|
|
139
116
|
try {
|
|
140
|
-
|
|
117
|
+
text = await retrying(async () => {
|
|
118
|
+
const posted = await fetch(relayed(transfer.url), {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
121
|
+
body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
|
|
122
|
+
signal: closed.signal,
|
|
123
|
+
});
|
|
124
|
+
const answer = await posted.text();
|
|
125
|
+
if (!posted.ok)
|
|
126
|
+
throw new HouseRefusal('/', posted.status, answer);
|
|
127
|
+
return answer;
|
|
128
|
+
}, closed.signal);
|
|
141
129
|
}
|
|
142
130
|
catch (error) {
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
if (!finalRefusal(error))
|
|
132
|
+
throw error;
|
|
133
|
+
return { refused: refusalIn(error.text, error.status) };
|
|
145
134
|
}
|
|
135
|
+
const held = JSON.parse(text).transfer;
|
|
136
|
+
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
146
137
|
};
|
|
147
|
-
const forward =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
151
|
-
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
152
|
-
}
|
|
153
|
-
const answer = await fetch(new URL(path, origin), {
|
|
154
|
-
method: 'POST',
|
|
155
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
156
|
-
body: versioned(conversationId, text),
|
|
157
|
-
signal: closed.signal,
|
|
158
|
-
});
|
|
159
|
-
return { status: answer.status, text: await answer.text() };
|
|
138
|
+
const forward = (text) => {
|
|
139
|
+
const message = JSON.parse(text);
|
|
140
|
+
return send(message, message.method === 'tools/call' ? operationId() : null);
|
|
160
141
|
};
|
|
161
142
|
const caller = {
|
|
162
143
|
key: `conversation:${conversationId}`,
|
|
@@ -178,11 +159,13 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
178
159
|
const { cwd, commit } = JSON.parse(text);
|
|
179
160
|
return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
|
|
180
161
|
}
|
|
162
|
+
if (path === '/git/fetch')
|
|
163
|
+
return { status: 200, text: JSON.stringify(await copies.fetch()) };
|
|
181
164
|
if (path === '/git/observed') {
|
|
182
165
|
await copies.observed(JSON.parse(text));
|
|
183
166
|
return { status: 200, text: '{}' };
|
|
184
167
|
}
|
|
185
|
-
return path === '/'
|
|
168
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
186
169
|
};
|
|
187
170
|
const sockets = join(kitHome(), 'bridges');
|
|
188
171
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
@@ -202,7 +185,8 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
202
185
|
response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
|
|
203
186
|
return;
|
|
204
187
|
}
|
|
205
|
-
|
|
188
|
+
const verb = request.url === '/git/push' || request.url === '/git/fetch';
|
|
189
|
+
response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
|
|
206
190
|
}
|
|
207
191
|
});
|
|
208
192
|
bridge.listen(socketPath);
|
|
@@ -229,7 +213,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
229
213
|
GIT_CONFIG_VALUE_0: app,
|
|
230
214
|
},
|
|
231
215
|
tool: async (name, args) => {
|
|
232
|
-
const forwarded = await forward(
|
|
216
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
233
217
|
if (forwarded.status !== 200)
|
|
234
218
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
235
219
|
const answer = JSON.parse(forwarded.text);
|
package/dist/chatgpt.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { kitHome, readOwnAgent, writeOwnAgent } from './home.js';
|
|
3
|
+
import { accepted, DEFAULT_HOUSE } from './login.js';
|
|
4
|
+
const KEY_SOURCE = 'New key for a script on Connections';
|
|
5
|
+
const USAGE = `usage: kit chatgpt --key <key> [--house <origin>]
|
|
6
|
+
--key <key> a key from ${KEY_SOURCE}
|
|
7
|
+
--house <origin> the House of the key, ${DEFAULT_HOUSE} unless one is stored
|
|
8
|
+
`;
|
|
9
|
+
export async function chatgpt(argv) {
|
|
10
|
+
const { values } = parseArgs({
|
|
11
|
+
args: argv,
|
|
12
|
+
options: { key: { type: 'string' }, house: { type: 'string' }, help: { type: 'boolean', default: false } },
|
|
13
|
+
});
|
|
14
|
+
if (values.help) {
|
|
15
|
+
process.stdout.write(USAGE);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (values.key === undefined)
|
|
19
|
+
throw new Error(`pass --key with a key from ${KEY_SOURCE}`);
|
|
20
|
+
const house = values.house ?? (await readOwnAgent())?.house ?? DEFAULT_HOUSE;
|
|
21
|
+
if (!(await accepted(house, values.key)))
|
|
22
|
+
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
23
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
24
|
+
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
25
|
+
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
26
|
+
}
|
package/dist/clis.js
CHANGED
|
@@ -1,48 +1,87 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { constants, readFileSync } from 'node:fs';
|
|
4
|
+
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { homedir, userInfo } from 'node:os';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
7
|
import { kitHome } from './home.js';
|
|
7
|
-
const
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
9
|
+
function houseRule(houses) {
|
|
10
|
+
return `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`;
|
|
11
|
+
}
|
|
12
|
+
const RAW_SUBTYPES = [
|
|
13
|
+
'background_tasks_changed',
|
|
14
|
+
'task_started',
|
|
15
|
+
'task_updated',
|
|
16
|
+
'task_notification',
|
|
17
|
+
'session_state_changed',
|
|
18
|
+
'hook_started',
|
|
19
|
+
'hook_response',
|
|
20
|
+
];
|
|
21
|
+
async function executable(path) {
|
|
22
|
+
try {
|
|
23
|
+
await access(path, constants.X_OK);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
21
28
|
}
|
|
22
|
-
return null;
|
|
23
29
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
async function listedHouses(file) {
|
|
31
|
+
let text;
|
|
32
|
+
try {
|
|
33
|
+
text = await readFile(file, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error.code === 'ENOENT')
|
|
37
|
+
return [];
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const listed = JSON.parse(HOUSE_EXECUTABLES.exec(text)?.[1] ?? '[]');
|
|
42
|
+
return Array.isArray(listed) ? listed.filter((house) => typeof house === 'string') : [];
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function allowHouseInCodex(path) {
|
|
49
|
+
const candidates = [...new Set(path.split(delimiter).filter((directory) => isAbsolute(directory)))].map((directory) => join(directory, 'house'));
|
|
50
|
+
const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
|
|
51
|
+
const file = join(rules, 'house.rules');
|
|
52
|
+
const houses = [];
|
|
53
|
+
for (const house of [...(await listedHouses(file)), ...candidates]) {
|
|
54
|
+
if (!houses.includes(house) && (await executable(house)))
|
|
55
|
+
houses.push(house);
|
|
56
|
+
}
|
|
57
|
+
await mkdir(rules, { recursive: true });
|
|
58
|
+
const temporary = `${file}.${randomUUID()}`;
|
|
59
|
+
await writeFile(temporary, houseRule(houses));
|
|
60
|
+
await rename(temporary, file);
|
|
61
|
+
}
|
|
62
|
+
async function nothing() { }
|
|
63
|
+
function bridgeInClaudeSandbox(bridge) {
|
|
64
|
+
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
31
65
|
}
|
|
32
66
|
export const CLIS = {
|
|
33
67
|
'codex-acp': {
|
|
34
68
|
bin: 'codex',
|
|
35
69
|
minimum: '0.159.1',
|
|
36
70
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
37
|
-
adapter: {
|
|
71
|
+
adapter: {
|
|
72
|
+
package: '@agentclientprotocol/codex-acp',
|
|
73
|
+
bin: 'codex-acp',
|
|
74
|
+
executable: 'CODEX_PATH',
|
|
75
|
+
env: { CODEX_CONFIG: JSON.stringify({ features: { shell_zsh_fork: true } }) },
|
|
76
|
+
},
|
|
38
77
|
args: [],
|
|
78
|
+
fullAccess: 'agent-full-access',
|
|
79
|
+
sessionMeta: () => ({}),
|
|
80
|
+
renames: true,
|
|
81
|
+
allowHouse: allowHouseInCodex,
|
|
39
82
|
signedIn: { command: ['login', 'status'] },
|
|
40
83
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
41
|
-
|
|
42
|
-
turnStarted: (notice) => threadStatus(notice) === 'active',
|
|
43
|
-
turnEnded: (notice) => threadStatus(notice) === 'idle',
|
|
44
|
-
job: asyncTask,
|
|
45
|
-
phase: airPhase,
|
|
84
|
+
air: ['asyncTasks', 'nativeSubagentSessions'],
|
|
46
85
|
},
|
|
47
86
|
'claude-agent-acp': {
|
|
48
87
|
bin: 'claude',
|
|
@@ -52,45 +91,49 @@ export const CLIS = {
|
|
|
52
91
|
package: '@agentclientprotocol/claude-agent-acp',
|
|
53
92
|
bin: 'claude-agent-acp',
|
|
54
93
|
executable: 'CLAUDE_CODE_EXECUTABLE',
|
|
55
|
-
env: { CLAUDE_CODE_ENTRYPOINT: 'claude-agent-acp' },
|
|
94
|
+
env: { CLAUDE_CODE_ENTRYPOINT: 'claude-agent-acp', IS_SANDBOX: '1' },
|
|
56
95
|
},
|
|
57
96
|
args: [],
|
|
97
|
+
fullAccess: 'bypassPermissions',
|
|
98
|
+
sessionMeta: (bridge, title) => ({
|
|
99
|
+
claudeCode: {
|
|
100
|
+
options: {
|
|
101
|
+
allowedTools: ['Bash(house:*)'],
|
|
102
|
+
includeHookEvents: true,
|
|
103
|
+
title,
|
|
104
|
+
settings: { sandbox: { network: bridgeInClaudeSandbox(bridge) } },
|
|
105
|
+
},
|
|
106
|
+
emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
renames: false,
|
|
110
|
+
allowHouse: nothing,
|
|
58
111
|
signedIn: { command: ['auth', 'status'] },
|
|
59
112
|
login: { args: ['auth', 'login', '--claudeai'], code: 'collect', rejected: 'Invalid code' },
|
|
60
|
-
|
|
61
|
-
turnStarted: () => false,
|
|
62
|
-
turnEnded: ({ method, params }) => method === 'session/update' &&
|
|
63
|
-
params?.update?.sessionUpdate === 'usage_update' &&
|
|
64
|
-
params.update._meta?.['_claude/origin'] !== undefined,
|
|
65
|
-
job: asyncTask,
|
|
66
|
-
phase: () => null,
|
|
113
|
+
air: ['asyncTasks', 'sessionFailure'],
|
|
67
114
|
},
|
|
68
115
|
'grok-build': {
|
|
69
116
|
bin: 'grok',
|
|
70
117
|
minimum: '1.0.46',
|
|
71
118
|
install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
72
119
|
adapter: null,
|
|
73
|
-
args: ['agent', '--no-leader', 'stdio'],
|
|
120
|
+
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
121
|
+
fullAccess: null,
|
|
122
|
+
sessionMeta: () => ({}),
|
|
123
|
+
renames: false,
|
|
124
|
+
allowHouse: nothing,
|
|
74
125
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
75
126
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
76
|
-
|
|
77
|
-
turnStarted: ({ method, params }) => method === '_x.ai/queue/changed' && params?.runningPromptId !== undefined,
|
|
78
|
-
turnEnded: ({ method, params }) => method === '_x.ai/session_notification' && params?.update?.sessionUpdate === 'turn_completed',
|
|
79
|
-
job: ({ method, params }) => {
|
|
80
|
-
if (method === '_x.ai/task_backgrounded')
|
|
81
|
-
return { id: params.update.task_id, running: true };
|
|
82
|
-
if (method === '_x.ai/task_completed')
|
|
83
|
-
return { id: params.update.task_snapshot.task_id, running: false };
|
|
84
|
-
return null;
|
|
85
|
-
},
|
|
86
|
-
phase: () => null,
|
|
127
|
+
air: ['asyncTasks'],
|
|
87
128
|
},
|
|
88
129
|
};
|
|
89
130
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
90
131
|
export const KIT_VERSION = KIT_PACKAGE.version;
|
|
91
132
|
const PROXY = /^https?_proxy$/i;
|
|
92
133
|
const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
|
|
134
|
+
// An install still running after fifteen minutes is ended and reported as failed, so a hung download cannot hold its CLI's report.
|
|
93
135
|
const INSTALL_MS = 15 * 60_000;
|
|
136
|
+
// A login shell or a version read is ended after half a minute, so a profile that waits for input cannot stall the report.
|
|
94
137
|
const READ_MS = 30_000;
|
|
95
138
|
const CAUSE_CHARACTERS = 4000;
|
|
96
139
|
function prefix(kind) {
|
|
@@ -118,8 +161,22 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
118
161
|
timeout: timeoutMs,
|
|
119
162
|
env: withoutProxy(),
|
|
120
163
|
detached,
|
|
121
|
-
signal,
|
|
164
|
+
signal: detached ? undefined : signal,
|
|
122
165
|
});
|
|
166
|
+
const stop = () => {
|
|
167
|
+
try {
|
|
168
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
};
|
|
172
|
+
if (detached && signal?.aborted)
|
|
173
|
+
stop();
|
|
174
|
+
if (detached)
|
|
175
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
176
|
+
const ended = (ran) => {
|
|
177
|
+
signal?.removeEventListener('abort', stop);
|
|
178
|
+
resolve(ran);
|
|
179
|
+
};
|
|
123
180
|
let stdout = '';
|
|
124
181
|
let output = '';
|
|
125
182
|
child.stdout.setEncoding('utf8').on('data', (chunk) => {
|
|
@@ -129,25 +186,25 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
129
186
|
child.stderr.setEncoding('utf8').on('data', (chunk) => {
|
|
130
187
|
output += chunk;
|
|
131
188
|
});
|
|
132
|
-
child.on('error', (error) =>
|
|
133
|
-
child.on('close', (status) =>
|
|
189
|
+
child.on('error', (error) => ended({ status: null, stdout, output: error.message }));
|
|
190
|
+
child.on('close', (status) => ended({ status, stdout, output }));
|
|
134
191
|
});
|
|
135
192
|
}
|
|
136
193
|
export function failureOf(ran, program) {
|
|
137
194
|
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
138
195
|
}
|
|
139
|
-
export async function installAdapter(kind) {
|
|
196
|
+
export async function installAdapter(kind, signal) {
|
|
140
197
|
const adapter = CLIS[kind].adapter;
|
|
141
198
|
if (adapter === null)
|
|
142
199
|
return null;
|
|
143
200
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
144
201
|
if ((await installedAdapter(kind)) === release)
|
|
145
202
|
return null;
|
|
146
|
-
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS);
|
|
203
|
+
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);
|
|
147
204
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
148
205
|
}
|
|
149
|
-
export async function installCli(kind) {
|
|
150
|
-
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true);
|
|
206
|
+
export async function installCli(kind, signal) {
|
|
207
|
+
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
|
|
151
208
|
return ran.status === 0 ? null : failureOf(ran, 'the install');
|
|
152
209
|
}
|
|
153
210
|
function loginShell() {
|
|
@@ -156,11 +213,11 @@ function loginShell() {
|
|
|
156
213
|
const FOUND = 'house-kit-cli ';
|
|
157
214
|
const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
|
|
158
215
|
const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$HOME/.local/bin" ;; esac; export PATH';
|
|
159
|
-
function inLoginShell(script, argument = '') {
|
|
160
|
-
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true);
|
|
216
|
+
function inLoginShell(script, argument = '', signal) {
|
|
217
|
+
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true, signal);
|
|
161
218
|
}
|
|
162
|
-
export async function locate(kind) {
|
|
163
|
-
const ran = await inLoginShell(LOOKUP, CLIS[kind].bin);
|
|
219
|
+
export async function locate(kind, signal) {
|
|
220
|
+
const ran = await inLoginShell(LOOKUP, CLIS[kind].bin, signal);
|
|
164
221
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
|
|
165
222
|
}
|
|
166
223
|
export async function loginPath() {
|
|
@@ -168,8 +225,14 @@ export async function loginPath() {
|
|
|
168
225
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
169
226
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
170
227
|
}
|
|
171
|
-
export async function
|
|
172
|
-
|
|
228
|
+
export async function writeCodexRule() {
|
|
229
|
+
const shell = await loginPath().catch(() => '');
|
|
230
|
+
await allowHouseInCodex([dirname(process.execPath), process.env.PATH ?? '', shell].join(delimiter)).catch((error) => {
|
|
231
|
+
throw new Error(`Codex's rule for house could not be written: ${error.message}`);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export async function versionOf(path, signal) {
|
|
235
|
+
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
173
236
|
}
|
|
174
237
|
function numbers(version) {
|
|
175
238
|
return version.split(/[.-]/, 3).map(Number);
|
|
@@ -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
|
+
});
|