@agentshouse/kit 0.2.1-c30728be20485 → 0.2.1-c3cf4b145dcda
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 +8 -9
- package/dist/acp.js +26 -13
- package/dist/agents.js +46 -28
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/clis.js +36 -62
- package/dist/conversations.js +276 -229
- package/dist/events.js +587 -0
- package/dist/files.js +14 -0
- package/dist/house-main.js +4 -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/dist/transcription.js +54 -8
- 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
|
|
@@ -49,11 +50,9 @@ commit to House; in the container, run it through the install command from the
|
|
|
49
50
|
copy's directory. Inside an Agent conversation the Agent runs `house push`.
|
|
50
51
|
|
|
51
52
|
House Kit turns each audio file of your message to an Agent into text on this
|
|
52
|
-
computer before the Agent reads
|
|
53
|
-
|
|
53
|
+
computer before the Agent reads it. While transcription is on, the speech
|
|
54
|
+
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
|
@@ -3,6 +3,7 @@ import { readFile, realpath } from 'node:fs/promises';
|
|
|
3
3
|
import { killTree, startAdapter } from './acp.js';
|
|
4
4
|
import { CLIS, KIT_VERSION, below, installAdapter, installCli, locate, run, versionOf } from './clis.js';
|
|
5
5
|
import { agentBase, kitHome } from './home.js';
|
|
6
|
+
import { holdTranscription, transcriptionReport } from './transcription.js';
|
|
6
7
|
const SIGNED_OUT = { signed_in: false, models: [], modes: [] };
|
|
7
8
|
function select(options, category) {
|
|
8
9
|
const option = options.find((candidate) => candidate.category === category);
|
|
@@ -46,21 +47,29 @@ async function models(kind, adapter, known) {
|
|
|
46
47
|
}
|
|
47
48
|
return { models: offered, modes };
|
|
48
49
|
}
|
|
49
|
-
async function signedIn(kind, cli, known) {
|
|
50
|
+
async function signedIn(kind, cli, known, signal) {
|
|
50
51
|
const probe = CLIS[kind].signedIn;
|
|
51
52
|
// A sign-in check that has not answered in half a minute counts as signed out rather than stalling the report.
|
|
52
53
|
if ('command' in probe)
|
|
53
|
-
return (await run(cli, probe.command, 30_000)).status === 0;
|
|
54
|
-
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;
|
|
55
56
|
}
|
|
56
|
-
|
|
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) {
|
|
57
65
|
const probe = CLIS[kind].signedIn;
|
|
58
66
|
// A sign-in check that has not answered in half a minute counts as signed out rather than stalling the report.
|
|
59
|
-
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)
|
|
60
68
|
return SIGNED_OUT;
|
|
61
69
|
let adapter;
|
|
62
70
|
try {
|
|
63
|
-
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();
|
|
64
73
|
if ('initializeMeta' in probe) {
|
|
65
74
|
const method = adapter.initialized._meta?.[probe.initializeMeta];
|
|
66
75
|
if (typeof method !== 'string' || method.length === 0)
|
|
@@ -69,7 +78,8 @@ async function offer(kind, cli, known) {
|
|
|
69
78
|
return { signed_in: true, ...(await models(kind, adapter, known.models)) };
|
|
70
79
|
}
|
|
71
80
|
catch (error) {
|
|
72
|
-
|
|
81
|
+
if (!signal?.aborted)
|
|
82
|
+
process.stderr.write(`kit: ${kind} did not offer its models: ${causeOf(error)}\n`);
|
|
73
83
|
return {
|
|
74
84
|
signed_in: adapter === undefined && 'initializeMeta' in probe ? known.signed_in : true,
|
|
75
85
|
models: known.models,
|
|
@@ -110,7 +120,7 @@ async function operatingSystem() {
|
|
|
110
120
|
return /^PRETTY_NAME="?([^"\n]*)"?$/m.exec(release)?.[1] ?? release;
|
|
111
121
|
}
|
|
112
122
|
export class Agents {
|
|
113
|
-
desired = { agents: [], routes: [] };
|
|
123
|
+
desired = { agents: [], routes: [], transcribe: false };
|
|
114
124
|
house;
|
|
115
125
|
reading = Promise.resolve();
|
|
116
126
|
queues = new Map();
|
|
@@ -134,7 +144,10 @@ export class Agents {
|
|
|
134
144
|
return this.desired;
|
|
135
145
|
});
|
|
136
146
|
this.reading = reading;
|
|
137
|
-
return reading.then(({ agents }) => {
|
|
147
|
+
return reading.then(({ agents, transcribe }) => {
|
|
148
|
+
holdTranscription(transcribe)
|
|
149
|
+
.then((changed) => (changed ? this.report() : undefined))
|
|
150
|
+
.catch(logged);
|
|
138
151
|
Promise.all(agents.map((kind) => this.load(kind)))
|
|
139
152
|
.then(() => this.report())
|
|
140
153
|
.catch(logged);
|
|
@@ -164,17 +177,18 @@ export class Agents {
|
|
|
164
177
|
return cli;
|
|
165
178
|
});
|
|
166
179
|
}
|
|
167
|
-
located(kind) {
|
|
168
|
-
return this.queue(kind, () => this.ensure(kind));
|
|
180
|
+
located(kind, signal) {
|
|
181
|
+
return this.queue(kind, () => this.ensure(kind, signal), signal);
|
|
169
182
|
}
|
|
170
183
|
async reread(kind) {
|
|
171
184
|
await this.examine(kind, this.readings.get(kind) ?? SIGNED_OUT, offer);
|
|
172
185
|
await this.report();
|
|
173
186
|
}
|
|
174
|
-
async recheck(kind) {
|
|
187
|
+
async recheck(kind, signal) {
|
|
175
188
|
const known = this.readings.get(kind) ?? SIGNED_OUT;
|
|
176
|
-
await this.examine(kind, known, async (kind, cli) => (await signedIn(kind, cli, known)) ? { ...known, signed_in: true } : SIGNED_OUT);
|
|
177
|
-
|
|
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();
|
|
178
192
|
}
|
|
179
193
|
report() {
|
|
180
194
|
const sending = this.sending.catch(() => undefined).then(() => this.send());
|
|
@@ -190,39 +204,42 @@ export class Agents {
|
|
|
190
204
|
this.readings.set(kind, reading);
|
|
191
205
|
this.report().catch(logged);
|
|
192
206
|
}
|
|
193
|
-
queue(kind, work) {
|
|
194
|
-
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
|
+
});
|
|
195
212
|
this.queues.set(kind, queued.catch(() => undefined));
|
|
196
|
-
return queued;
|
|
213
|
+
return signal === undefined ? queued : Promise.race([queued, ended(signal)]);
|
|
197
214
|
}
|
|
198
|
-
async ensure(kind) {
|
|
199
|
-
const failed = await installAdapter(kind);
|
|
215
|
+
async ensure(kind, signal) {
|
|
216
|
+
const failed = await installAdapter(kind, signal);
|
|
200
217
|
if (failed !== null)
|
|
201
218
|
throw new Error(failed);
|
|
202
|
-
const found = await locate(kind);
|
|
219
|
+
const found = await locate(kind, signal);
|
|
203
220
|
if (found !== null)
|
|
204
221
|
return found;
|
|
205
|
-
const installing = this.installing.catch(() => undefined).then(() => installCli(kind));
|
|
222
|
+
const installing = this.installing.catch(() => undefined).then(() => installCli(kind, signal));
|
|
206
223
|
this.installing = installing;
|
|
207
224
|
const failure = await installing;
|
|
208
225
|
if (failure !== null)
|
|
209
226
|
throw new Error(failure);
|
|
210
|
-
const installed = await locate(kind);
|
|
227
|
+
const installed = await locate(kind, signal);
|
|
211
228
|
if (installed === null)
|
|
212
229
|
throw new Error(`the login shell does not find ${CLIS[kind].bin} after its install`);
|
|
213
230
|
return installed;
|
|
214
231
|
}
|
|
215
|
-
async resolve(kind) {
|
|
232
|
+
async resolve(kind, signal) {
|
|
216
233
|
let cli;
|
|
217
234
|
try {
|
|
218
|
-
cli = await realpath(await this.ensure(kind));
|
|
235
|
+
cli = await realpath(await this.ensure(kind, signal));
|
|
219
236
|
}
|
|
220
237
|
catch (error) {
|
|
221
238
|
const failure = causeOf(error);
|
|
222
239
|
process.stderr.write(`kit: ${kind} did not install: ${failure}\n`);
|
|
223
240
|
return { release: null, failure, ...SIGNED_OUT };
|
|
224
241
|
}
|
|
225
|
-
const release = await versionOf(cli);
|
|
242
|
+
const release = await versionOf(cli, signal);
|
|
226
243
|
if (release === null)
|
|
227
244
|
return { release: null, failure: `${cli} --version names no version`, ...SIGNED_OUT };
|
|
228
245
|
const minimum = CLIS[kind].minimum;
|
|
@@ -230,11 +247,11 @@ export class Agents {
|
|
|
230
247
|
return { release, minimum, failure: null, ...SIGNED_OUT };
|
|
231
248
|
return { cli, release };
|
|
232
249
|
}
|
|
233
|
-
async examine(kind, known, read) {
|
|
250
|
+
async examine(kind, known, read, signal) {
|
|
234
251
|
const current = this.nextRead(kind);
|
|
235
|
-
const resolved = await this.queue(kind, () => this.resolve(kind));
|
|
252
|
+
const resolved = await this.queue(kind, () => this.resolve(kind, signal), signal);
|
|
236
253
|
const reading = 'cli' in resolved ? { release: resolved.release, failure: null, ...(await read(kind, resolved.cli, known)) } : resolved;
|
|
237
|
-
if (this.reads.get(kind) === current)
|
|
254
|
+
if (this.reads.get(kind) === current && !signal?.aborted)
|
|
238
255
|
this.readings.set(kind, reading);
|
|
239
256
|
}
|
|
240
257
|
async load(kind) {
|
|
@@ -254,6 +271,7 @@ export class Agents {
|
|
|
254
271
|
kit_version: KIT_VERSION,
|
|
255
272
|
agent_base: agentBase(),
|
|
256
273
|
agents,
|
|
274
|
+
transcription: transcriptionReport(),
|
|
257
275
|
...(hostKey === null ? {} : { ssh_host_key: hostKey }),
|
|
258
276
|
};
|
|
259
277
|
const state = JSON.stringify(body);
|
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-c3cf4b145dcda",
|
|
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-c3cf4b145dcda/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-c3cf4b145dcda/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-c3cf4b145dcda/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
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);
|