@agentshouse/kit 0.2.1-c30728be20485 → 0.2.1-c39de3ed26c1b
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 +9 -5
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +11 -37
- package/dist/chatgpt.js +25 -0
- package/dist/clis.js +115 -70
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +284 -236
- package/dist/copies.js +145 -110
- 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 +41 -3
- package/dist/house-main.js +151 -52
- package/dist/instructions.js +7 -5
- package/dist/kit-main.js +54 -6
- package/dist/login.js +95 -104
- package/dist/logout.js +16 -13
- package/dist/parts.js +82 -0
- package/dist/placement.js +48 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +38 -27
- package/dist/scope.js +203 -0
- 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/stream.js +9 -4
- package/dist/transcription.js +54 -8
- package/dist/update.js +27 -6
- package/dist/wire.js +6 -0
- package/dist/work.js +348 -0
- package/package.json +2 -2
- package/dist/blocks.js +0 -51
package/dist/bridge.js
CHANGED
|
@@ -11,11 +11,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
|
|
14
|
+
import { operationId } from './operation.js';
|
|
15
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');
|
|
@@ -68,11 +67,6 @@ function resendAfter(forwarded, 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,7 +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;
|
|
106
|
-
// 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.
|
|
107
101
|
await delay(after, undefined, { signal: closed.signal });
|
|
108
102
|
}
|
|
109
103
|
};
|
|
@@ -141,32 +135,9 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
141
135
|
const held = JSON.parse(text).transfer;
|
|
142
136
|
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
143
137
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.post('/kit/tools/mutations', {}, closed.signal)
|
|
148
|
-
.then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
|
|
149
|
-
try {
|
|
150
|
-
return (await mutations).has(tool);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
mutations = null;
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
const forward = async (path, text) => {
|
|
158
|
-
if (path === '/') {
|
|
159
|
-
const message = JSON.parse(text);
|
|
160
|
-
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
161
|
-
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
162
|
-
}
|
|
163
|
-
const answer = await fetch(relayed(new URL(path, origin)), {
|
|
164
|
-
method: 'POST',
|
|
165
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
166
|
-
body: versioned(conversationId, text),
|
|
167
|
-
signal: closed.signal,
|
|
168
|
-
});
|
|
169
|
-
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);
|
|
170
141
|
};
|
|
171
142
|
const caller = {
|
|
172
143
|
key: `conversation:${conversationId}`,
|
|
@@ -188,11 +159,13 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
188
159
|
const { cwd, commit } = JSON.parse(text);
|
|
189
160
|
return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
|
|
190
161
|
}
|
|
162
|
+
if (path === '/git/fetch')
|
|
163
|
+
return { status: 200, text: JSON.stringify(await copies.fetch()) };
|
|
191
164
|
if (path === '/git/observed') {
|
|
192
165
|
await copies.observed(JSON.parse(text));
|
|
193
166
|
return { status: 200, text: '{}' };
|
|
194
167
|
}
|
|
195
|
-
return path === '/'
|
|
168
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
196
169
|
};
|
|
197
170
|
const sockets = join(kitHome(), 'bridges');
|
|
198
171
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
@@ -212,7 +185,8 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
212
185
|
response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
|
|
213
186
|
return;
|
|
214
187
|
}
|
|
215
|
-
|
|
188
|
+
const verb = request.url === '/git/push' || request.url === '/git/fetch';
|
|
189
|
+
response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
|
|
216
190
|
}
|
|
217
191
|
});
|
|
218
192
|
bridge.listen(socketPath);
|
|
@@ -239,7 +213,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
239
213
|
GIT_CONFIG_VALUE_0: app,
|
|
240
214
|
},
|
|
241
215
|
tool: async (name, args) => {
|
|
242
|
-
const forwarded = await forward(
|
|
216
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
243
217
|
if (forwarded.status !== 200)
|
|
244
218
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
245
219
|
const answer = JSON.parse(forwarded.text);
|
package/dist/chatgpt.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
await writeOwnAgent({ house, credential: values.key });
|
|
24
|
+
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
25
|
+
}
|
package/dist/clis.js
CHANGED
|
@@ -1,57 +1,86 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { constants, readFileSync } from 'node:fs';
|
|
3
|
+
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
6
|
import { kitHome } from './home.js';
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
8
|
+
function houseRule(houses) {
|
|
9
|
+
return `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`;
|
|
10
|
+
}
|
|
11
|
+
const RAW_SUBTYPES = [
|
|
12
|
+
'background_tasks_changed',
|
|
13
|
+
'task_started',
|
|
14
|
+
'task_updated',
|
|
15
|
+
'task_notification',
|
|
16
|
+
'session_state_changed',
|
|
17
|
+
'hook_started',
|
|
18
|
+
'hook_response',
|
|
19
|
+
];
|
|
20
|
+
async function executable(path) {
|
|
21
|
+
try {
|
|
22
|
+
await access(path, constants.X_OK);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function listedHouses(file) {
|
|
30
|
+
let text;
|
|
31
|
+
try {
|
|
32
|
+
text = await readFile(file, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === 'ENOENT')
|
|
36
|
+
return [];
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const listed = JSON.parse(HOUSE_EXECUTABLES.exec(text)?.[1] ?? '[]');
|
|
41
|
+
return Array.isArray(listed) ? listed.filter((house) => typeof house === 'string') : [];
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function allowHouseInCodex(path) {
|
|
48
|
+
const candidates = [...new Set(path.split(delimiter).filter((directory) => isAbsolute(directory)))].map((directory) => join(directory, 'house'));
|
|
9
49
|
const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
|
|
50
|
+
const file = join(rules, 'house.rules');
|
|
51
|
+
const houses = [];
|
|
52
|
+
for (const house of [...(await listedHouses(file)), ...candidates]) {
|
|
53
|
+
if (!houses.includes(house) && (await executable(house)))
|
|
54
|
+
houses.push(house);
|
|
55
|
+
}
|
|
10
56
|
await mkdir(rules, { recursive: true });
|
|
11
|
-
|
|
57
|
+
const temporary = `${file}.${process.pid}`;
|
|
58
|
+
await writeFile(temporary, houseRule(houses));
|
|
59
|
+
await rename(temporary, file);
|
|
12
60
|
}
|
|
13
61
|
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;
|
|
62
|
+
function bridgeInClaudeSandbox(bridge) {
|
|
63
|
+
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
37
64
|
}
|
|
38
65
|
export const CLIS = {
|
|
39
66
|
'codex-acp': {
|
|
40
67
|
bin: 'codex',
|
|
41
68
|
minimum: '0.159.1',
|
|
42
69
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
43
|
-
adapter: {
|
|
70
|
+
adapter: {
|
|
71
|
+
package: '@agentclientprotocol/codex-acp',
|
|
72
|
+
bin: 'codex-acp',
|
|
73
|
+
executable: 'CODEX_PATH',
|
|
74
|
+
env: { CODEX_CONFIG: JSON.stringify({ features: { shell_zsh_fork: true } }) },
|
|
75
|
+
},
|
|
44
76
|
args: [],
|
|
45
77
|
fullAccess: 'agent-full-access',
|
|
46
|
-
sessionMeta: {},
|
|
78
|
+
sessionMeta: () => ({}),
|
|
79
|
+
renames: true,
|
|
47
80
|
allowHouse: allowHouseInCodex,
|
|
48
81
|
signedIn: { command: ['login', 'status'] },
|
|
49
82
|
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,
|
|
83
|
+
air: ['asyncTasks', 'nativeSubagentSessions'],
|
|
55
84
|
},
|
|
56
85
|
'claude-agent-acp': {
|
|
57
86
|
bin: 'claude',
|
|
@@ -65,40 +94,36 @@ export const CLIS = {
|
|
|
65
94
|
},
|
|
66
95
|
args: [],
|
|
67
96
|
fullAccess: 'bypassPermissions',
|
|
68
|
-
sessionMeta:
|
|
97
|
+
sessionMeta: (bridge, title) => ({
|
|
98
|
+
claudeCode: {
|
|
99
|
+
options: {
|
|
100
|
+
allowedTools: ['Bash(house:*)'],
|
|
101
|
+
includeHookEvents: true,
|
|
102
|
+
title,
|
|
103
|
+
settings: { sandbox: { network: bridgeInClaudeSandbox(bridge) } },
|
|
104
|
+
},
|
|
105
|
+
emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
renames: false,
|
|
69
109
|
allowHouse: nothing,
|
|
70
110
|
signedIn: { command: ['auth', 'status'] },
|
|
71
111
|
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,
|
|
112
|
+
air: ['asyncTasks', 'sessionFailure'],
|
|
79
113
|
},
|
|
80
114
|
'grok-build': {
|
|
81
115
|
bin: 'grok',
|
|
82
116
|
minimum: '1.0.46',
|
|
83
117
|
install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
84
118
|
adapter: null,
|
|
85
|
-
args: ['agent', '--always-approve', '--no-leader', 'stdio'],
|
|
119
|
+
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
86
120
|
fullAccess: null,
|
|
87
|
-
sessionMeta: {},
|
|
121
|
+
sessionMeta: () => ({}),
|
|
122
|
+
renames: false,
|
|
88
123
|
allowHouse: nothing,
|
|
89
124
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
90
125
|
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,
|
|
126
|
+
air: ['asyncTasks'],
|
|
102
127
|
},
|
|
103
128
|
};
|
|
104
129
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -135,8 +160,22 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
135
160
|
timeout: timeoutMs,
|
|
136
161
|
env: withoutProxy(),
|
|
137
162
|
detached,
|
|
138
|
-
signal,
|
|
163
|
+
signal: detached ? undefined : signal,
|
|
139
164
|
});
|
|
165
|
+
const stop = () => {
|
|
166
|
+
try {
|
|
167
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
168
|
+
}
|
|
169
|
+
catch { }
|
|
170
|
+
};
|
|
171
|
+
if (detached && signal?.aborted)
|
|
172
|
+
stop();
|
|
173
|
+
if (detached)
|
|
174
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
175
|
+
const ended = (ran) => {
|
|
176
|
+
signal?.removeEventListener('abort', stop);
|
|
177
|
+
resolve(ran);
|
|
178
|
+
};
|
|
140
179
|
let stdout = '';
|
|
141
180
|
let output = '';
|
|
142
181
|
child.stdout.setEncoding('utf8').on('data', (chunk) => {
|
|
@@ -146,25 +185,25 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
146
185
|
child.stderr.setEncoding('utf8').on('data', (chunk) => {
|
|
147
186
|
output += chunk;
|
|
148
187
|
});
|
|
149
|
-
child.on('error', (error) =>
|
|
150
|
-
child.on('close', (status) =>
|
|
188
|
+
child.on('error', (error) => ended({ status: null, stdout, output: error.message }));
|
|
189
|
+
child.on('close', (status) => ended({ status, stdout, output }));
|
|
151
190
|
});
|
|
152
191
|
}
|
|
153
192
|
export function failureOf(ran, program) {
|
|
154
193
|
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
155
194
|
}
|
|
156
|
-
export async function installAdapter(kind) {
|
|
195
|
+
export async function installAdapter(kind, signal) {
|
|
157
196
|
const adapter = CLIS[kind].adapter;
|
|
158
197
|
if (adapter === null)
|
|
159
198
|
return null;
|
|
160
199
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
161
200
|
if ((await installedAdapter(kind)) === release)
|
|
162
201
|
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);
|
|
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);
|
|
164
203
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
165
204
|
}
|
|
166
|
-
export async function installCli(kind) {
|
|
167
|
-
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true);
|
|
205
|
+
export async function installCli(kind, signal) {
|
|
206
|
+
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
|
|
168
207
|
return ran.status === 0 ? null : failureOf(ran, 'the install');
|
|
169
208
|
}
|
|
170
209
|
function loginShell() {
|
|
@@ -173,11 +212,11 @@ function loginShell() {
|
|
|
173
212
|
const FOUND = 'house-kit-cli ';
|
|
174
213
|
const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
|
|
175
214
|
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);
|
|
215
|
+
function inLoginShell(script, argument = '', signal) {
|
|
216
|
+
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true, signal);
|
|
178
217
|
}
|
|
179
|
-
export async function locate(kind) {
|
|
180
|
-
const ran = await inLoginShell(LOOKUP, CLIS[kind].bin);
|
|
218
|
+
export async function locate(kind, signal) {
|
|
219
|
+
const ran = await inLoginShell(LOOKUP, CLIS[kind].bin, signal);
|
|
181
220
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
|
|
182
221
|
}
|
|
183
222
|
export async function loginPath() {
|
|
@@ -185,8 +224,14 @@ export async function loginPath() {
|
|
|
185
224
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
186
225
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
187
226
|
}
|
|
188
|
-
export async function
|
|
189
|
-
|
|
227
|
+
export async function writeCodexRule() {
|
|
228
|
+
const shell = await loginPath().catch(() => '');
|
|
229
|
+
await allowHouseInCodex([dirname(process.execPath), process.env.PATH ?? '', shell].join(delimiter)).catch((error) => {
|
|
230
|
+
throw new Error(`Codex's rule for house could not be written: ${error.message}`);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
export async function versionOf(path, signal) {
|
|
234
|
+
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
190
235
|
}
|
|
191
236
|
function numbers(version) {
|
|
192
237
|
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
|
+
});
|