@agentshouse/kit 0.2.1-alpha.15 → 0.2.1-alpha.16

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 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.
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, cliCommand, withoutProxy } from './clis.js';
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 child = spawn(cliCommand(kind), CLIS[kind].args, {
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, cliCommand, install, run } from './clis.js';
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(cliCommand(kind), probe.command, 30_000)).status !== 0)
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 : { signed_in: true, models: known.models };
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
- installs = new Map();
80
- offers = new Map();
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
- const installing = this.installed()
104
- .then(() => reading)
105
- .then(({ agents }) => this.install(agents));
106
- this.installing = installing;
107
- if (this.reporting === null)
108
- this.reporting = this.reports();
109
- else
110
- this.again = true;
111
- return Promise.all([installing, this.reporting]).then(() => undefined);
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
- async send() {
119
- const agents = [];
120
- for (const [kind, installed] of this.installs) {
121
- const offered = installed.release === null ? SIGNED_OUT : await offer(kind, this.offers.get(kind) ?? SIGNED_OUT);
122
- this.offers.set(kind, offered);
123
- agents.push({ kind, ...installed, ...offered });
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
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.2.1-alpha.15",
3
+ "version": "0.2.1-alpha.16",
4
4
  "hosts": {
5
5
  "linux": {
6
- "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.15/connect-linux.sh)",
6
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.16/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.15/connect-linux.sh"
9
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.16/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.15/connect-windows.ps1)))",
20
+ "command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.16/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.15/connect-windows.ps1"
23
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.16/connect-windows.ps1"
24
24
  },
25
25
  "workspace": {
26
26
  "argument": "--workspace",
@@ -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
- package: '@agentclientprotocol/codex-acp',
34
- bin: 'codex-acp',
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: ['cli', 'login', 'status'] },
37
- login: { args: ['cli', 'login', '--device-auth'], code: 'show' },
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
- package: '@agentclientprotocol/claude-agent-acp',
46
- bin: 'claude-agent-acp',
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: ['--cli', 'auth', 'status'] },
49
- login: { args: ['--cli', 'auth', 'login', '--claudeai'], code: 'collect' },
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
- export function pinned(kind) {
81
- return KIT_PACKAGE.peerDependencies[CLIS[kind].package];
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 cliCommand(kind) {
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
- export async function installedRelease(kind) {
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, { stdio: ['ignore', 'pipe', 'pipe'], timeout: timeoutMs, env: withoutProxy() });
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
- export async function install(kind) {
116
- const release = pinned(kind);
117
- if ((await installedRelease(kind)) === release)
118
- return { release, failure: null };
119
- const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', `${CLIS[kind].package}@${release}`], 15 * 60_000);
120
- if (ran.status !== 0) {
121
- const failure = ran.output.trim() || `npm exited with ${ran.status ?? 'a signal'}`;
122
- process.stderr.write(`kit: ${kind} did not install: ${failure}\n`);
123
- return { release: null, failure };
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 { release: await installedRelease(kind), failure: null };
172
+ return found.includes('-') && !minimum.includes('-');
126
173
  }
@@ -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.installed(), conversation.killed.signal);
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/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, cliCommand, withoutProxy } from './clis.js';
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: ${error instanceof Error ? error.message : String(error)}\n`);
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.report())
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
- await this.agents.installed();
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(cliCommand(input.cli), login.args, {
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 "$?" > "$1"; systemctl start house-kit.service';
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.15",
3
+ "version": "0.2.1-alpha.16",
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": {