@agentshouse/kit 0.2.1-cb0bd1cadcfed → 0.2.1-cca9e1f43de44
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 +6 -7
- package/dist/acp.js +26 -13
- package/dist/agents.js +39 -26
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +7 -22
- package/dist/clis.js +36 -62
- package/dist/conversations.js +275 -228
- package/dist/events.js +587 -0
- package/dist/files.js +14 -0
- package/dist/house-main.js +28 -10
- package/dist/instructions.js +1 -1
- package/dist/kit-main.js +3 -0
- package/dist/login.js +4 -0
- package/dist/logout.js +3 -0
- package/dist/parts.js +64 -0
- package/dist/resident.js +3 -3
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +33 -9
- package/package.json +1 -1
- package/dist/blocks.js +0 -51
package/README.md
CHANGED
|
@@ -37,10 +37,11 @@ them out; running it again without the flag brings them back.
|
|
|
37
37
|
Environment's credential in `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is
|
|
38
38
|
set. Pass `--manual` to paste the code by hand on a machine without a browser,
|
|
39
39
|
and `--environment <id>` to reconnect an Environment you already have. It also
|
|
40
|
-
connects your own agent:
|
|
41
|
-
House as you
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
connects your own agent: outside an Agent conversation, `house <tool>` calls
|
|
41
|
+
House as you and `git` clones and pushes your Apps at `<house>/app/` as you,
|
|
42
|
+
until you revoke that connection on Connections. `house help` lists the Tools.
|
|
43
|
+
`kit logout` disconnects this computer, stops House Kit and ends that Git
|
|
44
|
+
access; a later `kit login` reconnects the same Environment.
|
|
44
45
|
|
|
45
46
|
`kit resident` keeps the Environment connected, and keeps the Rooms you chose
|
|
46
47
|
for this Environment as Git repositories under `local-copies` in the
|
|
@@ -54,6 +55,4 @@ engine and model use about 425 MB in `~/.house-kit`; turning it off deletes them
|
|
|
54
55
|
|
|
55
56
|
## License
|
|
56
57
|
|
|
57
|
-
MIT
|
|
58
|
-
|
|
59
|
-
The skills come from [mattpocock/skills](https://github.com/mattpocock/skills); thank you, Matt Pocock.
|
|
58
|
+
MIT. The skills come from [mattpocock/skills](https://github.com/mattpocock/skills); thank you, Matt Pocock.
|
package/dist/acp.js
CHANGED
|
@@ -3,9 +3,7 @@ import { execFileSync, spawn } from 'node:child_process';
|
|
|
3
3
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { Readable, Writable } from 'node:stream';
|
|
5
5
|
import { CLIS, KIT_VERSION, adapterCommand, withoutProxy } from './clis.js';
|
|
6
|
-
export
|
|
7
|
-
export const TURN_ENDED = 'kit/turn_ended';
|
|
8
|
-
export async function startAdapter(kind, cli, cwd, app, jobs = () => undefined, added = {}) {
|
|
6
|
+
export async function startAdapter(kind, cli, cwd, app, events = () => undefined, added = {}, signal) {
|
|
9
7
|
const adapter = CLIS[kind].adapter;
|
|
10
8
|
const child = spawn(adapter === null ? cli : adapterCommand(kind), CLIS[kind].args, {
|
|
11
9
|
cwd,
|
|
@@ -13,6 +11,9 @@ export async function startAdapter(kind, cli, cwd, app, jobs = () => undefined,
|
|
|
13
11
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
14
12
|
detached: true,
|
|
15
13
|
});
|
|
14
|
+
const stop = () => killTree(child);
|
|
15
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
16
|
+
child.on('exit', () => signal?.removeEventListener('abort', stop));
|
|
16
17
|
let stderr = '';
|
|
17
18
|
child.stderr.on('data', (chunk) => {
|
|
18
19
|
stderr = (stderr + chunk.toString('utf8')).slice(-4000);
|
|
@@ -29,16 +30,11 @@ export async function startAdapter(kind, cli, cwd, app, jobs = () => undefined,
|
|
|
29
30
|
writable: stream.writable,
|
|
30
31
|
readable: stream.readable.pipeThrough(new TransformStream({
|
|
31
32
|
transform(message, controller) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
jobs(job);
|
|
36
|
-
if (!notice.params?.update?.sessionUpdate?.startsWith('async_task_'))
|
|
33
|
+
const read = message;
|
|
34
|
+
events(read);
|
|
35
|
+
if (read.id !== undefined || (read.method !== 'session/update' && !read.method.startsWith('_'))) {
|
|
37
36
|
controller.enqueue(message);
|
|
38
|
-
|
|
39
|
-
controller.enqueue({ jsonrpc: '2.0', method: TURN_STARTED });
|
|
40
|
-
if (CLIS[kind].turnEnded(notice))
|
|
41
|
-
controller.enqueue({ jsonrpc: '2.0', method: TURN_ENDED });
|
|
37
|
+
}
|
|
42
38
|
},
|
|
43
39
|
})),
|
|
44
40
|
});
|
|
@@ -50,7 +46,8 @@ export async function startAdapter(kind, cli, cwd, app, jobs = () => undefined,
|
|
|
50
46
|
fs: { readTextFile: false, writeTextFile: false },
|
|
51
47
|
terminal: false,
|
|
52
48
|
elicitation: { form: {} },
|
|
53
|
-
|
|
49
|
+
session: { compaction: {}, notices: {} },
|
|
50
|
+
_meta: { jetbrains: { air: { version: 1, capabilities: CLIS[kind].air } } },
|
|
54
51
|
},
|
|
55
52
|
clientInfo: { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
56
53
|
});
|
|
@@ -127,6 +124,22 @@ function marked(marker) {
|
|
|
127
124
|
})
|
|
128
125
|
.map(Number);
|
|
129
126
|
}
|
|
127
|
+
function commandLine(pid) {
|
|
128
|
+
try {
|
|
129
|
+
return readFileSync(`/proc/${pid}/cmdline`, 'utf8').split('\0').join(' ').trim();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function processes(marker) {
|
|
136
|
+
const pids = marked(marker);
|
|
137
|
+
const tree = [...pids, ...descendants(pids)];
|
|
138
|
+
const commands = process.platform === 'darwin'
|
|
139
|
+
? new Map(listed('-Aww').map(([pid, , command]) => [pid, command]))
|
|
140
|
+
: new Map(tree.map((pid) => [pid, commandLine(pid)]));
|
|
141
|
+
return new Set(tree.flatMap((pid) => (commands.get(pid) ? [`${pid} ${commands.get(pid)}`] : [])));
|
|
142
|
+
}
|
|
130
143
|
export function killTree(child) {
|
|
131
144
|
killAll([child.pid, ...descendants([child.pid])]);
|
|
132
145
|
}
|
package/dist/agents.js
CHANGED
|
@@ -47,21 +47,29 @@ async function models(kind, adapter, known) {
|
|
|
47
47
|
}
|
|
48
48
|
return { models: offered, modes };
|
|
49
49
|
}
|
|
50
|
-
async function signedIn(kind, cli, known) {
|
|
50
|
+
async function signedIn(kind, cli, known, signal) {
|
|
51
51
|
const probe = CLIS[kind].signedIn;
|
|
52
52
|
// A sign-in check that has not answered in half a minute counts as signed out rather than stalling the report.
|
|
53
53
|
if ('command' in probe)
|
|
54
|
-
return (await run(cli, probe.command, 30_000)).status === 0;
|
|
55
|
-
return (await offer(kind, cli, known)).signed_in;
|
|
54
|
+
return (await run(cli, probe.command, 30_000, false, signal)).status === 0;
|
|
55
|
+
return (await offer(kind, cli, known, signal)).signed_in;
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
function ended(signal) {
|
|
58
|
+
return new Promise((_, reject) => {
|
|
59
|
+
if (signal.aborted)
|
|
60
|
+
reject(signal.reason);
|
|
61
|
+
signal.addEventListener('abort', () => reject(signal.reason), { once: true });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function offer(kind, cli, known, signal) {
|
|
58
65
|
const probe = CLIS[kind].signedIn;
|
|
59
66
|
// A sign-in check that has not answered in half a minute counts as signed out rather than stalling the report.
|
|
60
|
-
if ('command' in probe && (await run(cli, probe.command, 30_000)).status !== 0)
|
|
67
|
+
if ('command' in probe && (await run(cli, probe.command, 30_000, false, signal)).status !== 0)
|
|
61
68
|
return SIGNED_OUT;
|
|
62
69
|
let adapter;
|
|
63
70
|
try {
|
|
64
|
-
adapter = await startAdapter(kind, cli, kitHome(), client({ name: '@agentshouse/kit' }));
|
|
71
|
+
adapter = await startAdapter(kind, cli, kitHome(), client({ name: '@agentshouse/kit' }), () => undefined, {}, signal);
|
|
72
|
+
signal?.throwIfAborted();
|
|
65
73
|
if ('initializeMeta' in probe) {
|
|
66
74
|
const method = adapter.initialized._meta?.[probe.initializeMeta];
|
|
67
75
|
if (typeof method !== 'string' || method.length === 0)
|
|
@@ -70,7 +78,8 @@ async function offer(kind, cli, known) {
|
|
|
70
78
|
return { signed_in: true, ...(await models(kind, adapter, known.models)) };
|
|
71
79
|
}
|
|
72
80
|
catch (error) {
|
|
73
|
-
|
|
81
|
+
if (!signal?.aborted)
|
|
82
|
+
process.stderr.write(`kit: ${kind} did not offer its models: ${causeOf(error)}\n`);
|
|
74
83
|
return {
|
|
75
84
|
signed_in: adapter === undefined && 'initializeMeta' in probe ? known.signed_in : true,
|
|
76
85
|
models: known.models,
|
|
@@ -168,17 +177,18 @@ export class Agents {
|
|
|
168
177
|
return cli;
|
|
169
178
|
});
|
|
170
179
|
}
|
|
171
|
-
located(kind) {
|
|
172
|
-
return this.queue(kind, () => this.ensure(kind));
|
|
180
|
+
located(kind, signal) {
|
|
181
|
+
return this.queue(kind, () => this.ensure(kind, signal), signal);
|
|
173
182
|
}
|
|
174
183
|
async reread(kind) {
|
|
175
184
|
await this.examine(kind, this.readings.get(kind) ?? SIGNED_OUT, offer);
|
|
176
185
|
await this.report();
|
|
177
186
|
}
|
|
178
|
-
async recheck(kind) {
|
|
187
|
+
async recheck(kind, signal) {
|
|
179
188
|
const known = this.readings.get(kind) ?? SIGNED_OUT;
|
|
180
|
-
await this.examine(kind, known, async (kind, cli) => (await signedIn(kind, cli, known)) ? { ...known, signed_in: true } : SIGNED_OUT);
|
|
181
|
-
|
|
189
|
+
await this.examine(kind, known, async (kind, cli) => ((await signedIn(kind, cli, known, signal)) ? { ...known, signed_in: true } : SIGNED_OUT), signal);
|
|
190
|
+
if (!signal.aborted)
|
|
191
|
+
await this.report();
|
|
182
192
|
}
|
|
183
193
|
report() {
|
|
184
194
|
const sending = this.sending.catch(() => undefined).then(() => this.send());
|
|
@@ -194,39 +204,42 @@ export class Agents {
|
|
|
194
204
|
this.readings.set(kind, reading);
|
|
195
205
|
this.report().catch(logged);
|
|
196
206
|
}
|
|
197
|
-
queue(kind, work) {
|
|
198
|
-
const queued = (this.queues.get(kind) ?? Promise.resolve()).then(
|
|
207
|
+
queue(kind, work, signal) {
|
|
208
|
+
const queued = (this.queues.get(kind) ?? Promise.resolve()).then(() => {
|
|
209
|
+
signal?.throwIfAborted();
|
|
210
|
+
return work();
|
|
211
|
+
});
|
|
199
212
|
this.queues.set(kind, queued.catch(() => undefined));
|
|
200
|
-
return queued;
|
|
213
|
+
return signal === undefined ? queued : Promise.race([queued, ended(signal)]);
|
|
201
214
|
}
|
|
202
|
-
async ensure(kind) {
|
|
203
|
-
const failed = await installAdapter(kind);
|
|
215
|
+
async ensure(kind, signal) {
|
|
216
|
+
const failed = await installAdapter(kind, signal);
|
|
204
217
|
if (failed !== null)
|
|
205
218
|
throw new Error(failed);
|
|
206
|
-
const found = await locate(kind);
|
|
219
|
+
const found = await locate(kind, signal);
|
|
207
220
|
if (found !== null)
|
|
208
221
|
return found;
|
|
209
|
-
const installing = this.installing.catch(() => undefined).then(() => installCli(kind));
|
|
222
|
+
const installing = this.installing.catch(() => undefined).then(() => installCli(kind, signal));
|
|
210
223
|
this.installing = installing;
|
|
211
224
|
const failure = await installing;
|
|
212
225
|
if (failure !== null)
|
|
213
226
|
throw new Error(failure);
|
|
214
|
-
const installed = await locate(kind);
|
|
227
|
+
const installed = await locate(kind, signal);
|
|
215
228
|
if (installed === null)
|
|
216
229
|
throw new Error(`the login shell does not find ${CLIS[kind].bin} after its install`);
|
|
217
230
|
return installed;
|
|
218
231
|
}
|
|
219
|
-
async resolve(kind) {
|
|
232
|
+
async resolve(kind, signal) {
|
|
220
233
|
let cli;
|
|
221
234
|
try {
|
|
222
|
-
cli = await realpath(await this.ensure(kind));
|
|
235
|
+
cli = await realpath(await this.ensure(kind, signal));
|
|
223
236
|
}
|
|
224
237
|
catch (error) {
|
|
225
238
|
const failure = causeOf(error);
|
|
226
239
|
process.stderr.write(`kit: ${kind} did not install: ${failure}\n`);
|
|
227
240
|
return { release: null, failure, ...SIGNED_OUT };
|
|
228
241
|
}
|
|
229
|
-
const release = await versionOf(cli);
|
|
242
|
+
const release = await versionOf(cli, signal);
|
|
230
243
|
if (release === null)
|
|
231
244
|
return { release: null, failure: `${cli} --version names no version`, ...SIGNED_OUT };
|
|
232
245
|
const minimum = CLIS[kind].minimum;
|
|
@@ -234,11 +247,11 @@ export class Agents {
|
|
|
234
247
|
return { release, minimum, failure: null, ...SIGNED_OUT };
|
|
235
248
|
return { cli, release };
|
|
236
249
|
}
|
|
237
|
-
async examine(kind, known, read) {
|
|
250
|
+
async examine(kind, known, read, signal) {
|
|
238
251
|
const current = this.nextRead(kind);
|
|
239
|
-
const resolved = await this.queue(kind, () => this.resolve(kind));
|
|
252
|
+
const resolved = await this.queue(kind, () => this.resolve(kind, signal), signal);
|
|
240
253
|
const reading = 'cli' in resolved ? { release: resolved.release, failure: null, ...(await read(kind, resolved.cli, known)) } : resolved;
|
|
241
|
-
if (this.reads.get(kind) === current)
|
|
254
|
+
if (this.reads.get(kind) === current && !signal?.aborted)
|
|
242
255
|
this.readings.set(kind, reading);
|
|
243
256
|
}
|
|
244
257
|
async load(kind) {
|
package/dist/app-git.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { extname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { run } from './git.js';
|
|
4
|
+
import { kitHome, readOwnAgent } from './home.js';
|
|
5
|
+
import { quoted } from './shell.js';
|
|
6
|
+
function section(house) {
|
|
7
|
+
return `credential.${new URL('/app/', house).href}`;
|
|
8
|
+
}
|
|
9
|
+
function helper() {
|
|
10
|
+
const main = fileURLToPath(new URL(`./kit-main${extname(fileURLToPath(import.meta.url))}`, import.meta.url));
|
|
11
|
+
return `!HOUSE_KIT_HOME=${quoted(resolve(kitHome()))} ${quoted(process.execPath)} ${quoted(main)} git-credential`;
|
|
12
|
+
}
|
|
13
|
+
async function configured(args) {
|
|
14
|
+
try {
|
|
15
|
+
return await run(kitHome(), ['config', '--global', ...args]);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT')
|
|
19
|
+
return null;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function forget(house) {
|
|
24
|
+
await configured(['--remove-section', section(house)]);
|
|
25
|
+
}
|
|
26
|
+
async function register(house) {
|
|
27
|
+
await forget(house);
|
|
28
|
+
for (const value of ['', helper()]) {
|
|
29
|
+
const ran = await configured(['--add', `${section(house)}.helper`, value]);
|
|
30
|
+
if (ran === null)
|
|
31
|
+
return;
|
|
32
|
+
if (ran.status !== 0)
|
|
33
|
+
throw new Error(`git config failed: ${ran.stderr.trim()}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function placeAppGit(before, after) {
|
|
37
|
+
if (before !== null && before.house !== after?.house)
|
|
38
|
+
await forget(before.house);
|
|
39
|
+
if (after !== null)
|
|
40
|
+
await register(after.house);
|
|
41
|
+
}
|
|
42
|
+
export async function forgetAppGit() {
|
|
43
|
+
const ownAgent = await readOwnAgent();
|
|
44
|
+
if (ownAgent !== null)
|
|
45
|
+
await forget(ownAgent.house);
|
|
46
|
+
}
|
|
47
|
+
export async function answerGitCredential(operation) {
|
|
48
|
+
let asked = '';
|
|
49
|
+
for await (const chunk of process.stdin)
|
|
50
|
+
asked += String(chunk);
|
|
51
|
+
if (operation !== 'get')
|
|
52
|
+
return;
|
|
53
|
+
const attributes = new Map(asked
|
|
54
|
+
.split('\n')
|
|
55
|
+
.filter((line) => line.includes('='))
|
|
56
|
+
.map((line) => [line.slice(0, line.indexOf('=')), line.slice(line.indexOf('=') + 1)]));
|
|
57
|
+
const ownAgent = await readOwnAgent();
|
|
58
|
+
if (ownAgent === null)
|
|
59
|
+
return;
|
|
60
|
+
const house = new URL(ownAgent.house);
|
|
61
|
+
if (attributes.get('protocol') !== house.protocol.slice(0, -1) || attributes.get('host') !== house.host)
|
|
62
|
+
return;
|
|
63
|
+
process.stdout.write(`username=house\npassword=${ownAgent.credential}\n`);
|
|
64
|
+
}
|
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-cca9e1f43de44",
|
|
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-cca9e1f43de44/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-cca9e1f43de44/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-cca9e1f43de44/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
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)
|
|
@@ -154,19 +148,10 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
154
148
|
throw error;
|
|
155
149
|
}
|
|
156
150
|
};
|
|
157
|
-
const forward = async (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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() };
|
|
151
|
+
const forward = async (text) => {
|
|
152
|
+
const message = JSON.parse(text);
|
|
153
|
+
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
154
|
+
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
170
155
|
};
|
|
171
156
|
const caller = {
|
|
172
157
|
key: `conversation:${conversationId}`,
|
|
@@ -192,7 +177,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
192
177
|
await copies.observed(JSON.parse(text));
|
|
193
178
|
return { status: 200, text: '{}' };
|
|
194
179
|
}
|
|
195
|
-
return path === '/'
|
|
180
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
196
181
|
};
|
|
197
182
|
const sockets = join(kitHome(), 'bridges');
|
|
198
183
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
@@ -239,7 +224,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
239
224
|
GIT_CONFIG_VALUE_0: app,
|
|
240
225
|
},
|
|
241
226
|
tool: async (name, args) => {
|
|
242
|
-
const forwarded = await forward(
|
|
227
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
243
228
|
if (forwarded.status !== 200)
|
|
244
229
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
245
230
|
const answer = JSON.parse(forwarded.text);
|
package/dist/clis.js
CHANGED
|
@@ -4,37 +4,12 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import { homedir, userInfo } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { kitHome } from './home.js';
|
|
7
|
-
const RUNNING_JOB = new Set(['running', 'paused']);
|
|
8
7
|
async function allowHouseInCodex() {
|
|
9
8
|
const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
|
|
10
9
|
await mkdir(rules, { recursive: true });
|
|
11
10
|
await writeFile(join(rules, 'house.rules'), 'prefix_rule(pattern = ["house"], decision = "allow")\n');
|
|
12
11
|
}
|
|
13
12
|
async function nothing() { }
|
|
14
|
-
function threadStatus({ method, params }) {
|
|
15
|
-
if (method !== 'session/update' || params?.update?.sessionUpdate !== 'session_info_update')
|
|
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;
|
|
37
|
-
}
|
|
38
13
|
export const CLIS = {
|
|
39
14
|
'codex-acp': {
|
|
40
15
|
bin: 'codex',
|
|
@@ -47,11 +22,7 @@ export const CLIS = {
|
|
|
47
22
|
allowHouse: allowHouseInCodex,
|
|
48
23
|
signedIn: { command: ['login', 'status'] },
|
|
49
24
|
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,
|
|
25
|
+
air: ['asyncTasks', 'nativeSubagentSessions'],
|
|
55
26
|
},
|
|
56
27
|
'claude-agent-acp': {
|
|
57
28
|
bin: 'claude',
|
|
@@ -65,17 +36,16 @@ export const CLIS = {
|
|
|
65
36
|
},
|
|
66
37
|
args: [],
|
|
67
38
|
fullAccess: 'bypassPermissions',
|
|
68
|
-
sessionMeta: {
|
|
39
|
+
sessionMeta: {
|
|
40
|
+
claudeCode: {
|
|
41
|
+
options: { allowedTools: ['Bash(house:*)'] },
|
|
42
|
+
emitRawSDKMessages: ['background_tasks_changed', 'session_state_changed', 'task_started', 'task_notification'].map((subtype) => ({ type: 'system', subtype })),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
69
45
|
allowHouse: nothing,
|
|
70
46
|
signedIn: { command: ['auth', 'status'] },
|
|
71
47
|
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,
|
|
48
|
+
air: ['asyncTasks', 'sessionFailure'],
|
|
79
49
|
},
|
|
80
50
|
'grok-build': {
|
|
81
51
|
bin: 'grok',
|
|
@@ -88,17 +58,7 @@ export const CLIS = {
|
|
|
88
58
|
allowHouse: nothing,
|
|
89
59
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
90
60
|
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,
|
|
61
|
+
air: ['asyncTasks'],
|
|
102
62
|
},
|
|
103
63
|
};
|
|
104
64
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -135,8 +95,22 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
135
95
|
timeout: timeoutMs,
|
|
136
96
|
env: withoutProxy(),
|
|
137
97
|
detached,
|
|
138
|
-
signal,
|
|
98
|
+
signal: detached ? undefined : signal,
|
|
139
99
|
});
|
|
100
|
+
const stop = () => {
|
|
101
|
+
try {
|
|
102
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
};
|
|
106
|
+
if (detached && signal?.aborted)
|
|
107
|
+
stop();
|
|
108
|
+
if (detached)
|
|
109
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
110
|
+
const ended = (ran) => {
|
|
111
|
+
signal?.removeEventListener('abort', stop);
|
|
112
|
+
resolve(ran);
|
|
113
|
+
};
|
|
140
114
|
let stdout = '';
|
|
141
115
|
let output = '';
|
|
142
116
|
child.stdout.setEncoding('utf8').on('data', (chunk) => {
|
|
@@ -146,25 +120,25 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
146
120
|
child.stderr.setEncoding('utf8').on('data', (chunk) => {
|
|
147
121
|
output += chunk;
|
|
148
122
|
});
|
|
149
|
-
child.on('error', (error) =>
|
|
150
|
-
child.on('close', (status) =>
|
|
123
|
+
child.on('error', (error) => ended({ status: null, stdout, output: error.message }));
|
|
124
|
+
child.on('close', (status) => ended({ status, stdout, output }));
|
|
151
125
|
});
|
|
152
126
|
}
|
|
153
127
|
export function failureOf(ran, program) {
|
|
154
128
|
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
155
129
|
}
|
|
156
|
-
export async function installAdapter(kind) {
|
|
130
|
+
export async function installAdapter(kind, signal) {
|
|
157
131
|
const adapter = CLIS[kind].adapter;
|
|
158
132
|
if (adapter === null)
|
|
159
133
|
return null;
|
|
160
134
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
161
135
|
if ((await installedAdapter(kind)) === release)
|
|
162
136
|
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);
|
|
137
|
+
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
138
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
165
139
|
}
|
|
166
|
-
export async function installCli(kind) {
|
|
167
|
-
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true);
|
|
140
|
+
export async function installCli(kind, signal) {
|
|
141
|
+
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
|
|
168
142
|
return ran.status === 0 ? null : failureOf(ran, 'the install');
|
|
169
143
|
}
|
|
170
144
|
function loginShell() {
|
|
@@ -173,11 +147,11 @@ function loginShell() {
|
|
|
173
147
|
const FOUND = 'house-kit-cli ';
|
|
174
148
|
const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
|
|
175
149
|
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);
|
|
150
|
+
function inLoginShell(script, argument = '', signal) {
|
|
151
|
+
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true, signal);
|
|
178
152
|
}
|
|
179
|
-
export async function locate(kind) {
|
|
180
|
-
const ran = await inLoginShell(LOOKUP, CLIS[kind].bin);
|
|
153
|
+
export async function locate(kind, signal) {
|
|
154
|
+
const ran = await inLoginShell(LOOKUP, CLIS[kind].bin, signal);
|
|
181
155
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
|
|
182
156
|
}
|
|
183
157
|
export async function loginPath() {
|
|
@@ -185,8 +159,8 @@ export async function loginPath() {
|
|
|
185
159
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
186
160
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
187
161
|
}
|
|
188
|
-
export async function versionOf(path) {
|
|
189
|
-
return VERSION.exec((await run(path, ['--version'], READ_MS)).stdout)?.[0] ?? null;
|
|
162
|
+
export async function versionOf(path, signal) {
|
|
163
|
+
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
190
164
|
}
|
|
191
165
|
function numbers(version) {
|
|
192
166
|
return version.split(/[.-]/, 3).map(Number);
|