@agentshouse/kit 0.2.1-alpha.15 → 0.2.1-alpha.17
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 -1
- package/dist/acp.js +5 -4
- package/dist/agents.js +131 -46
- package/dist/bootstrap.json +5 -5
- package/dist/bridge.js +6 -36
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +77 -30
- package/dist/conversations.js +2 -2
- package/dist/home.js +11 -1
- package/dist/house-main.js +27 -17
- package/dist/login.js +20 -1
- package/dist/mcp.js +38 -0
- package/dist/resident.js +0 -2
- package/dist/sign-in.js +15 -5
- package/dist/update.js +4 -3
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -24,6 +24,11 @@ On your own Ubuntu 26.04 server, run the Linux command with `--linux` to
|
|
|
24
24
|
install House Kit natively, with its workspace at `/agents/house`; `kit` and
|
|
25
25
|
`house` are then on the server's `PATH`.
|
|
26
26
|
|
|
27
|
+
House Kit runs the agent CLIs your login shell finds and installs a missing
|
|
28
|
+
one through that CLI's own installer. Running the command again offers to
|
|
29
|
+
update each CLI older than House Kit needs; add `--update-clis` to update every
|
|
30
|
+
one to its latest release without asking.
|
|
31
|
+
|
|
27
32
|
House Kit also gives every Agent on the Environment a set of agent skills. Add
|
|
28
33
|
`--no-skills` to the command to leave them out; running it again without the
|
|
29
34
|
flag brings them back.
|
|
@@ -33,7 +38,9 @@ flag brings them back.
|
|
|
33
38
|
`kit login` opens a link you confirm in your browser and keeps the
|
|
34
39
|
Environment's credential in `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is
|
|
35
40
|
set. Pass `--manual` to paste the code by hand on a machine without a browser,
|
|
36
|
-
and `--environment <id>` to reconnect an Environment you already have.
|
|
41
|
+
and `--environment <id>` to reconnect an Environment you already have. It also
|
|
42
|
+
connects your own agent: `house <tool>` run outside an Agent conversation calls
|
|
43
|
+
House as you, until you revoke that connection on Connections.
|
|
37
44
|
|
|
38
45
|
`kit resident` keeps the Environment connected, and keeps the Rooms you chose
|
|
39
46
|
for this Environment as Git repositories under `working-copies` in the
|
package/dist/acp.js
CHANGED
|
@@ -2,13 +2,14 @@ import { ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
|
|
|
2
2
|
import { execFileSync, spawn } from 'node:child_process';
|
|
3
3
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { Readable, Writable } from 'node:stream';
|
|
5
|
-
import { CLIS, KIT_VERSION,
|
|
5
|
+
import { CLIS, KIT_VERSION, adapterCommand, withoutProxy } from './clis.js';
|
|
6
6
|
export const TURN_STARTED = 'kit/turn_started';
|
|
7
7
|
export const TURN_ENDED = 'kit/turn_ended';
|
|
8
|
-
export async function startAdapter(kind, cwd, app, jobs = () => undefined, added = {}) {
|
|
9
|
-
const
|
|
8
|
+
export async function startAdapter(kind, cli, cwd, app, jobs = () => undefined, added = {}) {
|
|
9
|
+
const adapter = CLIS[kind].adapter;
|
|
10
|
+
const child = spawn(adapter === null ? cli : adapterCommand(kind), CLIS[kind].args, {
|
|
10
11
|
cwd,
|
|
11
|
-
env: { ...withoutProxy(), ...added },
|
|
12
|
+
env: { ...withoutProxy(), ...(adapter === null ? {} : { [adapter.executable]: cli }), ...added },
|
|
12
13
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
13
14
|
detached: true,
|
|
14
15
|
});
|
package/dist/agents.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { client } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
3
3
|
import { killTree, startAdapter } from './acp.js';
|
|
4
|
-
import { CLIS, KIT_VERSION,
|
|
4
|
+
import { CLIS, KIT_VERSION, below, installAdapter, installCli, locate, run, versionOf } from './clis.js';
|
|
5
5
|
import { agentBase, kitHome } from './home.js';
|
|
6
6
|
const SIGNED_OUT = { signed_in: false, models: [] };
|
|
7
7
|
function select(options, category) {
|
|
@@ -14,6 +14,9 @@ function values(options) {
|
|
|
14
14
|
function causeOf(error) {
|
|
15
15
|
return error instanceof Error ? error.message : String(error);
|
|
16
16
|
}
|
|
17
|
+
function logged(error) {
|
|
18
|
+
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
19
|
+
}
|
|
17
20
|
async function models(kind, adapter, known) {
|
|
18
21
|
const agent = adapter.connection.agent;
|
|
19
22
|
const opened = await agent.request('session/new', { cwd: kitHome(), mcpServers: [] });
|
|
@@ -38,13 +41,13 @@ async function models(kind, adapter, known) {
|
|
|
38
41
|
}
|
|
39
42
|
return offered;
|
|
40
43
|
}
|
|
41
|
-
async function offer(kind, known) {
|
|
44
|
+
async function offer(kind, cli, known) {
|
|
42
45
|
const probe = CLIS[kind].signedIn;
|
|
43
|
-
if ('command' in probe && (await run(
|
|
46
|
+
if ('command' in probe && (await run(cli, probe.command, 30_000)).status !== 0)
|
|
44
47
|
return SIGNED_OUT;
|
|
45
48
|
let adapter;
|
|
46
49
|
try {
|
|
47
|
-
adapter = await startAdapter(kind, kitHome(), client({ name: '@agentshouse/kit' }));
|
|
50
|
+
adapter = await startAdapter(kind, cli, kitHome(), client({ name: '@agentshouse/kit' }));
|
|
48
51
|
if ('initializeMeta' in probe) {
|
|
49
52
|
const method = adapter.initialized._meta?.[probe.initializeMeta];
|
|
50
53
|
if (typeof method !== 'string' || method.length === 0)
|
|
@@ -54,13 +57,27 @@ async function offer(kind, known) {
|
|
|
54
57
|
}
|
|
55
58
|
catch (error) {
|
|
56
59
|
process.stderr.write(`kit: ${kind} did not offer its models: ${causeOf(error)}\n`);
|
|
57
|
-
return adapter === undefined && 'initializeMeta' in probe ? known :
|
|
60
|
+
return { signed_in: adapter === undefined && 'initializeMeta' in probe ? known.signed_in : true, models: known.models };
|
|
58
61
|
}
|
|
59
62
|
finally {
|
|
60
63
|
if (adapter !== undefined)
|
|
61
64
|
killTree(adapter.child);
|
|
62
65
|
}
|
|
63
66
|
}
|
|
67
|
+
function reported(kind, reading) {
|
|
68
|
+
return {
|
|
69
|
+
kind,
|
|
70
|
+
release: reading.release,
|
|
71
|
+
...(reading.minimum === undefined ? {} : { minimum: reading.minimum }),
|
|
72
|
+
failure: reading.failure,
|
|
73
|
+
signed_in: reading.signed_in,
|
|
74
|
+
models: reading.models,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function tooOld(kind, release) {
|
|
78
|
+
const cli = CLIS[kind];
|
|
79
|
+
return `${cli.bin} ${release} is older than ${cli.minimum}, the oldest this Kit runs`;
|
|
80
|
+
}
|
|
64
81
|
async function sshHostKey() {
|
|
65
82
|
const listed = await run('ssh-keygen', ['-l', '-E', 'sha256', '-f', '/etc/ssh/ssh_host_ed25519_key.pub'], 10_000);
|
|
66
83
|
return listed.status === 0 ? (/^\d+ (SHA256:\S+)/.exec(listed.output)?.[1] ?? null) : null;
|
|
@@ -75,11 +92,10 @@ export class Agents {
|
|
|
75
92
|
desired = { agents: [], routes: [] };
|
|
76
93
|
house;
|
|
77
94
|
reading = Promise.resolve();
|
|
95
|
+
queues = new Map();
|
|
78
96
|
installing = Promise.resolve();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
reporting = null;
|
|
82
|
-
again = false;
|
|
97
|
+
readings = new Map();
|
|
98
|
+
reads = new Map();
|
|
83
99
|
sending = Promise.resolve();
|
|
84
100
|
sent = null;
|
|
85
101
|
constructor(house) {
|
|
@@ -88,9 +104,6 @@ export class Agents {
|
|
|
88
104
|
read() {
|
|
89
105
|
return this.reading.then(() => undefined, () => undefined);
|
|
90
106
|
}
|
|
91
|
-
installed() {
|
|
92
|
-
return this.installing.catch(() => undefined);
|
|
93
|
-
}
|
|
94
107
|
route(agentId) {
|
|
95
108
|
return this.desired.routes.find((route) => route.agent_id === agentId);
|
|
96
109
|
}
|
|
@@ -100,28 +113,118 @@ export class Agents {
|
|
|
100
113
|
return this.desired;
|
|
101
114
|
});
|
|
102
115
|
this.reading = reading;
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
return reading.then(({ agents }) => {
|
|
117
|
+
Promise.all(agents.map((kind) => this.queue(kind, () => this.load(kind))))
|
|
118
|
+
.then(() => this.report())
|
|
119
|
+
.catch(logged);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
cli(kind) {
|
|
123
|
+
return this.queue(kind, async () => {
|
|
124
|
+
const resolved = await this.resolve(kind);
|
|
125
|
+
if (!('cli' in resolved)) {
|
|
126
|
+
this.update(kind, resolved);
|
|
127
|
+
throw new Error(resolved.failure ?? tooOld(kind, resolved.release));
|
|
128
|
+
}
|
|
129
|
+
const { cli, release } = resolved;
|
|
130
|
+
const known = this.readings.get(kind);
|
|
131
|
+
const kept = known?.release === null || known?.minimum !== undefined ? SIGNED_OUT : (known ?? SIGNED_OUT);
|
|
132
|
+
if (known?.release !== release || known.minimum !== undefined) {
|
|
133
|
+
this.update(kind, { release, failure: null, signed_in: kept.signed_in, models: kept.models });
|
|
134
|
+
}
|
|
135
|
+
const read = this.nextRead(kind);
|
|
136
|
+
offer(kind, cli, kept)
|
|
137
|
+
.then((offered) => {
|
|
138
|
+
const current = this.readings.get(kind);
|
|
139
|
+
if (this.reads.get(kind) === read && current?.release === release)
|
|
140
|
+
this.update(kind, { ...current, ...offered });
|
|
141
|
+
})
|
|
142
|
+
.catch(logged);
|
|
143
|
+
return cli;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
located(kind) {
|
|
147
|
+
return this.queue(kind, () => this.ensure(kind));
|
|
148
|
+
}
|
|
149
|
+
async reread(kind) {
|
|
150
|
+
await this.queue(kind, async () => {
|
|
151
|
+
this.nextRead(kind);
|
|
152
|
+
this.readings.set(kind, await this.examine(kind, this.readings.get(kind) ?? SIGNED_OUT));
|
|
153
|
+
});
|
|
154
|
+
await this.report();
|
|
112
155
|
}
|
|
113
156
|
report() {
|
|
114
157
|
const sending = this.sending.catch(() => undefined).then(() => this.send());
|
|
115
158
|
this.sending = sending;
|
|
116
159
|
return sending;
|
|
117
160
|
}
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
161
|
+
nextRead(kind) {
|
|
162
|
+
const read = (this.reads.get(kind) ?? 0) + 1;
|
|
163
|
+
this.reads.set(kind, read);
|
|
164
|
+
return read;
|
|
165
|
+
}
|
|
166
|
+
update(kind, reading) {
|
|
167
|
+
this.readings.set(kind, reading);
|
|
168
|
+
this.report().catch(logged);
|
|
169
|
+
}
|
|
170
|
+
queue(kind, work) {
|
|
171
|
+
const queued = (this.queues.get(kind) ?? Promise.resolve()).then(work);
|
|
172
|
+
this.queues.set(kind, queued.catch(() => undefined));
|
|
173
|
+
return queued;
|
|
174
|
+
}
|
|
175
|
+
async ensure(kind) {
|
|
176
|
+
const failed = await installAdapter(kind);
|
|
177
|
+
if (failed !== null)
|
|
178
|
+
throw new Error(failed);
|
|
179
|
+
const found = await locate(kind);
|
|
180
|
+
if (found !== null)
|
|
181
|
+
return found;
|
|
182
|
+
const installing = this.installing.catch(() => undefined).then(() => installCli(kind));
|
|
183
|
+
this.installing = installing;
|
|
184
|
+
const failure = await installing;
|
|
185
|
+
if (failure !== null)
|
|
186
|
+
throw new Error(failure);
|
|
187
|
+
const installed = await locate(kind);
|
|
188
|
+
if (installed === null)
|
|
189
|
+
throw new Error(`the login shell does not find ${CLIS[kind].bin} after its install`);
|
|
190
|
+
return installed;
|
|
191
|
+
}
|
|
192
|
+
async resolve(kind) {
|
|
193
|
+
let cli;
|
|
194
|
+
try {
|
|
195
|
+
cli = await realpath(await this.ensure(kind));
|
|
124
196
|
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const failure = causeOf(error);
|
|
199
|
+
process.stderr.write(`kit: ${kind} did not install: ${failure}\n`);
|
|
200
|
+
return { release: null, failure, ...SIGNED_OUT };
|
|
201
|
+
}
|
|
202
|
+
const release = await versionOf(cli);
|
|
203
|
+
if (release === null)
|
|
204
|
+
return { release: null, failure: `${cli} --version names no version`, ...SIGNED_OUT };
|
|
205
|
+
const minimum = CLIS[kind].minimum;
|
|
206
|
+
if (below(release, minimum))
|
|
207
|
+
return { release, minimum, failure: null, ...SIGNED_OUT };
|
|
208
|
+
return { cli, release };
|
|
209
|
+
}
|
|
210
|
+
async examine(kind, known) {
|
|
211
|
+
const resolved = await this.resolve(kind);
|
|
212
|
+
if (!('cli' in resolved))
|
|
213
|
+
return resolved;
|
|
214
|
+
return { release: resolved.release, failure: null, ...(await offer(kind, resolved.cli, known)) };
|
|
215
|
+
}
|
|
216
|
+
async load(kind) {
|
|
217
|
+
const known = this.readings.get(kind);
|
|
218
|
+
if (known !== undefined && known.failure === null)
|
|
219
|
+
return;
|
|
220
|
+
this.nextRead(kind);
|
|
221
|
+
this.readings.set(kind, await this.examine(kind, SIGNED_OUT));
|
|
222
|
+
}
|
|
223
|
+
async send() {
|
|
224
|
+
const agents = this.desired.agents.flatMap((kind) => {
|
|
225
|
+
const reading = this.readings.get(kind);
|
|
226
|
+
return reading === undefined ? [] : [reported(kind, reading)];
|
|
227
|
+
});
|
|
125
228
|
const hostKey = await sshHostKey();
|
|
126
229
|
const body = {
|
|
127
230
|
os: await operatingSystem(),
|
|
@@ -136,22 +239,4 @@ export class Agents {
|
|
|
136
239
|
await this.house.deliver('/kit/agents/report', body);
|
|
137
240
|
this.sent = state;
|
|
138
241
|
}
|
|
139
|
-
async install(kinds) {
|
|
140
|
-
const installs = new Map();
|
|
141
|
-
for (const kind of kinds)
|
|
142
|
-
installs.set(kind, await install(kind));
|
|
143
|
-
this.installs = installs;
|
|
144
|
-
}
|
|
145
|
-
async reports() {
|
|
146
|
-
try {
|
|
147
|
-
do {
|
|
148
|
-
this.again = false;
|
|
149
|
-
await this.installing;
|
|
150
|
-
await this.report();
|
|
151
|
-
} while (this.again);
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
this.reporting = null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
242
|
}
|
package/dist/bootstrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.2.1-alpha.
|
|
3
|
+
"version": "0.2.1-alpha.17",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.17/connect-linux.sh)",
|
|
7
7
|
"asset": {
|
|
8
8
|
"name": "connect-linux.sh",
|
|
9
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.17/connect-linux.sh"
|
|
10
10
|
},
|
|
11
11
|
"workspace": {
|
|
12
12
|
"argument": "--workspace",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"windows": {
|
|
20
|
-
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.
|
|
20
|
+
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.17/connect-windows.ps1)))",
|
|
21
21
|
"asset": {
|
|
22
22
|
"name": "connect-windows.ps1",
|
|
23
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.
|
|
23
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.17/connect-windows.ps1"
|
|
24
24
|
},
|
|
25
25
|
"workspace": {
|
|
26
26
|
"argument": "--workspace",
|
package/dist/bridge.js
CHANGED
|
@@ -7,12 +7,11 @@ import { request as httpsRequest } from 'node:https';
|
|
|
7
7
|
import { delimiter, join } from 'node:path';
|
|
8
8
|
import { parse } from 'yaml';
|
|
9
9
|
import { HouseRefusal, retryDelay } from './api.js';
|
|
10
|
-
import { KIT_VERSION } from './clis.js';
|
|
11
10
|
import { changedPaths, } from './copies.js';
|
|
12
11
|
import { kitHome, readEnrolment } from './home.js';
|
|
12
|
+
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
13
13
|
import { operationId, uuidOf } from './operation.js';
|
|
14
14
|
import { refusalOf } from './refusals.js';
|
|
15
|
-
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
16
15
|
const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
17
16
|
const ATTACHMENTS = '/kit/attachments/upload';
|
|
18
17
|
export async function received(request) {
|
|
@@ -34,7 +33,6 @@ function relay(request, response, target, authorization) {
|
|
|
34
33
|
forwarded.on('error', () => response.destroy());
|
|
35
34
|
request.pipe(forwarded);
|
|
36
35
|
}
|
|
37
|
-
const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
38
36
|
function refusalIn(text, status) {
|
|
39
37
|
return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
|
|
40
38
|
}
|
|
@@ -90,35 +88,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
90
88
|
const origin = (await readEnrolment()).house;
|
|
91
89
|
const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
|
|
92
90
|
const closed = new AbortController();
|
|
93
|
-
const
|
|
94
|
-
...message,
|
|
95
|
-
params: {
|
|
96
|
-
...message.params,
|
|
97
|
-
_meta: {
|
|
98
|
-
'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
|
|
99
|
-
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
100
|
-
'io.modelcontextprotocol/clientCapabilities': {},
|
|
101
|
-
...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
const sent = async (message, operation) => {
|
|
106
|
-
const tool = message.method === 'tools/call' ? String(message.params?.name) : null;
|
|
107
|
-
const answer = await fetch(new URL('/', origin), {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: {
|
|
110
|
-
'content-type': 'application/json',
|
|
111
|
-
authorization: `Bearer ${credential}`,
|
|
112
|
-
accept: 'application/json, text/event-stream',
|
|
113
|
-
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
114
|
-
'mcp-method': message.method,
|
|
115
|
-
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
116
|
-
},
|
|
117
|
-
body: body(message, operation),
|
|
118
|
-
signal: closed.signal,
|
|
119
|
-
});
|
|
120
|
-
return { status: answer.status, text: await answer.text() };
|
|
121
|
-
};
|
|
91
|
+
const sent = (message, operation) => callHouse(origin, credential, message, operation, closed.signal);
|
|
122
92
|
const send = async (message, operation) => {
|
|
123
93
|
if (operation === null)
|
|
124
94
|
return sent(message, null);
|
|
@@ -134,7 +104,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
134
104
|
await delay(after, undefined, { signal: closed.signal });
|
|
135
105
|
}
|
|
136
106
|
};
|
|
137
|
-
const call = (name, args) => (
|
|
107
|
+
const call = (name, args) => rpc('tools/call', { name, arguments: args });
|
|
138
108
|
const staged = async (operation, changes) => {
|
|
139
109
|
const bytes = Buffer.from(JSON.stringify({ changes }));
|
|
140
110
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
@@ -175,7 +145,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
175
145
|
const forward = async (path, text) => {
|
|
176
146
|
if (path === '/') {
|
|
177
147
|
const message = JSON.parse(text);
|
|
178
|
-
const tool = message.method === 'tools/call' ? String(message.params
|
|
148
|
+
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
179
149
|
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
180
150
|
}
|
|
181
151
|
const answer = await fetch(new URL(path, origin), {
|
|
@@ -191,7 +161,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
191
161
|
submit: async (operation, changes, held, retain) => {
|
|
192
162
|
const inline = call('edit', { changes });
|
|
193
163
|
let upload = held;
|
|
194
|
-
if (upload === undefined && Buffer.byteLength(
|
|
164
|
+
if (upload === undefined && Buffer.byteLength(mcpBody(inline, operation)) > MCP_REQUEST_BYTES) {
|
|
195
165
|
const prepared = await staged(operation, changes);
|
|
196
166
|
if ('refused' in prepared)
|
|
197
167
|
return prepared;
|
|
@@ -257,7 +227,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
257
227
|
GIT_CONFIG_VALUE_0: app,
|
|
258
228
|
},
|
|
259
229
|
tool: async (name, args) => {
|
|
260
|
-
const forwarded = await forward('/', JSON.stringify(
|
|
230
|
+
const forwarded = await forward('/', JSON.stringify(call(name, args)));
|
|
261
231
|
if (forwarded.status !== 200)
|
|
262
232
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
263
233
|
const answer = JSON.parse(forwarded.text);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
import { post } from './api.js';
|
|
3
|
+
import { CLIS, below, locate, versionOf } from './clis.js';
|
|
4
|
+
import { readEnrolment } from './home.js';
|
|
5
|
+
setGlobalProxyFromEnv();
|
|
6
|
+
try {
|
|
7
|
+
const { house, credential } = (await readEnrolment());
|
|
8
|
+
const { agents } = await post(house, '/kit/agents/desired', {}, credential);
|
|
9
|
+
for (const kind of agents) {
|
|
10
|
+
const cli = await locate(kind);
|
|
11
|
+
if (cli === null)
|
|
12
|
+
continue;
|
|
13
|
+
const release = await versionOf(cli);
|
|
14
|
+
if (release === null)
|
|
15
|
+
continue;
|
|
16
|
+
const { bin, minimum } = CLIS[kind];
|
|
17
|
+
process.stdout.write(`${[bin, cli, release, minimum, below(release, minimum) ? 'old' : 'current'].join('\t')}\n`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
process.stderr.write(`kit: ${error.message}\n`);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|
package/dist/clis.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { userInfo } from 'node:os';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
6
|
import { kitHome } from './home.js';
|
|
6
7
|
const RUNNING_JOB = new Set(['running', 'paused']);
|
|
@@ -30,11 +31,13 @@ function airPhase({ _meta }) {
|
|
|
30
31
|
}
|
|
31
32
|
export const CLIS = {
|
|
32
33
|
'codex-acp': {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
bin: 'codex',
|
|
35
|
+
minimum: '0.159.1',
|
|
36
|
+
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
37
|
+
adapter: { package: '@agentclientprotocol/codex-acp', bin: 'codex-acp', executable: 'CODEX_PATH' },
|
|
35
38
|
args: [],
|
|
36
|
-
signedIn: { command: ['
|
|
37
|
-
login: { args: ['
|
|
39
|
+
signedIn: { command: ['login', 'status'] },
|
|
40
|
+
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
38
41
|
queues: false,
|
|
39
42
|
turnStarted: (notice) => threadStatus(notice) === 'active',
|
|
40
43
|
turnEnded: (notice) => threadStatus(notice) === 'idle',
|
|
@@ -42,11 +45,17 @@ export const CLIS = {
|
|
|
42
45
|
phase: airPhase,
|
|
43
46
|
},
|
|
44
47
|
'claude-agent-acp': {
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
bin: 'claude',
|
|
49
|
+
minimum: '2.1.286',
|
|
50
|
+
install: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
51
|
+
adapter: {
|
|
52
|
+
package: '@agentclientprotocol/claude-agent-acp',
|
|
53
|
+
bin: 'claude-agent-acp',
|
|
54
|
+
executable: 'CLAUDE_CODE_EXECUTABLE',
|
|
55
|
+
},
|
|
47
56
|
args: [],
|
|
48
|
-
signedIn: { command: ['
|
|
49
|
-
login: { args: ['
|
|
57
|
+
signedIn: { command: ['auth', 'status'] },
|
|
58
|
+
login: { args: ['auth', 'login', '--claudeai'], code: 'collect' },
|
|
50
59
|
queues: false,
|
|
51
60
|
turnStarted: () => false,
|
|
52
61
|
turnEnded: ({ method, params }) => method === 'session/update' &&
|
|
@@ -56,8 +65,10 @@ export const CLIS = {
|
|
|
56
65
|
phase: () => null,
|
|
57
66
|
},
|
|
58
67
|
'grok-build': {
|
|
59
|
-
package: '@xai-official/grok',
|
|
60
68
|
bin: 'grok',
|
|
69
|
+
minimum: '1.0.46',
|
|
70
|
+
install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
71
|
+
adapter: null,
|
|
61
72
|
args: ['agent', '--no-leader', 'stdio'],
|
|
62
73
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
63
74
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
@@ -77,18 +88,19 @@ export const CLIS = {
|
|
|
77
88
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
78
89
|
export const KIT_VERSION = KIT_PACKAGE.version;
|
|
79
90
|
const PROXY = /^https?_proxy$/i;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
|
|
92
|
+
const INSTALL_MS = 15 * 60_000;
|
|
93
|
+
const READ_MS = 30_000;
|
|
94
|
+
const CAUSE_CHARACTERS = 4000;
|
|
83
95
|
function prefix(kind) {
|
|
84
96
|
return join(kitHome(), 'agents', kind);
|
|
85
97
|
}
|
|
86
|
-
export function
|
|
87
|
-
return join(prefix(kind), 'node_modules', '.bin', CLIS[kind].bin);
|
|
98
|
+
export function adapterCommand(kind) {
|
|
99
|
+
return join(prefix(kind), 'node_modules', '.bin', CLIS[kind].adapter.bin);
|
|
88
100
|
}
|
|
89
|
-
|
|
101
|
+
async function installedAdapter(kind) {
|
|
90
102
|
try {
|
|
91
|
-
const manifest = join(prefix(kind), 'node_modules', CLIS[kind].package, 'package.json');
|
|
103
|
+
const manifest = join(prefix(kind), 'node_modules', CLIS[kind].adapter.package, 'package.json');
|
|
92
104
|
return JSON.parse(await readFile(manifest, 'utf8')).version;
|
|
93
105
|
}
|
|
94
106
|
catch {
|
|
@@ -98,29 +110,64 @@ export async function installedRelease(kind) {
|
|
|
98
110
|
export function withoutProxy() {
|
|
99
111
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
100
112
|
}
|
|
101
|
-
export function run(command, args, timeoutMs) {
|
|
113
|
+
export function run(command, args, timeoutMs, detached = false) {
|
|
102
114
|
return new Promise((resolve) => {
|
|
103
|
-
const child = spawn(command, args, {
|
|
115
|
+
const child = spawn(command, args, {
|
|
116
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
117
|
+
timeout: timeoutMs,
|
|
118
|
+
env: withoutProxy(),
|
|
119
|
+
detached,
|
|
120
|
+
});
|
|
121
|
+
let stdout = '';
|
|
104
122
|
let output = '';
|
|
105
123
|
child.stdout.on('data', (chunk) => {
|
|
124
|
+
stdout += chunk.toString('utf8');
|
|
106
125
|
output += chunk.toString('utf8');
|
|
107
126
|
});
|
|
108
127
|
child.stderr.on('data', (chunk) => {
|
|
109
128
|
output += chunk.toString('utf8');
|
|
110
129
|
});
|
|
111
|
-
child.on('error', (error) => resolve({ status: null, output: error.message }));
|
|
112
|
-
child.on('close', (status) => resolve({ status, output }));
|
|
130
|
+
child.on('error', (error) => resolve({ status: null, stdout, output: error.message }));
|
|
131
|
+
child.on('close', (status) => resolve({ status, stdout, output }));
|
|
113
132
|
});
|
|
114
133
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
function failureOf(ran, program) {
|
|
135
|
+
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
136
|
+
}
|
|
137
|
+
export async function installAdapter(kind) {
|
|
138
|
+
const adapter = CLIS[kind].adapter;
|
|
139
|
+
if (adapter === null)
|
|
140
|
+
return null;
|
|
141
|
+
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
142
|
+
if ((await installedAdapter(kind)) === release)
|
|
143
|
+
return null;
|
|
144
|
+
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS);
|
|
145
|
+
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
146
|
+
}
|
|
147
|
+
export async function installCli(kind) {
|
|
148
|
+
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true);
|
|
149
|
+
return ran.status === 0 ? null : failureOf(ran, 'the install');
|
|
150
|
+
}
|
|
151
|
+
function loginShell() {
|
|
152
|
+
return process.env.SHELL || userInfo().shell;
|
|
153
|
+
}
|
|
154
|
+
const FOUND = 'house-kit-cli ';
|
|
155
|
+
const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
|
|
156
|
+
export async function locate(kind) {
|
|
157
|
+
const ran = await run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${LOOKUP}' sh ${CLIS[kind].bin}`], READ_MS, true);
|
|
158
|
+
return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
|
|
159
|
+
}
|
|
160
|
+
export async function versionOf(path) {
|
|
161
|
+
return VERSION.exec((await run(path, ['--version'], READ_MS)).stdout)?.[0] ?? null;
|
|
162
|
+
}
|
|
163
|
+
function numbers(version) {
|
|
164
|
+
return version.split(/[.-]/, 3).map(Number);
|
|
165
|
+
}
|
|
166
|
+
export function below(found, minimum) {
|
|
167
|
+
const [have, need] = [numbers(found), numbers(minimum)];
|
|
168
|
+
for (let index = 0; index < 3; index++) {
|
|
169
|
+
if (have[index] !== need[index])
|
|
170
|
+
return have[index] < need[index];
|
|
124
171
|
}
|
|
125
|
-
return
|
|
172
|
+
return found.includes('-') && !minimum.includes('-');
|
|
126
173
|
}
|
package/dist/conversations.js
CHANGED
|
@@ -253,7 +253,7 @@ export class Conversations {
|
|
|
253
253
|
.onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
|
|
254
254
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
255
255
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal));
|
|
256
|
-
await unlessKilled(this.kit.agents.
|
|
256
|
+
const cli = await unlessKilled(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
257
257
|
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
258
258
|
if (conversation.kills > 0) {
|
|
259
259
|
bridge.close();
|
|
@@ -262,7 +262,7 @@ export class Conversations {
|
|
|
262
262
|
conversation.opening = bridge;
|
|
263
263
|
try {
|
|
264
264
|
await unlessKilled(createHowWeWork(bridge), conversation.killed.signal);
|
|
265
|
-
const adapter = await startAdapter(route.kind, route.working_directory, app, (job) => this.job(conversation, job), bridge.env);
|
|
265
|
+
const adapter = await startAdapter(route.kind, cli, route.working_directory, app, (job) => this.job(conversation, job), bridge.env);
|
|
266
266
|
void adapter.exited.then(() => bridge.close());
|
|
267
267
|
const agent = adapter.connection.agent;
|
|
268
268
|
const cwd = route.working_directory;
|
package/dist/home.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
const ENROLMENT = 'credential.json';
|
|
5
5
|
const CONFIGURATION = 'kit.json';
|
|
6
|
+
const OWN_AGENT = 'own-agent.json';
|
|
6
7
|
export function kitHome() {
|
|
7
8
|
return process.env.HOUSE_KIT_HOME ?? join(homedir(), '.house-kit');
|
|
8
9
|
}
|
|
@@ -35,6 +36,15 @@ export function readEnrolment() {
|
|
|
35
36
|
export function writeEnrolment(enrolment) {
|
|
36
37
|
return writeHome(ENROLMENT, enrolment);
|
|
37
38
|
}
|
|
39
|
+
export function readOwnAgent() {
|
|
40
|
+
return readHome(OWN_AGENT);
|
|
41
|
+
}
|
|
42
|
+
export function writeOwnAgent(ownAgent) {
|
|
43
|
+
return writeHome(OWN_AGENT, ownAgent);
|
|
44
|
+
}
|
|
45
|
+
export function forgetOwnAgent() {
|
|
46
|
+
return rm(join(kitHome(), OWN_AGENT), { force: true });
|
|
47
|
+
}
|
|
38
48
|
export async function readConfiguration() {
|
|
39
49
|
return { skills: (await readHome(CONFIGURATION))?.skills !== false };
|
|
40
50
|
}
|
package/dist/house-main.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createHash
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { request } from 'node:http';
|
|
4
|
+
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
5
5
|
import { basename, join } from 'node:path';
|
|
6
|
-
import { kitHome } from './home.js';
|
|
6
|
+
import { kitHome, readOwnAgent } from './home.js';
|
|
7
|
+
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
7
8
|
import { refusalOf } from './refusals.js';
|
|
8
9
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
9
10
|
const GIT_USAGE = 'usage: house git push [commit] [--owner]';
|
|
10
11
|
const ARGUMENTS = `the arguments are one JSON object in single quotes, like house search '{"query":"invoice"}'`;
|
|
11
12
|
const CLOSED = "this conversation's House connection is closed; nothing to do from here";
|
|
12
13
|
const REVOKED = 'this conversation no longer has House access; nothing to do from here';
|
|
14
|
+
const UNCONNECTED = "this computer's own agent has no House connection; run kit login";
|
|
15
|
+
const DISCONNECTED = "House no longer accepts this computer's own agent; run kit login";
|
|
13
16
|
const socketPath = process.env.HOUSE_BRIDGE;
|
|
14
17
|
function refused(path, status, text) {
|
|
15
18
|
const refusal = refusalOf(text);
|
|
@@ -42,8 +45,21 @@ function bridged(path, body, headers = { 'content-type': 'application/json' }, t
|
|
|
42
45
|
sent.end(body instanceof Buffer ? body : JSON.stringify(body));
|
|
43
46
|
});
|
|
44
47
|
}
|
|
48
|
+
async function direct(message) {
|
|
49
|
+
const ownAgent = await readOwnAgent();
|
|
50
|
+
if (ownAgent === null)
|
|
51
|
+
throw new Error(UNCONNECTED);
|
|
52
|
+
setGlobalProxyFromEnv();
|
|
53
|
+
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
54
|
+
throw new Error(UNREACHABLE);
|
|
55
|
+
});
|
|
56
|
+
if (answer.status === 200)
|
|
57
|
+
return answer.text;
|
|
58
|
+
throw new Error(answer.status === 401 ? DISCONNECTED : (refusalOf(answer.text)?.text ?? `House answered ${answer.status}`));
|
|
59
|
+
}
|
|
45
60
|
async function mcp(method, params) {
|
|
46
|
-
const
|
|
61
|
+
const message = rpc(method, params);
|
|
62
|
+
const answer = JSON.parse(socketPath === undefined ? await direct(message) : await bridged('/', message));
|
|
47
63
|
if (answer.error !== undefined)
|
|
48
64
|
throw new Error(answer.error.message);
|
|
49
65
|
return answer.result;
|
|
@@ -122,10 +138,10 @@ async function house([verb, argument]) {
|
|
|
122
138
|
return `${tool.description}\n${JSON.stringify(tool.inputSchema)}`;
|
|
123
139
|
}
|
|
124
140
|
const args = argumentsOf(argument);
|
|
125
|
-
if (verb === 'upload_attachment') {
|
|
141
|
+
if (socketPath !== undefined && verb === 'upload_attachment') {
|
|
126
142
|
return JSON.stringify({ attachment: await upload(args.path, args.room_ref, 'upload_attachment needs "path", a local file') });
|
|
127
143
|
}
|
|
128
|
-
if (verb === 'append_record' && Array.isArray(args.attachments)) {
|
|
144
|
+
if (socketPath !== undefined && verb === 'append_record' && Array.isArray(args.attachments)) {
|
|
129
145
|
const attachments = [];
|
|
130
146
|
for (const path of args.attachments) {
|
|
131
147
|
attachments.push(await upload(path, args.room_ref, 'each attachments entry is the path of a local file'));
|
|
@@ -139,15 +155,9 @@ async function house([verb, argument]) {
|
|
|
139
155
|
return text;
|
|
140
156
|
}
|
|
141
157
|
const argv = process.argv.slice(2);
|
|
142
|
-
|
|
143
|
-
process.
|
|
158
|
+
(argv[0] === 'git' ? gitPush(argv.slice(1)) : house(argv)).then((text) => {
|
|
159
|
+
process.stdout.write(`${text}\n`);
|
|
160
|
+
}, (error) => {
|
|
161
|
+
process.stderr.write(`house: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
144
162
|
process.exitCode = 1;
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
(argv[0] === 'git' ? gitPush(argv.slice(1)) : house(argv)).then((text) => {
|
|
148
|
-
process.stdout.write(`${text}\n`);
|
|
149
|
-
}, (error) => {
|
|
150
|
-
process.stderr.write(`house: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
151
|
-
process.exitCode = 1;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
163
|
+
});
|
package/dist/login.js
CHANGED
|
@@ -4,7 +4,8 @@ import { hostname } from 'node:os';
|
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { post } from './api.js';
|
|
7
|
-
import { readEnrolment, writeEnrolment } from './home.js';
|
|
7
|
+
import { forgetOwnAgent, readEnrolment, readOwnAgent, writeEnrolment, writeOwnAgent } from './home.js';
|
|
8
|
+
import { callHouse, rpc } from './mcp.js';
|
|
8
9
|
const DEFAULT_HOUSE = 'https://agents.house';
|
|
9
10
|
function listen() {
|
|
10
11
|
return new Promise((resolve, reject) => {
|
|
@@ -48,6 +49,14 @@ function listen() {
|
|
|
48
49
|
});
|
|
49
50
|
});
|
|
50
51
|
}
|
|
52
|
+
async function accepted(house, credential) {
|
|
53
|
+
const checked = await callHouse(house, credential, rpc('tools/list', {}));
|
|
54
|
+
if (checked.status === 200)
|
|
55
|
+
return true;
|
|
56
|
+
if (checked.status === 401)
|
|
57
|
+
return false;
|
|
58
|
+
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
59
|
+
}
|
|
51
60
|
async function typed(question) {
|
|
52
61
|
const reader = createInterface({ input: process.stdin, output: process.stdout });
|
|
53
62
|
const closed = new Promise((_, reject) => {
|
|
@@ -73,17 +82,27 @@ export async function login(argv) {
|
|
|
73
82
|
const house = values.house ?? enrolled?.house ?? DEFAULT_HOUSE;
|
|
74
83
|
const replaces = process.env.HOUSE_KIT_REPLACES;
|
|
75
84
|
const environment = replaces === undefined ? (values.environment ?? (enrolled?.house === house ? enrolled.environment : undefined)) : undefined;
|
|
85
|
+
const ownAgent = await readOwnAgent();
|
|
86
|
+
const held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
|
|
76
87
|
const verifier = randomBytes(32).toString('base64url');
|
|
77
88
|
const started = {
|
|
78
89
|
...(environment === undefined
|
|
79
90
|
? { act: 'new_environment', label: hostname(), ...(replaces === undefined ? {} : { replaces }) }
|
|
80
91
|
: { act: 'existing_environment', environment_id: environment }),
|
|
81
92
|
code_challenge: createHash('sha256').update(verifier).digest('base64url'),
|
|
93
|
+
...(held ? {} : { headless: true }),
|
|
82
94
|
};
|
|
83
95
|
const exchange = async (code) => {
|
|
84
96
|
const exchanged = await post(house, '/kit/token', { code, code_verifier: verifier });
|
|
85
97
|
await writeEnrolment({ house, environment: exchanged.environment, credential: exchanged.credential });
|
|
86
98
|
process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
|
|
99
|
+
if (exchanged.headless_credential !== undefined) {
|
|
100
|
+
await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
|
|
101
|
+
}
|
|
102
|
+
else if (held && ownAgent.user !== exchanged.user) {
|
|
103
|
+
await forgetOwnAgent();
|
|
104
|
+
process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
|
|
105
|
+
}
|
|
87
106
|
};
|
|
88
107
|
if (values.manual) {
|
|
89
108
|
const { start_url } = await post(house, '/kit', {
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { KIT_VERSION } from './clis.js';
|
|
3
|
+
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
4
|
+
export const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
5
|
+
export function rpc(method, params) {
|
|
6
|
+
return { jsonrpc: '2.0', id: randomUUID(), method, params };
|
|
7
|
+
}
|
|
8
|
+
export function mcpBody(message, operation) {
|
|
9
|
+
return JSON.stringify({
|
|
10
|
+
...message,
|
|
11
|
+
params: {
|
|
12
|
+
...message.params,
|
|
13
|
+
_meta: {
|
|
14
|
+
'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
|
|
15
|
+
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
16
|
+
'io.modelcontextprotocol/clientCapabilities': {},
|
|
17
|
+
...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function callHouse(origin, credential, message, operation = null, signal) {
|
|
23
|
+
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
24
|
+
const answer = await fetch(new URL('/', origin), {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
authorization: `Bearer ${credential}`,
|
|
29
|
+
accept: 'application/json, text/event-stream',
|
|
30
|
+
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
31
|
+
'mcp-method': message.method,
|
|
32
|
+
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
33
|
+
},
|
|
34
|
+
body: mcpBody(message, operation),
|
|
35
|
+
signal,
|
|
36
|
+
});
|
|
37
|
+
return { status: answer.status, text: await answer.text() };
|
|
38
|
+
}
|
package/dist/resident.js
CHANGED
|
@@ -14,7 +14,6 @@ import { SignIns } from './sign-in.js';
|
|
|
14
14
|
import { placeSkillSet } from './skills.js';
|
|
15
15
|
import { holdStream } from './stream.js';
|
|
16
16
|
import { Updates } from './update.js';
|
|
17
|
-
const AGENTS_REREAD_MS = 60 * 60_000;
|
|
18
17
|
function report(error) {
|
|
19
18
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
20
19
|
}
|
|
@@ -95,7 +94,6 @@ export async function resident() {
|
|
|
95
94
|
await house.deliver('/kit/restarted', {}).catch(report);
|
|
96
95
|
await updates.resumed().catch(report);
|
|
97
96
|
logged(agents.refresh());
|
|
98
|
-
setInterval(() => logged(agents.refresh()), AGENTS_REREAD_MS);
|
|
99
97
|
stream = holdStream({
|
|
100
98
|
opened: () => {
|
|
101
99
|
conversations.opened();
|
package/dist/sign-in.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
3
|
import { killTree } from './acp.js';
|
|
4
|
-
import { CLIS,
|
|
4
|
+
import { CLIS, withoutProxy } from './clis.js';
|
|
5
5
|
import { holdSecretInput } from './secret-input.js';
|
|
6
6
|
const ESCAPE = /\u001b\[[\d;]*m/g;
|
|
7
7
|
const LINK = /https:\/\/\S+/;
|
|
8
8
|
const CODE = /^[A-Z0-9]+-[A-Z0-9]+$/;
|
|
9
|
+
function causeOf(error) {
|
|
10
|
+
return error instanceof Error ? error.message : String(error);
|
|
11
|
+
}
|
|
9
12
|
function logged(error) {
|
|
10
|
-
process.stderr.write(`kit: ${
|
|
13
|
+
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
11
14
|
}
|
|
12
15
|
export class SignIns {
|
|
13
16
|
house;
|
|
@@ -28,7 +31,7 @@ export class SignIns {
|
|
|
28
31
|
this.running.add(input.input_id);
|
|
29
32
|
this.changed();
|
|
30
33
|
this.login(input)
|
|
31
|
-
.then(() => this.agents.
|
|
34
|
+
.then(() => this.agents.reread(input.cli))
|
|
32
35
|
.then(() => this.house.deliver(`/kit/inputs/${input.input_id}/ack`, {}))
|
|
33
36
|
.catch(logged)
|
|
34
37
|
.finally(() => {
|
|
@@ -37,9 +40,16 @@ export class SignIns {
|
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
async login(input) {
|
|
40
|
-
|
|
43
|
+
let cli;
|
|
44
|
+
try {
|
|
45
|
+
cli = await this.agents.located(input.cli);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
process.stderr.write(`kit: the ${input.cli} sign-in failed: ${causeOf(error)}\n`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
41
51
|
const login = CLIS[input.cli].login;
|
|
42
|
-
const child = spawn(
|
|
52
|
+
const child = spawn(cli, login.args, {
|
|
43
53
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
44
54
|
detached: true,
|
|
45
55
|
env: withoutProxy(),
|
package/dist/update.js
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { run } from './clis.js';
|
|
7
7
|
import { kitHome } from './home.js';
|
|
8
|
-
const RECORD = 'bash "$0" --linux; printf %s "$?" > "$
|
|
8
|
+
const RECORD = 'outcome=$1; shift; bash "$0" --linux "$@"; printf %s "$?" > "$outcome"; systemctl start house-kit.service';
|
|
9
9
|
function logged(error) {
|
|
10
10
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
11
|
}
|
|
@@ -46,7 +46,7 @@ async function finished(inputId) {
|
|
|
46
46
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
function rerun(script, inputId) {
|
|
49
|
+
function rerun(script, inputId, flags) {
|
|
50
50
|
return new Promise((resolve) => {
|
|
51
51
|
const child = spawn('systemd-run', [
|
|
52
52
|
'--wait',
|
|
@@ -59,6 +59,7 @@ function rerun(script, inputId) {
|
|
|
59
59
|
RECORD,
|
|
60
60
|
script,
|
|
61
61
|
outcome(),
|
|
62
|
+
...flags,
|
|
62
63
|
], { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
63
64
|
child.on('error', () => resolve({ refused: 'no service manager runs this Kit' }));
|
|
64
65
|
child.on('close', () => resolve(null));
|
|
@@ -102,7 +103,7 @@ export class Updates {
|
|
|
102
103
|
await writeFile(path, script, { mode: 0o700 });
|
|
103
104
|
await rm(outcome(), { force: true });
|
|
104
105
|
await writeFile(pending(), `${JSON.stringify({ input_id: input.input_id })}\n`, { mode: 0o600 });
|
|
105
|
-
return (await rerun(path, input.input_id)) ?? (await recorded()) ?? {
|
|
106
|
+
return (await rerun(path, input.input_id, input.update_clis ? ['--update-clis'] : [])) ?? (await recorded()) ?? {
|
|
106
107
|
refused: 'the Kit bootstrap did not finish',
|
|
107
108
|
};
|
|
108
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.2.1-alpha.
|
|
3
|
+
"version": "0.2.1-alpha.17",
|
|
4
4
|
"description": "House Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,8 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@agentclientprotocol/claude-agent-acp": "0.85.1",
|
|
34
|
-
"@agentclientprotocol/codex-acp": "2.1.1"
|
|
35
|
-
"@xai-official/grok": "1.0.46"
|
|
34
|
+
"@agentclientprotocol/codex-acp": "2.1.1"
|
|
36
35
|
},
|
|
37
36
|
"peerDependenciesMeta": {
|
|
38
37
|
"@agentclientprotocol/claude-agent-acp": {
|
|
@@ -40,9 +39,6 @@
|
|
|
40
39
|
},
|
|
41
40
|
"@agentclientprotocol/codex-acp": {
|
|
42
41
|
"optional": true
|
|
43
|
-
},
|
|
44
|
-
"@xai-official/grok": {
|
|
45
|
-
"optional": true
|
|
46
42
|
}
|
|
47
43
|
},
|
|
48
44
|
"devDependencies": {
|