@agentshouse/kit 0.2.1-c2c600f2d2b30 → 0.2.1-c2f0855c31ca3
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 +26 -26
- package/dist/acp.js +16 -93
- package/dist/agents.js +70 -32
- package/dist/api.js +25 -3
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +33 -52
- package/dist/chatgpt.js +26 -0
- package/dist/clis.js +116 -70
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +284 -236
- package/dist/copies.js +148 -113
- package/dist/end.js +18 -4
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +521 -0
- package/dist/files.js +21 -1
- package/dist/git.js +11 -8
- package/dist/home.js +78 -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 +94 -104
- package/dist/logout.js +16 -13
- package/dist/parts.js +82 -0
- package/dist/placement.js +48 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +38 -27
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +2 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +33 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -10
- package/dist/skills.js +80 -12
- package/dist/stream.js +12 -7
- package/dist/transcription.js +57 -11
- 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/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/bridge.js
CHANGED
|
@@ -6,16 +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
16
|
import { relayed } from './relay.js';
|
|
17
17
|
const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
18
|
-
const ATTACHMENTS = '/kit/attachments/upload';
|
|
19
18
|
export async function received(request) {
|
|
20
19
|
let text = '';
|
|
21
20
|
request.setEncoding('utf8');
|
|
@@ -40,8 +39,6 @@ function refusalIn(text, status) {
|
|
|
40
39
|
return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
|
|
41
40
|
}
|
|
42
41
|
function answered(forwarded) {
|
|
43
|
-
if (forwarded.status >= 500)
|
|
44
|
-
throw new HouseRefusal('/', forwarded.status, forwarded.text);
|
|
45
42
|
if (forwarded.status !== 200)
|
|
46
43
|
return { refused: refusalIn(forwarded.text, forwarded.status) };
|
|
47
44
|
const answer = JSON.parse(forwarded.text);
|
|
@@ -65,16 +62,11 @@ function pendingSeconds(forwarded) {
|
|
|
65
62
|
function resendAfter(forwarded, attempt) {
|
|
66
63
|
if (forwarded === null)
|
|
67
64
|
return retryDelay(attempt);
|
|
68
|
-
if (forwarded.status
|
|
69
|
-
return
|
|
65
|
+
if (forwarded.status !== 200)
|
|
66
|
+
return retryable(forwarded.text) === false ? null : retryDelay(attempt);
|
|
70
67
|
const seconds = pendingSeconds(forwarded);
|
|
71
68
|
return seconds === null ? null : seconds * 1000;
|
|
72
69
|
}
|
|
73
|
-
function versioned(conversationId, text) {
|
|
74
|
-
const file = JSON.parse(text);
|
|
75
|
-
const identity = JSON.stringify([conversationId, file.room_ref ?? null, file.name, file.sha256]);
|
|
76
|
-
return JSON.stringify({ ...file, version: uuidOf(createHash('sha256').update(identity).digest(), 8) });
|
|
77
|
-
}
|
|
78
70
|
function editAnswer(forwarded) {
|
|
79
71
|
const answer = answered(forwarded);
|
|
80
72
|
if ('refused' in answer)
|
|
@@ -105,7 +97,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
105
97
|
const after = resendAfter(forwarded, attempt);
|
|
106
98
|
if (after === null)
|
|
107
99
|
return forwarded;
|
|
108
|
-
// A
|
|
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.
|
|
109
101
|
await delay(after, undefined, { signal: closed.signal });
|
|
110
102
|
}
|
|
111
103
|
};
|
|
@@ -120,46 +112,32 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
120
112
|
if ('refused' in prepared)
|
|
121
113
|
return prepared;
|
|
122
114
|
const { transfer } = parse(prepared.text);
|
|
123
|
-
|
|
124
|
-
method: 'POST',
|
|
125
|
-
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
126
|
-
body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
|
|
127
|
-
signal: closed.signal,
|
|
128
|
-
});
|
|
129
|
-
const text = await posted.text();
|
|
130
|
-
if (posted.status >= 500)
|
|
131
|
-
throw new HouseRefusal('/', posted.status, text);
|
|
132
|
-
if (!posted.ok)
|
|
133
|
-
return { refused: refusalIn(text, posted.status) };
|
|
134
|
-
const held = JSON.parse(text).transfer;
|
|
135
|
-
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
136
|
-
};
|
|
137
|
-
let mutations = null;
|
|
138
|
-
const writes = async (tool) => {
|
|
139
|
-
mutations ??= house
|
|
140
|
-
.post('/kit/tools/mutations', {}, closed.signal)
|
|
141
|
-
.then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
|
|
115
|
+
let text;
|
|
142
116
|
try {
|
|
143
|
-
|
|
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);
|
|
144
129
|
}
|
|
145
130
|
catch (error) {
|
|
146
|
-
|
|
147
|
-
|
|
131
|
+
if (!finalRefusal(error))
|
|
132
|
+
throw error;
|
|
133
|
+
return { refused: refusalIn(error.text, error.status) };
|
|
148
134
|
}
|
|
135
|
+
const held = JSON.parse(text).transfer;
|
|
136
|
+
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
149
137
|
};
|
|
150
|
-
const forward =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
154
|
-
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
155
|
-
}
|
|
156
|
-
const answer = await fetch(relayed(new URL(path, origin)), {
|
|
157
|
-
method: 'POST',
|
|
158
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
159
|
-
body: versioned(conversationId, text),
|
|
160
|
-
signal: closed.signal,
|
|
161
|
-
});
|
|
162
|
-
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);
|
|
163
141
|
};
|
|
164
142
|
const caller = {
|
|
165
143
|
key: `conversation:${conversationId}`,
|
|
@@ -181,11 +159,13 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
181
159
|
const { cwd, commit } = JSON.parse(text);
|
|
182
160
|
return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
|
|
183
161
|
}
|
|
162
|
+
if (path === '/git/fetch')
|
|
163
|
+
return { status: 200, text: JSON.stringify(await copies.fetch()) };
|
|
184
164
|
if (path === '/git/observed') {
|
|
185
165
|
await copies.observed(JSON.parse(text));
|
|
186
166
|
return { status: 200, text: '{}' };
|
|
187
167
|
}
|
|
188
|
-
return path === '/'
|
|
168
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
189
169
|
};
|
|
190
170
|
const sockets = join(kitHome(), 'bridges');
|
|
191
171
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
@@ -205,7 +185,8 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
205
185
|
response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
|
|
206
186
|
return;
|
|
207
187
|
}
|
|
208
|
-
|
|
188
|
+
const verb = request.url === '/git/push' || request.url === '/git/fetch';
|
|
189
|
+
response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
|
|
209
190
|
}
|
|
210
191
|
});
|
|
211
192
|
bridge.listen(socketPath);
|
|
@@ -232,7 +213,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
232
213
|
GIT_CONFIG_VALUE_0: app,
|
|
233
214
|
},
|
|
234
215
|
tool: async (name, args) => {
|
|
235
|
-
const forwarded = await forward(
|
|
216
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
236
217
|
if (forwarded.status !== 200)
|
|
237
218
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
238
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,57 +1,87 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
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';
|
|
4
5
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
7
|
import { kitHome } from './home.js';
|
|
7
|
-
const
|
|
8
|
-
|
|
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;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
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'));
|
|
9
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
|
+
}
|
|
10
57
|
await mkdir(rules, { recursive: true });
|
|
11
|
-
|
|
58
|
+
const temporary = `${file}.${randomUUID()}`;
|
|
59
|
+
await writeFile(temporary, houseRule(houses));
|
|
60
|
+
await rename(temporary, file);
|
|
12
61
|
}
|
|
13
62
|
async function nothing() { }
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
return undefined;
|
|
17
|
-
return params.update._meta?.codex?.threadStatus?.type;
|
|
18
|
-
}
|
|
19
|
-
function asyncTask({ method, params }) {
|
|
20
|
-
const update = params?.update;
|
|
21
|
-
if (method !== 'session/update' || update?.asyncTaskId === undefined)
|
|
22
|
-
return null;
|
|
23
|
-
if (update.sessionUpdate === 'async_task_spawned')
|
|
24
|
-
return { id: update.asyncTaskId, running: true };
|
|
25
|
-
if (update.sessionUpdate === 'async_task_state_update' && !RUNNING_JOB.has(update.state ?? '')) {
|
|
26
|
-
return { id: update.asyncTaskId, running: false };
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
function airPhase({ _meta }) {
|
|
31
|
-
const phase = _meta?.jetbrains?.air?.phase;
|
|
32
|
-
if (phase === 'commentary')
|
|
33
|
-
return 'note';
|
|
34
|
-
if (phase === 'final_answer')
|
|
35
|
-
return 'answer';
|
|
36
|
-
return null;
|
|
63
|
+
function bridgeInClaudeSandbox(bridge) {
|
|
64
|
+
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
37
65
|
}
|
|
38
66
|
export const CLIS = {
|
|
39
67
|
'codex-acp': {
|
|
40
68
|
bin: 'codex',
|
|
41
69
|
minimum: '0.159.1',
|
|
42
70
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
43
|
-
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
|
+
},
|
|
44
77
|
args: [],
|
|
45
78
|
fullAccess: 'agent-full-access',
|
|
46
|
-
sessionMeta: {},
|
|
79
|
+
sessionMeta: () => ({}),
|
|
80
|
+
renames: true,
|
|
47
81
|
allowHouse: allowHouseInCodex,
|
|
48
82
|
signedIn: { command: ['login', 'status'] },
|
|
49
83
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
50
|
-
|
|
51
|
-
turnStarted: (notice) => threadStatus(notice) === 'active',
|
|
52
|
-
turnEnded: (notice) => threadStatus(notice) === 'idle',
|
|
53
|
-
job: asyncTask,
|
|
54
|
-
phase: airPhase,
|
|
84
|
+
air: ['asyncTasks', 'nativeSubagentSessions'],
|
|
55
85
|
},
|
|
56
86
|
'claude-agent-acp': {
|
|
57
87
|
bin: 'claude',
|
|
@@ -65,40 +95,36 @@ export const CLIS = {
|
|
|
65
95
|
},
|
|
66
96
|
args: [],
|
|
67
97
|
fullAccess: 'bypassPermissions',
|
|
68
|
-
sessionMeta:
|
|
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,
|
|
69
110
|
allowHouse: nothing,
|
|
70
111
|
signedIn: { command: ['auth', 'status'] },
|
|
71
112
|
login: { args: ['auth', 'login', '--claudeai'], code: 'collect', rejected: 'Invalid code' },
|
|
72
|
-
|
|
73
|
-
turnStarted: () => false,
|
|
74
|
-
turnEnded: ({ method, params }) => method === 'session/update' &&
|
|
75
|
-
params?.update?.sessionUpdate === 'usage_update' &&
|
|
76
|
-
params.update._meta?.['_claude/origin'] !== undefined,
|
|
77
|
-
job: asyncTask,
|
|
78
|
-
phase: () => null,
|
|
113
|
+
air: ['asyncTasks', 'sessionFailure'],
|
|
79
114
|
},
|
|
80
115
|
'grok-build': {
|
|
81
116
|
bin: 'grok',
|
|
82
117
|
minimum: '1.0.46',
|
|
83
118
|
install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
84
119
|
adapter: null,
|
|
85
|
-
args: ['agent', '--always-approve', '--no-leader', 'stdio'],
|
|
120
|
+
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
86
121
|
fullAccess: null,
|
|
87
|
-
sessionMeta: {},
|
|
122
|
+
sessionMeta: () => ({}),
|
|
123
|
+
renames: false,
|
|
88
124
|
allowHouse: nothing,
|
|
89
125
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
90
126
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
91
|
-
|
|
92
|
-
turnStarted: ({ method, params }) => method === '_x.ai/queue/changed' && params?.runningPromptId !== undefined,
|
|
93
|
-
turnEnded: ({ method, params }) => method === '_x.ai/session_notification' && params?.update?.sessionUpdate === 'turn_completed',
|
|
94
|
-
job: ({ method, params }) => {
|
|
95
|
-
if (method === '_x.ai/task_backgrounded')
|
|
96
|
-
return { id: params.update.task_id, running: true };
|
|
97
|
-
if (method === '_x.ai/task_completed')
|
|
98
|
-
return { id: params.update.task_snapshot.task_id, running: false };
|
|
99
|
-
return null;
|
|
100
|
-
},
|
|
101
|
-
phase: () => null,
|
|
127
|
+
air: ['asyncTasks'],
|
|
102
128
|
},
|
|
103
129
|
};
|
|
104
130
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -135,8 +161,22 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
135
161
|
timeout: timeoutMs,
|
|
136
162
|
env: withoutProxy(),
|
|
137
163
|
detached,
|
|
138
|
-
signal,
|
|
164
|
+
signal: detached ? undefined : signal,
|
|
139
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
|
+
};
|
|
140
180
|
let stdout = '';
|
|
141
181
|
let output = '';
|
|
142
182
|
child.stdout.setEncoding('utf8').on('data', (chunk) => {
|
|
@@ -146,25 +186,25 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
146
186
|
child.stderr.setEncoding('utf8').on('data', (chunk) => {
|
|
147
187
|
output += chunk;
|
|
148
188
|
});
|
|
149
|
-
child.on('error', (error) =>
|
|
150
|
-
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 }));
|
|
151
191
|
});
|
|
152
192
|
}
|
|
153
193
|
export function failureOf(ran, program) {
|
|
154
194
|
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
155
195
|
}
|
|
156
|
-
export async function installAdapter(kind) {
|
|
196
|
+
export async function installAdapter(kind, signal) {
|
|
157
197
|
const adapter = CLIS[kind].adapter;
|
|
158
198
|
if (adapter === null)
|
|
159
199
|
return null;
|
|
160
200
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
161
201
|
if ((await installedAdapter(kind)) === release)
|
|
162
202
|
return null;
|
|
163
|
-
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);
|
|
164
204
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
165
205
|
}
|
|
166
|
-
export async function installCli(kind) {
|
|
167
|
-
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);
|
|
168
208
|
return ran.status === 0 ? null : failureOf(ran, 'the install');
|
|
169
209
|
}
|
|
170
210
|
function loginShell() {
|
|
@@ -173,11 +213,11 @@ function loginShell() {
|
|
|
173
213
|
const FOUND = 'house-kit-cli ';
|
|
174
214
|
const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
|
|
175
215
|
const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$HOME/.local/bin" ;; esac; export PATH';
|
|
176
|
-
function inLoginShell(script, argument = '') {
|
|
177
|
-
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);
|
|
178
218
|
}
|
|
179
|
-
export async function locate(kind) {
|
|
180
|
-
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);
|
|
181
221
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
|
|
182
222
|
}
|
|
183
223
|
export async function loginPath() {
|
|
@@ -185,8 +225,14 @@ export async function loginPath() {
|
|
|
185
225
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
186
226
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
187
227
|
}
|
|
188
|
-
export async function
|
|
189
|
-
|
|
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;
|
|
190
236
|
}
|
|
191
237
|
function numbers(version) {
|
|
192
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
|
+
});
|