@agentshouse/kit 0.2.1-ceeaf32286734 → 0.2.1-cf268dff1869b
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 +13 -13
- package/dist/agents.js +48 -22
- package/dist/building.js +7 -3
- package/dist/clis.js +19 -49
- package/dist/connection.js +2 -13
- package/dist/end.js +1 -1
- package/dist/home.js +0 -7
- package/dist/placement.js +10 -18
- package/dist/resident.js +9 -10
- package/dist/scope.js +12 -4
- package/dist/update.js +105 -159
- package/dist/wire.js +23 -5
- package/package.json +2 -2
- package/dist/clis-main.js +0 -23
package/README.md
CHANGED
|
@@ -9,25 +9,25 @@ Run `sh -c 'kit=$(curl -fsSL "$0") || { echo "Kit could not be downloaded; run t
|
|
|
9
9
|
or `& { try { $kit = irm agents.house/kit.ps1 } catch { throw 'Kit could not be downloaded; run this command again.' }; & ([scriptblock]::Create($kit)) @args }` on Windows, which run nothing of a failed download, and again to update; options follow `agents.house/kit`.
|
|
10
10
|
On macOS and Linux it installs Kit natively under your own account, without an admin password, as a service that runs
|
|
11
11
|
while you are logged in. `kit` and `house` are then on your login shell's `PATH`, and your workspace is `~/AgentsHouse` unless you
|
|
12
|
-
pass `--workspace <path>`.
|
|
12
|
+
pass `--workspace <path>`. Every run places the Kit release your Building serves, with the Node.js and agent adapters it runs on, in a directory of its own beside the one that runs, then starts it; the earlier one stays until the next release. Kit also updates itself: when your Building takes another release, Kit places it the same way and, once no turn is open, ends so its service, or the container's restart policy, starts it. When House refuses the Kit that runs as too old or too new, Kit places your Building's release at once, ending any running turn, and ends the same way; while it cannot, it stays offline, prints why with the command that installs it again, and tries again regularly.
|
|
13
13
|
|
|
14
|
-
Add `--container` to run Kit in one Docker container instead, with your
|
|
15
|
-
workspace
|
|
16
|
-
|
|
14
|
+
Add `--container` to run Kit in one Docker container instead, with your workspace mounted at `/agents/house`; your Agents then reach only that
|
|
15
|
+
workspace. The container's image holds no Kit release and no Node.js: every run places them in Kit home's `kit` folder, which a container created again keeps. On Windows,
|
|
16
|
+
Kit home is the Docker volume `house-kit-home` and the container is the one choice. Append a `kit` or `house` command to the container's options to run it in that container.
|
|
17
17
|
Running the command for the other choice on a connected computer asks you to
|
|
18
18
|
confirm, then replaces its Environment, whose Agents end, with a new one. Running it with a different `HOUSE_KIT_HOME`
|
|
19
19
|
from the running Kit process asks you to confirm before it replaces that process, and leaves the earlier Kit home as it was.
|
|
20
20
|
|
|
21
|
-
On your own Linux server, run the Linux command with `--linux` to
|
|
22
|
-
install Kit natively, with its workspace at `/agents/house`; `kit` and
|
|
23
|
-
`house` are then on the server's `PATH`.
|
|
21
|
+
On your own Linux server, run the Linux command with `--linux` to install Kit natively, with its workspace at `/agents/house` and a missing `ca-certificates`, `curl` or `git` installed through apt; `kit` and `house` are then on the server's `PATH`.
|
|
24
22
|
|
|
25
|
-
Kit runs the
|
|
26
|
-
|
|
27
|
-
each CLI
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
Kit is built for macOS 27 on Apple silicon, Ubuntu 26.04 LTS on amd64 and Windows 11 on amd64. On another release, distribution or architecture the command prints one warning line, such as `Warning: macOS 26.7.1 is outdated; update to macOS 27.`, and installs anyway. It refuses only an architecture other than amd64 and arm64, and a native install on a Linux without glibc, where `--container` still runs Kit. On a Linux without systemd it installs and connects Kit but starts nothing: run the `kit resident` command it prints under your own service manager, restarting it whenever it ends with a failure. `--container` installs Docker Engine on Ubuntu and Debian; another Linux needs a working Docker Engine first.
|
|
24
|
+
|
|
25
|
+
Kit runs each agent CLI through the link your login shell finds and installs a missing one through that CLI's own
|
|
26
|
+
installer. When it starts and regularly after, Kit runs each CLI's own update command, such as `claude update`, beside
|
|
27
|
+
your conversations: a running conversation keeps its version and the next one runs the new release. A CLI older than
|
|
28
|
+
the oldest release Kit is built for still runs, though its conversations may fail: Kit tells House its version, that
|
|
29
|
+
oldest release and its update command. Kit also gives every Agent on the Environment the agent skills of
|
|
30
|
+
`@agentshouse/skills`, at the version each Kit release installs. Add `--no-skills` to the command to leave them out; running it again without the flag brings them back.
|
|
31
31
|
|
|
32
32
|
## Commands
|
|
33
33
|
|
package/dist/agents.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { client } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
4
|
import { signedOut, startAdapter } from './acp.js';
|
|
5
|
-
import { CLIS, KIT_RELEASE, below,
|
|
5
|
+
import { CLIS, KIT_RELEASE, below, installCli, locate, run, updateCli, versionOf } from './clis.js';
|
|
6
6
|
import { agentBase, kitHome } from './home.js';
|
|
7
|
-
import { updatesItself } from './placement.js';
|
|
8
7
|
import { holdTranscription, transcriptionReport } from './transcription.js';
|
|
9
8
|
import { KIT_WIRE } from './wire.js';
|
|
10
9
|
// Two quiet seconds let a fresh conversation start before background model discovery competes for its CPU.
|
|
11
10
|
const MODEL_PROBE_QUIET_MS = 2_000;
|
|
11
|
+
// A CLI's maker can publish several releases a day, so running each chosen CLI's update command hourly brings a new one to the next conversation within the hour for one small check.
|
|
12
|
+
export const CLI_UPDATE_MS = 60 * 60_000;
|
|
12
13
|
// A sign-in's model read that has not answered in ten seconds leaves its sign-in to the status check, so a hung CLI never holds the sign-in's result.
|
|
13
14
|
const SIGNED_IN_READ_MS = 10_000;
|
|
14
15
|
const SIGNED_OUT = { signed_in: false, models: [], modes: [] };
|
|
@@ -110,10 +111,11 @@ async function offer(kind, cli, known, signal) {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
function reported(kind, reading) {
|
|
114
|
+
const { minimum, update } = CLIS[kind];
|
|
113
115
|
return {
|
|
114
116
|
kind,
|
|
115
117
|
release: reading.release,
|
|
116
|
-
...(reading.
|
|
118
|
+
...(reading.release !== null && below(reading.release, minimum) ? { minimum, update } : {}),
|
|
117
119
|
failure: reading.failure,
|
|
118
120
|
...(reading.installing === undefined ? {} : { installing: reading.installing }),
|
|
119
121
|
signed_in: reading.signed_in,
|
|
@@ -123,10 +125,6 @@ function reported(kind, reading) {
|
|
|
123
125
|
};
|
|
124
126
|
}
|
|
125
127
|
const NOT_READY = "the Agent's CLI is not ready on this computer";
|
|
126
|
-
function tooOld(kind, release) {
|
|
127
|
-
const cli = CLIS[kind];
|
|
128
|
-
return `${cli.bin} ${release} is older than ${cli.minimum}, the oldest this Kit runs`;
|
|
129
|
-
}
|
|
130
128
|
async function sshHostKey() {
|
|
131
129
|
// Listing a local host key takes a moment, so ten seconds only bounds a stuck ssh-keygen.
|
|
132
130
|
const listed = await run('ssh-keygen', ['-l', '-E', 'sha256', '-f', '/etc/ssh/ssh_host_ed25519_key.pub'], 10_000);
|
|
@@ -168,13 +166,15 @@ export class Agents {
|
|
|
168
166
|
sending = Promise.resolve();
|
|
169
167
|
sent = null;
|
|
170
168
|
refreshing = 0;
|
|
169
|
+
loaded = Promise.resolve();
|
|
170
|
+
updating = null;
|
|
171
171
|
changed;
|
|
172
172
|
constructor(house, changed = () => undefined) {
|
|
173
173
|
this.house = house;
|
|
174
174
|
this.changed = changed;
|
|
175
175
|
}
|
|
176
176
|
idle() {
|
|
177
|
-
return this.refreshing === 0;
|
|
177
|
+
return this.refreshing === 0 && this.updating === null;
|
|
178
178
|
}
|
|
179
179
|
read() {
|
|
180
180
|
return this.reading.then(() => undefined, () => undefined);
|
|
@@ -190,7 +190,7 @@ export class Agents {
|
|
|
190
190
|
return this.desired;
|
|
191
191
|
});
|
|
192
192
|
this.reading = reading;
|
|
193
|
-
reading
|
|
193
|
+
this.loaded = reading
|
|
194
194
|
.then(({ agents, transcribe }) => Promise.all([
|
|
195
195
|
holdTranscription(transcribe)
|
|
196
196
|
.then((changed) => (changed ? this.report() : undefined))
|
|
@@ -206,18 +206,22 @@ export class Agents {
|
|
|
206
206
|
});
|
|
207
207
|
return reading.then(() => undefined);
|
|
208
208
|
}
|
|
209
|
+
keepCurrent() {
|
|
210
|
+
this.updateAll(this.loaded);
|
|
211
|
+
setInterval(() => this.updateAll(Promise.resolve()), CLI_UPDATE_MS);
|
|
212
|
+
}
|
|
209
213
|
cli(kind) {
|
|
210
214
|
this.starts += 1;
|
|
211
215
|
return this.queue(kind, async () => {
|
|
212
216
|
const resolved = await this.resolve(kind);
|
|
213
217
|
if (!('cli' in resolved)) {
|
|
214
218
|
this.update(kind, resolved);
|
|
215
|
-
throw new Error(
|
|
219
|
+
throw new Error(NOT_READY);
|
|
216
220
|
}
|
|
217
221
|
const { cli, release } = resolved;
|
|
218
222
|
const known = this.readings.get(kind);
|
|
219
|
-
const kept = known?.release === null
|
|
220
|
-
if (known?.release !== release ||
|
|
223
|
+
const kept = known?.release === null ? SIGNED_OUT : (known ?? SIGNED_OUT);
|
|
224
|
+
if (known?.release !== release || this.installs.has(kind)) {
|
|
221
225
|
this.update(kind, { release, failure: null, signed_in: kept.signed_in, models: kept.models, modes: kept.modes });
|
|
222
226
|
}
|
|
223
227
|
const read = this.nextRead(kind);
|
|
@@ -284,9 +288,6 @@ export class Agents {
|
|
|
284
288
|
return signal === undefined ? queued : Promise.race([queued, ended(signal)]);
|
|
285
289
|
}
|
|
286
290
|
async ensure(kind, signal, owner = {}) {
|
|
287
|
-
const failed = await installAdapter(kind, signal);
|
|
288
|
-
if (failed !== null)
|
|
289
|
-
throw new Error(failed);
|
|
290
291
|
const found = await locate(kind, signal);
|
|
291
292
|
if (found !== null)
|
|
292
293
|
return found;
|
|
@@ -305,7 +306,7 @@ export class Agents {
|
|
|
305
306
|
async resolve(kind, signal, owner) {
|
|
306
307
|
let cli;
|
|
307
308
|
try {
|
|
308
|
-
cli = await
|
|
309
|
+
cli = await this.ensure(kind, signal, owner);
|
|
309
310
|
}
|
|
310
311
|
catch (error) {
|
|
311
312
|
const failure = causeOf(error);
|
|
@@ -315,9 +316,6 @@ export class Agents {
|
|
|
315
316
|
const release = await versionOf(cli, signal);
|
|
316
317
|
if (release === null)
|
|
317
318
|
return { release: null, failure: `${cli} --version names no version`, ...SIGNED_OUT };
|
|
318
|
-
const minimum = CLIS[kind].minimum;
|
|
319
|
-
if (below(release, minimum))
|
|
320
|
-
return { release, minimum, failure: null, ...SIGNED_OUT };
|
|
321
319
|
return { cli, release };
|
|
322
320
|
}
|
|
323
321
|
async examine(kind, known, read, signal, owner) {
|
|
@@ -327,6 +325,35 @@ export class Agents {
|
|
|
327
325
|
if (this.reads.get(kind) === current && !signal?.aborted)
|
|
328
326
|
this.hold(kind, reading);
|
|
329
327
|
}
|
|
328
|
+
updateAll(after) {
|
|
329
|
+
if (this.updating !== null)
|
|
330
|
+
return;
|
|
331
|
+
this.updating = after
|
|
332
|
+
.then(async () => {
|
|
333
|
+
for (const kind of this.desired.agents) {
|
|
334
|
+
if (this.installs.has(kind) || !this.readings.get(kind)?.release)
|
|
335
|
+
continue;
|
|
336
|
+
const failure = await updateCli(kind);
|
|
337
|
+
if (failure !== null)
|
|
338
|
+
process.stderr.write(`kit: ${kind} did not update: ${failure}\n`);
|
|
339
|
+
await this.readVersion(kind);
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
.catch(logged)
|
|
343
|
+
.finally(() => {
|
|
344
|
+
this.updating = null;
|
|
345
|
+
this.changed();
|
|
346
|
+
});
|
|
347
|
+
this.changed();
|
|
348
|
+
}
|
|
349
|
+
async readVersion(kind) {
|
|
350
|
+
const cli = await locate(kind);
|
|
351
|
+
const release = cli === null ? null : await versionOf(cli);
|
|
352
|
+
const current = this.readings.get(kind);
|
|
353
|
+
if (release === null || !current?.release || current.release === release)
|
|
354
|
+
return;
|
|
355
|
+
this.update(kind, { ...current, release });
|
|
356
|
+
}
|
|
330
357
|
async quietOffer(kind, cli, known) {
|
|
331
358
|
for (let seen = -1; seen !== this.starts;) {
|
|
332
359
|
seen = this.starts;
|
|
@@ -336,7 +363,7 @@ export class Agents {
|
|
|
336
363
|
}
|
|
337
364
|
async load(kind) {
|
|
338
365
|
const known = this.readings.get(kind);
|
|
339
|
-
if (known !== undefined && known.failure === null
|
|
366
|
+
if (known !== undefined && known.failure === null)
|
|
340
367
|
return;
|
|
341
368
|
await this.examine(kind, SIGNED_OUT, (kind, cli, known) => this.quietOffer(kind, cli, known));
|
|
342
369
|
await this.report();
|
|
@@ -353,7 +380,6 @@ export class Agents {
|
|
|
353
380
|
...(form === undefined ? {} : { form }),
|
|
354
381
|
kit_release: KIT_RELEASE,
|
|
355
382
|
wire: KIT_WIRE,
|
|
356
|
-
updates_itself: await updatesItself(),
|
|
357
383
|
agent_base: agentBase(),
|
|
358
384
|
agents,
|
|
359
385
|
transcription: transcriptionReport(),
|
package/dist/building.js
CHANGED
|
@@ -16,10 +16,14 @@ async function served(name, signal) {
|
|
|
16
16
|
await answer.body?.cancel();
|
|
17
17
|
throw new Error(`House answered ${answer.status} for ${name}`);
|
|
18
18
|
}
|
|
19
|
-
export async function
|
|
19
|
+
export async function servedRelease(signal) {
|
|
20
20
|
const manifest = (await (await served(MANIFEST, signal)).json());
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const installer = manifest.hosts?.linux;
|
|
22
|
+
const sha256 = typeof installer === 'string' ? manifest.files?.[installer]?.sha256 : undefined;
|
|
23
|
+
if (typeof manifest.version !== 'string' || typeof installer !== 'string' || typeof sha256 !== 'string') {
|
|
24
|
+
throw new Error(`House serves no Kit release in ${MANIFEST}`);
|
|
25
|
+
}
|
|
26
|
+
return { version: manifest.version, installer, sha256 };
|
|
23
27
|
}
|
|
24
28
|
export async function downloadServed(name, sha256, destination, signal, most = Infinity) {
|
|
25
29
|
const answer = await served(name, signal);
|
package/dist/clis.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { constants, readFileSync } from 'node:fs';
|
|
4
|
-
import { access, mkdir, readFile, rename,
|
|
4
|
+
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { homedir, userInfo } from 'node:os';
|
|
6
6
|
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
7
|
-
import {
|
|
8
|
-
import { kitHome } from './home.js';
|
|
7
|
+
import { releaseDirectory } from './placement.js';
|
|
9
8
|
import { childEnvironment } from './relay.js';
|
|
10
9
|
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
11
10
|
function houseRule(houses) {
|
|
@@ -74,6 +73,7 @@ export const CLIS = {
|
|
|
74
73
|
bin: 'codex',
|
|
75
74
|
minimum: '0.160.1',
|
|
76
75
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
76
|
+
update: 'codex update',
|
|
77
77
|
adapter: {
|
|
78
78
|
package: '@agentclientprotocol/codex-acp',
|
|
79
79
|
bin: 'codex-acp',
|
|
@@ -97,6 +97,7 @@ export const CLIS = {
|
|
|
97
97
|
bin: 'claude',
|
|
98
98
|
minimum: '2.1.303',
|
|
99
99
|
install: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
100
|
+
update: 'claude update',
|
|
100
101
|
adapter: {
|
|
101
102
|
package: '@agentclientprotocol/claude-agent-acp',
|
|
102
103
|
bin: 'claude-agent-acp',
|
|
@@ -127,6 +128,7 @@ export const CLIS = {
|
|
|
127
128
|
bin: 'grok',
|
|
128
129
|
minimum: '1.0.46',
|
|
129
130
|
install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
131
|
+
update: 'grok update',
|
|
130
132
|
adapter: null,
|
|
131
133
|
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
132
134
|
fullAccess: null,
|
|
@@ -142,25 +144,13 @@ export const CLIS = {
|
|
|
142
144
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
143
145
|
export const KIT_RELEASE = KIT_PACKAGE.version;
|
|
144
146
|
const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
|
|
145
|
-
// An install still running after fifteen minutes is ended
|
|
147
|
+
// An install or update still running after fifteen minutes is ended as failed, so a hung download can neither hold its CLI's report nor keep the Kit busy.
|
|
146
148
|
const INSTALL_MS = 15 * 60_000;
|
|
147
149
|
// A login shell or a version read is ended after half a minute, so a profile that waits for input cannot stall the report.
|
|
148
150
|
const READ_MS = 30_000;
|
|
149
151
|
const CAUSE_CHARACTERS = 4000;
|
|
150
|
-
function prefix(kind) {
|
|
151
|
-
return join(kitHome(), 'agents', kind);
|
|
152
|
-
}
|
|
153
152
|
export function adapterCommand(kind) {
|
|
154
|
-
return join(
|
|
155
|
-
}
|
|
156
|
-
async function installedAdapter(kind) {
|
|
157
|
-
try {
|
|
158
|
-
const manifest = join(prefix(kind), 'node_modules', CLIS[kind].adapter.package, 'package.json');
|
|
159
|
-
return JSON.parse(await readFile(manifest, 'utf8')).version;
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
153
|
+
return join(releaseDirectory(KIT_RELEASE), 'adapters', kind, 'node_modules', '.bin', CLIS[kind].adapter.bin);
|
|
164
154
|
}
|
|
165
155
|
export function run(command, args, timeoutMs, detached = false, signal) {
|
|
166
156
|
return new Promise((resolve) => {
|
|
@@ -204,38 +194,15 @@ export function failureOf(ran, program) {
|
|
|
204
194
|
export function adapterArchive(kind) {
|
|
205
195
|
return `${kind}-${KIT_PACKAGE.peerDependencies[CLIS[kind].adapter.package]}.tar.gz`;
|
|
206
196
|
}
|
|
207
|
-
|
|
208
|
-
const adapter = CLIS[kind].adapter;
|
|
209
|
-
if (adapter === null)
|
|
210
|
-
return null;
|
|
211
|
-
if ((await installedAdapter(kind)) === KIT_PACKAGE.peerDependencies[adapter.package])
|
|
212
|
-
return null;
|
|
213
|
-
const name = adapterArchive(kind);
|
|
214
|
-
const archive = `${prefix(kind)}.tar.gz`;
|
|
197
|
+
async function bounded(program, work, signal) {
|
|
215
198
|
const deadline = AbortSignal.timeout(INSTALL_MS);
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return `House serves no ${name}`;
|
|
221
|
-
await rm(prefix(kind), { recursive: true, force: true });
|
|
222
|
-
await mkdir(prefix(kind), { recursive: true });
|
|
223
|
-
await downloadServed(name, sha256, archive, bounded);
|
|
224
|
-
const ran = await run('tar', ['-xzf', archive, '-C', prefix(kind)], INSTALL_MS, true, signal);
|
|
225
|
-
return ran.status === 0 ? null : failureOf(ran, 'tar');
|
|
226
|
-
}
|
|
227
|
-
catch (error) {
|
|
228
|
-
if (deadline.aborted && signal?.aborted !== true)
|
|
229
|
-
return `${name} did not arrive within ${INSTALL_MS / 1000} seconds`;
|
|
230
|
-
return error instanceof Error ? error.message : String(error);
|
|
231
|
-
}
|
|
232
|
-
finally {
|
|
233
|
-
await rm(archive, { force: true });
|
|
234
|
-
}
|
|
199
|
+
const ran = await work(signal === undefined ? deadline : AbortSignal.any([signal, deadline]));
|
|
200
|
+
if (deadline.aborted && signal?.aborted !== true)
|
|
201
|
+
return `${program} did not end within ${INSTALL_MS / 1000} seconds`;
|
|
202
|
+
return ran.status === 0 ? null : failureOf(ran, program);
|
|
235
203
|
}
|
|
236
|
-
export
|
|
237
|
-
|
|
238
|
-
return ran.status === 0 ? null : failureOf(ran, 'the install');
|
|
204
|
+
export function installCli(kind, signal) {
|
|
205
|
+
return bounded('the install', (within) => run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, within), signal);
|
|
239
206
|
}
|
|
240
207
|
function loginShell() {
|
|
241
208
|
return process.env.SHELL || userInfo().shell;
|
|
@@ -243,8 +210,8 @@ function loginShell() {
|
|
|
243
210
|
const FOUND = 'house-kit-cli ';
|
|
244
211
|
const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
|
|
245
212
|
const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$HOME/.local/bin" ;; esac; export PATH';
|
|
246
|
-
function inLoginShell(script, argument = '', signal) {
|
|
247
|
-
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`],
|
|
213
|
+
function inLoginShell(script, argument = '', signal, timeoutMs = READ_MS) {
|
|
214
|
+
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], timeoutMs, true, signal);
|
|
248
215
|
}
|
|
249
216
|
export async function onLoginPath(program, signal) {
|
|
250
217
|
const ran = await inLoginShell(LOOKUP, program, signal);
|
|
@@ -253,6 +220,9 @@ export async function onLoginPath(program, signal) {
|
|
|
253
220
|
export function locate(kind, signal) {
|
|
254
221
|
return onLoginPath(CLIS[kind].bin, signal);
|
|
255
222
|
}
|
|
223
|
+
export function updateCli(kind) {
|
|
224
|
+
return bounded('the update', (within) => inLoginShell(CLIS[kind].update, '', within, INSTALL_MS));
|
|
225
|
+
}
|
|
256
226
|
export async function loginPath() {
|
|
257
227
|
const marker = 'house-kit-path ';
|
|
258
228
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
package/dist/connection.js
CHANGED
|
@@ -4,7 +4,7 @@ import { end, REPLACED, signInAgain } from './end.js';
|
|
|
4
4
|
import { readEnrolment } from './home.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
6
|
import { Silence } from './silence.js';
|
|
7
|
-
import { endUnserved, KIT_WIRE } from './wire.js';
|
|
7
|
+
import { endUnserved, KIT_WIRE, unserved } from './wire.js';
|
|
8
8
|
const PATH = '/kit/stream';
|
|
9
9
|
const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
|
|
10
10
|
const REFUSAL_HEADER = 'x-house-refusal';
|
|
@@ -32,11 +32,10 @@ subscribe('undici:request:headers', (message) => {
|
|
|
32
32
|
export function holdConnection(handlers) {
|
|
33
33
|
let connection = null;
|
|
34
34
|
let socket = null;
|
|
35
|
-
let paused = false;
|
|
36
35
|
let failures = 0;
|
|
37
36
|
const connect = async () => {
|
|
38
37
|
const { house, credential } = (await readEnrolment());
|
|
39
|
-
if (
|
|
38
|
+
if (socket !== null || unserved())
|
|
40
39
|
return;
|
|
41
40
|
refused = null;
|
|
42
41
|
const opening = new WebSocket(relayed(connectionUrl(house)), {
|
|
@@ -65,8 +64,6 @@ export function holdConnection(handlers) {
|
|
|
65
64
|
endUnserved(refused.text);
|
|
66
65
|
if (!opened && refused !== null)
|
|
67
66
|
process.stderr.write(`kit: ${refused.message}\n`);
|
|
68
|
-
if (paused)
|
|
69
|
-
return;
|
|
70
67
|
// A closed connection reopens in one to three seconds, spread so Kit processes one restart closed return apart; an opening House refused or left unanswered backs off on the jittered curve, retryable or not, since a resident outlives a House deploy.
|
|
71
68
|
setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
|
|
72
69
|
};
|
|
@@ -104,13 +101,5 @@ export function holdConnection(handlers) {
|
|
|
104
101
|
connection.send(JSON.stringify(notification));
|
|
105
102
|
return true;
|
|
106
103
|
},
|
|
107
|
-
pause() {
|
|
108
|
-
paused = true;
|
|
109
|
-
socket?.close();
|
|
110
|
-
return () => {
|
|
111
|
-
paused = false;
|
|
112
|
-
void connect();
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
104
|
};
|
|
116
105
|
}
|
package/dist/end.js
CHANGED
|
@@ -14,7 +14,7 @@ export function end(reason) {
|
|
|
14
14
|
export function restart(reason) {
|
|
15
15
|
exit(reason, 1);
|
|
16
16
|
}
|
|
17
|
-
function updateLine(lead) {
|
|
17
|
+
export function updateLine(lead) {
|
|
18
18
|
const command = installCommand();
|
|
19
19
|
return command === null ? `${lead}; install it again.` : `${lead}. Update it: ${command}`;
|
|
20
20
|
}
|
package/dist/home.js
CHANGED
|
@@ -8,7 +8,6 @@ const ENROLMENT = 'credential.json';
|
|
|
8
8
|
const DISCONNECTED = 'disconnected.json';
|
|
9
9
|
const CONFIGURATION = 'kit.json';
|
|
10
10
|
const OWN_AGENT = 'own-agent.json';
|
|
11
|
-
const RESTART = 'restart';
|
|
12
11
|
let defaulted;
|
|
13
12
|
function writable(directory) {
|
|
14
13
|
try {
|
|
@@ -132,12 +131,6 @@ export async function forgetRejected(rejected) {
|
|
|
132
131
|
export function enrolled() {
|
|
133
132
|
return existsSync(join(kitHome(), ENROLMENT));
|
|
134
133
|
}
|
|
135
|
-
export function restartAsked() {
|
|
136
|
-
return existsSync(join(kitHome(), RESTART));
|
|
137
|
-
}
|
|
138
|
-
export function forgetRestart() {
|
|
139
|
-
return rm(join(kitHome(), RESTART), { force: true });
|
|
140
|
-
}
|
|
141
134
|
export function readOwnAgent() {
|
|
142
135
|
return readHome(OWN_AGENT);
|
|
143
136
|
}
|
package/dist/placement.js
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { run } from './clis.js';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
4
3
|
import { kitHome } from './home.js';
|
|
5
|
-
export function service() {
|
|
6
|
-
if (process.platform !== 'linux' || !process.env.INVOCATION_ID)
|
|
7
|
-
return null;
|
|
8
|
-
return process.env.MANAGERPID ? 'user' : 'system';
|
|
9
|
-
}
|
|
10
|
-
export function throughSudo() {
|
|
11
|
-
return service() === 'system' && process.getuid() !== 0;
|
|
12
|
-
}
|
|
13
|
-
export async function updatesItself() {
|
|
14
|
-
if (service() === null)
|
|
15
|
-
return false;
|
|
16
|
-
if (!throughSudo())
|
|
17
|
-
return true;
|
|
18
|
-
// sudo answers at once, so ten seconds only bounds a stuck sudo.
|
|
19
|
-
return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
|
|
20
|
-
}
|
|
21
4
|
function recorded(name) {
|
|
22
5
|
try {
|
|
23
6
|
return readFileSync(join(kitHome(), name), 'utf8').trim();
|
|
@@ -37,3 +20,12 @@ export function machine() {
|
|
|
37
20
|
return placed;
|
|
38
21
|
return process.platform === 'darwin' ? 'macos' : 'linux';
|
|
39
22
|
}
|
|
23
|
+
export function placedPrefix() {
|
|
24
|
+
const prefix = process.env.HOUSE_KIT_PREFIX;
|
|
25
|
+
if (prefix === undefined || prefix === '')
|
|
26
|
+
throw new Error('this Kit process was not started by a launcher its installer placed');
|
|
27
|
+
return resolve(prefix);
|
|
28
|
+
}
|
|
29
|
+
export function releaseDirectory(release) {
|
|
30
|
+
return join(placedPrefix(), 'releases', release);
|
|
31
|
+
}
|
package/dist/resident.js
CHANGED
|
@@ -11,7 +11,7 @@ import { received } from './bridge.js';
|
|
|
11
11
|
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
13
|
import { restart, unknownMethod } from './end.js';
|
|
14
|
-
import { agentBase, enrolled,
|
|
14
|
+
import { agentBase, enrolled, kitHome, ownerLock, ownerSocket, readEnrolment } from './home.js';
|
|
15
15
|
import { Calls } from './calls.js';
|
|
16
16
|
import { killDescendants } from './scope.js';
|
|
17
17
|
import { removeInputs, Scripts } from './scripts.js';
|
|
@@ -20,6 +20,7 @@ import { SignIns } from './sign-in.js';
|
|
|
20
20
|
import { placeSkillSet } from './skills.js';
|
|
21
21
|
import { holdConnection } from './connection.js';
|
|
22
22
|
import { KitUpdate } from './update.js';
|
|
23
|
+
import { takeUnservedRelease } from './wire.js';
|
|
23
24
|
import { codexLauncher } from './work.js';
|
|
24
25
|
function report(error) {
|
|
25
26
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -110,7 +111,6 @@ function connected() {
|
|
|
110
111
|
}
|
|
111
112
|
async function started() {
|
|
112
113
|
await connected();
|
|
113
|
-
await forgetRestart();
|
|
114
114
|
watch(kitHome(), () => {
|
|
115
115
|
if (enrolled())
|
|
116
116
|
return;
|
|
@@ -140,11 +140,8 @@ export async function resident() {
|
|
|
140
140
|
if (!idle()) {
|
|
141
141
|
reported = false;
|
|
142
142
|
}
|
|
143
|
-
else if (
|
|
144
|
-
restart(
|
|
145
|
-
}
|
|
146
|
-
else if (update.held() && conversations.quiet()) {
|
|
147
|
-
update.apply(connection.pause());
|
|
143
|
+
else if (update.placedRelease() !== null && conversations.quiet()) {
|
|
144
|
+
restart(`Kit ${update.placedRelease()} is placed; its supervisor starts it`);
|
|
148
145
|
}
|
|
149
146
|
else if (!reported) {
|
|
150
147
|
reported = true;
|
|
@@ -161,7 +158,9 @@ export async function resident() {
|
|
|
161
158
|
changed,
|
|
162
159
|
});
|
|
163
160
|
const signIns = new SignIns(house, agents, calls);
|
|
164
|
-
const update = new KitUpdate(calls
|
|
161
|
+
const update = new KitUpdate(calls);
|
|
162
|
+
if (process.env.HOUSE_KIT_SANDBOX !== '1')
|
|
163
|
+
takeUnservedRelease((release) => logged(update.takeRefused(release)));
|
|
165
164
|
removeInputs();
|
|
166
165
|
const scripts = new Scripts(calls, (notification) => connection?.send(notification) ?? false);
|
|
167
166
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
@@ -172,8 +171,8 @@ export async function resident() {
|
|
|
172
171
|
});
|
|
173
172
|
}
|
|
174
173
|
await house.deliver('/kit/restarted', {}).catch(report);
|
|
175
|
-
await update.resumed().catch(report);
|
|
176
174
|
logged(agents.refresh());
|
|
175
|
+
agents.keepCurrent();
|
|
177
176
|
connection = holdConnection({
|
|
178
177
|
opened: () => {
|
|
179
178
|
conversations.opened();
|
|
@@ -185,7 +184,7 @@ export async function resident() {
|
|
|
185
184
|
if (call.kind === 'sign_in')
|
|
186
185
|
signIns.start(call);
|
|
187
186
|
else if (call.kind === 'update')
|
|
188
|
-
update.
|
|
187
|
+
update.take(call);
|
|
189
188
|
else if (call.kind === 'run_wizard' || call.kind === 'run_function')
|
|
190
189
|
scripts.start(call);
|
|
191
190
|
else if (CONVERSATION_METHODS.has(call.kind))
|
package/dist/scope.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { mkdirSync, readdirSync, readFileSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, readdirSync, readFileSync, realpathSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
const LISTING_BYTES = 1 << 28;
|
|
5
5
|
const CGROUPS = '/sys/fs/cgroup';
|
|
@@ -122,15 +122,23 @@ function members(group) {
|
|
|
122
122
|
}
|
|
123
123
|
return pids;
|
|
124
124
|
}
|
|
125
|
+
function besideCli(cli) {
|
|
126
|
+
try {
|
|
127
|
+
return `${realpathSync(cli)}-`;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
125
133
|
export class Scope {
|
|
126
134
|
marker;
|
|
127
|
-
|
|
135
|
+
helpers;
|
|
128
136
|
group;
|
|
129
137
|
session = null;
|
|
130
138
|
recorded = null;
|
|
131
139
|
constructor(marker, name, cli) {
|
|
132
140
|
this.marker = marker;
|
|
133
|
-
this.
|
|
141
|
+
this.helpers = besideCli(cli);
|
|
134
142
|
const own = ownGroup();
|
|
135
143
|
this.group = own === null ? null : join(own, name);
|
|
136
144
|
try {
|
|
@@ -168,7 +176,7 @@ export class Scope {
|
|
|
168
176
|
return true;
|
|
169
177
|
if (this.recorded === null)
|
|
170
178
|
return found.size > 0;
|
|
171
|
-
return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || command.startsWith(
|
|
179
|
+
return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || (this.helpers !== null && command.startsWith(this.helpers))));
|
|
172
180
|
}
|
|
173
181
|
empty() {
|
|
174
182
|
return this.found()?.size === 0;
|
package/dist/update.js
CHANGED
|
@@ -1,181 +1,127 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
5
3
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
// A
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function unit(callId) {
|
|
26
|
-
return `house-kit-update-${callId}`;
|
|
27
|
-
}
|
|
28
|
-
function manager() {
|
|
29
|
-
return service() === 'user' ? ['--user'] : [];
|
|
30
|
-
}
|
|
31
|
-
function elevated() {
|
|
32
|
-
return throughSudo() ? ['sudo', '-n'] : [];
|
|
33
|
-
}
|
|
34
|
-
function account() {
|
|
35
|
-
return throughSudo() ? [`--uid=${process.getuid()}`, `--gid=${process.getgid()}`] : [];
|
|
36
|
-
}
|
|
37
|
-
function placement() {
|
|
38
|
-
return service() === 'user' ? ['--workspace', workspaceRoot()] : ['--linux'];
|
|
39
|
-
}
|
|
40
|
-
function record() {
|
|
41
|
-
return `outcome=$1; shift; timeout ${RERUN_MS / 1000} bash "$0" "$@"; printf %s "$?" > "$outcome"; ${[...elevated(), 'systemctl', ...manager(), 'start', 'house-kit.service'].join(' ')}`;
|
|
42
|
-
}
|
|
43
|
-
async function pendingCall() {
|
|
44
|
-
try {
|
|
45
|
-
return JSON.parse(await readFile(pending(), 'utf8')).input_id;
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function recorded() {
|
|
52
|
-
const status = await readFile(outcome(), 'utf8').catch(() => null);
|
|
53
|
-
if (status === null)
|
|
54
|
-
return null;
|
|
55
|
-
if (status.trim() === TIMED_OUT)
|
|
56
|
-
return { refused: `the Kit bootstrap did not finish within ${RERUN_MS / 1000} seconds` };
|
|
57
|
-
return status.trim() === '0' ? {} : { refused: `the Kit bootstrap exited with ${status.trim()}` };
|
|
58
|
-
}
|
|
59
|
-
async function forget(callId) {
|
|
60
|
-
if ((await pendingCall()) !== callId)
|
|
61
|
-
return;
|
|
62
|
-
await rm(pending(), { force: true });
|
|
63
|
-
await rm(outcome(), { force: true });
|
|
64
|
-
}
|
|
65
|
-
async function finished(callId) {
|
|
66
|
-
for (;;) {
|
|
67
|
-
const result = await recorded();
|
|
68
|
-
if (result !== null)
|
|
69
|
-
return result;
|
|
70
|
-
// systemctl answers at once, so ten seconds only bounds a stuck service manager.
|
|
71
|
-
if ((await run('systemctl', [...manager(), 'is-active', '--quiet', unit(callId)], 10_000)).status !== 0) {
|
|
72
|
-
return (await recorded()) ?? { refused: 'the Kit bootstrap did not finish' };
|
|
73
|
-
}
|
|
74
|
-
// The bootstrap runs for minutes, so checking each second sees its outcome within a second at little cost.
|
|
75
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
async function bounded(body, most) {
|
|
79
|
-
const chunks = [];
|
|
80
|
-
let bytes = 0;
|
|
81
|
-
for await (const chunk of body ?? []) {
|
|
82
|
-
bytes += chunk.length;
|
|
83
|
-
if (bytes > most)
|
|
84
|
-
return null;
|
|
85
|
-
chunks.push(chunk);
|
|
86
|
-
}
|
|
87
|
-
return Buffer.concat(chunks, bytes);
|
|
88
|
-
}
|
|
89
|
-
function rerun(script, callId, flags) {
|
|
4
|
+
import { downloadServed, servedRelease } from './building.js';
|
|
5
|
+
import { KIT_RELEASE } from './clis.js';
|
|
6
|
+
import { restart, updateLine } from './end.js';
|
|
7
|
+
import { kitHome, readEnrolment } from './home.js';
|
|
8
|
+
import { placedPrefix } from './placement.js';
|
|
9
|
+
import { childEnvironment, relayOrigin } from './relay.js';
|
|
10
|
+
const INSTALLER_BYTES = 1_000_000;
|
|
11
|
+
// A release's Node.js, Kit package and adapters are about a hundred megabytes, so an hour carries them at thirty kilobytes a second, and a placement still running then has hung.
|
|
12
|
+
const PLACEMENT_MS = 60 * 60_000;
|
|
13
|
+
// A refused Kit is offline until its placement succeeds, so it tries again within five minutes of its Building or this computer recovering, while a placement that fails only after its hundred-megabyte download runs at most twelve times an hour.
|
|
14
|
+
export const PLACEMENT_RETRY_MS = 5 * 60_000;
|
|
15
|
+
const REFUSED = 'kit_bootstrap_refused: ';
|
|
16
|
+
const OUTPUT_CHARACTERS = 4000;
|
|
17
|
+
function causeOf(error) {
|
|
18
|
+
if (error instanceof TypeError && error.cause instanceof Error)
|
|
19
|
+
return `the Building did not answer: ${error.cause.message}`;
|
|
20
|
+
return error instanceof Error ? error.message : String(error);
|
|
21
|
+
}
|
|
22
|
+
function placing(installer, house, prefix, signal) {
|
|
90
23
|
return new Promise((resolve) => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
'
|
|
94
|
-
...
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
24
|
+
const relay = relayOrigin();
|
|
25
|
+
const child = spawn('bash', [installer, '--house', house, '--place', prefix], {
|
|
26
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
27
|
+
env: { ...childEnvironment(), ...(relay === undefined ? {} : { HOUSE_KIT_RELAY: relay }) },
|
|
28
|
+
detached: true,
|
|
29
|
+
});
|
|
30
|
+
const stop = () => {
|
|
31
|
+
try {
|
|
32
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
};
|
|
36
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
37
|
+
if (signal.aborted)
|
|
38
|
+
stop();
|
|
39
|
+
let output = '';
|
|
40
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
41
|
+
stream.setEncoding('utf8').on('data', (chunk) => {
|
|
42
|
+
output = (output + chunk).slice(-OUTPUT_CHARACTERS);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const ended = (failure) => {
|
|
46
|
+
signal.removeEventListener('abort', stop);
|
|
47
|
+
resolve(failure);
|
|
48
|
+
};
|
|
49
|
+
child.on('error', (error) => ended(error.message));
|
|
50
|
+
child.on('close', (status) => {
|
|
51
|
+
if (status === 0)
|
|
52
|
+
return ended(null);
|
|
53
|
+
if (signal.aborted)
|
|
54
|
+
return ended(`the placement did not end within ${PLACEMENT_MS / 1000} seconds`);
|
|
55
|
+
const lines = output.split('\n').filter((line) => line.trim() !== '');
|
|
56
|
+
const refused = lines.findLast((line) => line.startsWith(REFUSED));
|
|
57
|
+
ended(refused?.slice(REFUSED.length) ?? lines.at(-1) ?? `the placement ended with status ${status ?? 'a signal'}`);
|
|
58
|
+
});
|
|
113
59
|
});
|
|
114
60
|
}
|
|
115
61
|
export class KitUpdate {
|
|
116
62
|
calls;
|
|
117
|
-
changed;
|
|
118
|
-
waiting = new Map();
|
|
119
63
|
queue = Promise.resolve();
|
|
120
|
-
|
|
64
|
+
placed = null;
|
|
65
|
+
constructor(calls) {
|
|
121
66
|
this.calls = calls;
|
|
122
|
-
this.changed = changed;
|
|
123
67
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (callId === null)
|
|
127
|
-
return;
|
|
128
|
-
await this.enqueue(callId, () => finished(callId));
|
|
68
|
+
take(call) {
|
|
69
|
+
void this.calls.carry(call.input_id, async () => ({ result: await this.queued(() => this.update(call.release)) }));
|
|
129
70
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
71
|
+
async takeRefused(release) {
|
|
72
|
+
for (;;) {
|
|
73
|
+
const failure = await this.queued(async () => (this.placed === null ? this.place(release, 'served') : null));
|
|
74
|
+
if (failure === null)
|
|
75
|
+
restart(`Kit ${this.placed} is placed; its supervisor starts it`);
|
|
76
|
+
process.stderr.write(`kit: ${updateLine(`Kit ${KIT_RELEASE} stays offline and tries again in ${PLACEMENT_RETRY_MS / 60_000} minutes`)}\n`);
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, PLACEMENT_RETRY_MS));
|
|
78
|
+
}
|
|
133
79
|
}
|
|
134
|
-
|
|
135
|
-
return this.
|
|
80
|
+
placedRelease() {
|
|
81
|
+
return this.placed;
|
|
136
82
|
}
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
this.
|
|
140
|
-
|
|
83
|
+
queued(work) {
|
|
84
|
+
const taken = this.queue.then(work);
|
|
85
|
+
this.queue = taken.catch(() => undefined);
|
|
86
|
+
return taken;
|
|
141
87
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
88
|
+
async update(release) {
|
|
89
|
+
if (release === (this.placed ?? KIT_RELEASE))
|
|
90
|
+
return {};
|
|
91
|
+
if (this.placed !== null)
|
|
92
|
+
return { refused: `Kit ${this.placed} is placed and starts first, once no turn is open` };
|
|
93
|
+
const failure = await this.place(release, 'named');
|
|
94
|
+
if (failure !== null)
|
|
95
|
+
return { refused: failure };
|
|
96
|
+
process.stderr.write(`kit: Kit ${release} is placed; it starts once no turn is open\n`);
|
|
97
|
+
return {};
|
|
150
98
|
}
|
|
151
|
-
async
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
let script;
|
|
99
|
+
async place(release, which) {
|
|
100
|
+
const signal = AbortSignal.timeout(PLACEMENT_MS);
|
|
101
|
+
let version = release;
|
|
102
|
+
let failure;
|
|
156
103
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
104
|
+
const prefix = placedPrefix();
|
|
105
|
+
const served = await servedRelease(signal);
|
|
106
|
+
if (which === 'named' && served.version !== release)
|
|
107
|
+
throw new Error(`the Building serves Kit ${served.version}, not Kit ${release}`);
|
|
108
|
+
version = served.version;
|
|
109
|
+
if (version === KIT_RELEASE)
|
|
110
|
+
throw new Error(`the Building serves Kit ${KIT_RELEASE}, which this Kit runs`);
|
|
111
|
+
await mkdir(join(kitHome(), 'update'), { recursive: true, mode: 0o700 });
|
|
112
|
+
const installer = join(kitHome(), 'update', served.installer);
|
|
113
|
+
await downloadServed(served.installer, served.sha256, installer, signal, INSTALLER_BYTES);
|
|
114
|
+
failure = await placing(installer, (await readEnrolment()).house, prefix, signal);
|
|
161
115
|
}
|
|
162
116
|
catch (error) {
|
|
163
|
-
|
|
164
|
-
throw error;
|
|
165
|
-
return { refused: `the Kit bootstrap did not arrive within ${BOOTSTRAP_MS / 1000} seconds` };
|
|
117
|
+
failure = signal.aborted ? `the placement did not end within ${PLACEMENT_MS / 1000} seconds` : causeOf(error);
|
|
166
118
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return
|
|
119
|
+
if (failure !== null) {
|
|
120
|
+
const refused = `Kit ${version} could not be placed: ${failure}`;
|
|
121
|
+
process.stderr.write(`kit: ${refused}\n`);
|
|
122
|
+
return refused;
|
|
171
123
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
await writeFile(path, script, { mode: 0o700 });
|
|
175
|
-
await rm(outcome(), { force: true });
|
|
176
|
-
await writeFile(pending(), `${JSON.stringify({ input_id: call.input_id })}\n`, { mode: 0o600 });
|
|
177
|
-
return (await rerun(path, call.input_id, call.update_clis ? ['--update-clis'] : [])) ?? (await recorded()) ?? {
|
|
178
|
-
refused: 'the Kit bootstrap did not finish',
|
|
179
|
-
};
|
|
124
|
+
this.placed = version;
|
|
125
|
+
return null;
|
|
180
126
|
}
|
|
181
127
|
}
|
package/dist/wire.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { newerThanHouse, updateRequired } from './end.js';
|
|
2
2
|
import { machine } from './placement.js';
|
|
3
3
|
import { refusalOf } from './refusals.js';
|
|
4
|
-
export const KIT_WIRE =
|
|
4
|
+
export const KIT_WIRE = 4;
|
|
5
5
|
export const KIT_WIRE_HEADER = 'x-house-kit-wire';
|
|
6
|
-
|
|
6
|
+
let take = null;
|
|
7
|
+
let taken = false;
|
|
8
|
+
function unservedBy(text) {
|
|
7
9
|
let body;
|
|
8
10
|
try {
|
|
9
11
|
body = JSON.parse(text);
|
|
@@ -11,12 +13,28 @@ function newer(text) {
|
|
|
11
13
|
catch {
|
|
12
14
|
return null;
|
|
13
15
|
}
|
|
14
|
-
|
|
16
|
+
const error = body?.error;
|
|
17
|
+
if (error?.code !== 'kit_update_required' && error?.code !== 'kit_newer_than_house')
|
|
18
|
+
return null;
|
|
19
|
+
return typeof error.release === 'string' ? error : null;
|
|
20
|
+
}
|
|
21
|
+
export function takeUnservedRelease(taking) {
|
|
22
|
+
take = taking;
|
|
23
|
+
}
|
|
24
|
+
export function unserved() {
|
|
25
|
+
return taken;
|
|
15
26
|
}
|
|
16
27
|
export function endUnserved(text) {
|
|
28
|
+
const refused = unservedBy(text);
|
|
29
|
+
if (refused !== null && take !== null) {
|
|
30
|
+
if (!taken) {
|
|
31
|
+
taken = true;
|
|
32
|
+
take(refused.release);
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
17
36
|
if (refusalOf(text)?.code === 'kit_update_required')
|
|
18
37
|
updateRequired();
|
|
19
|
-
|
|
20
|
-
if (refused !== null)
|
|
38
|
+
if (refused?.code === 'kit_newer_than_house')
|
|
21
39
|
newerThanHouse(refused.release, refused.commands[machine()]);
|
|
22
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-cf268dff1869b",
|
|
4
4
|
"description": "Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@agentclientprotocol/sdk": "2.0.1",
|
|
28
28
|
"@agentshouse/mdmodel": "0.1.0-c4f4d37bc9d6a",
|
|
29
|
-
"@agentshouse/skills": "0.1.0-
|
|
29
|
+
"@agentshouse/skills": "0.1.0-cdd0f15bef719",
|
|
30
30
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
31
31
|
"yaml": "2.9.1"
|
|
32
32
|
},
|
package/dist/clis-main.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { post } from './api.js';
|
|
2
|
-
import { CLIS, below, locate, versionOf } from './clis.js';
|
|
3
|
-
import { readEnrolment } from './home.js';
|
|
4
|
-
import { proxyUnlessRelayed } from './relay.js';
|
|
5
|
-
proxyUnlessRelayed();
|
|
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
|
-
}
|