@agentshouse/kit 0.2.1-c5d51f1e4ce19 → 0.2.1-c64c31420c9bf
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 +9 -9
- package/dist/agents.js +2 -1
- package/dist/api.js +1 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +2 -5
- package/dist/chatgpt.js +1 -1
- package/dist/conversations.js +33 -10
- package/dist/copies.js +16 -8
- package/dist/end.js +3 -2
- package/dist/git.js +2 -3
- package/dist/home.js +27 -13
- package/dist/house-cli.js +3 -3
- package/dist/login.js +14 -5
- package/dist/logout.js +0 -2
- package/dist/placement.js +2 -20
- package/dist/resident.js +51 -10
- package/dist/stream.js +1 -1
- package/dist/work.js +3 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,18 +5,18 @@ your Agents' conversations through the agent CLIs you chose and gives each Agent
|
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
|
-
Run `curl -fsSL
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
your login shell's `PATH`, and your workspace is `~/AgentsHouse` unless you
|
|
8
|
+
Run `sh -c 'kit=$(curl -fsSL "$0") || { echo "House Kit could not be downloaded; run this command again." >&2; exit 1; }; eval "$kit"' agents.house/kit`,
|
|
9
|
+
or `& { try { $kit = irm agents.house/kit.ps1 } catch { throw 'House 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
|
+
On macOS and Linux it installs House Kit natively under your own account, without an admin password, as a service that runs
|
|
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
12
|
pass `--workspace <path>`.
|
|
13
13
|
|
|
14
14
|
Add `--container` to run House Kit in one Docker container instead, with your
|
|
15
15
|
workspace mounted at `/agents/house`; your Agents then reach only that
|
|
16
|
-
workspace. On Windows 11 the container is the one choice. Append a `kit` or
|
|
17
|
-
`house` command to the container's options to run it in that container.
|
|
16
|
+
workspace. On Windows 11 the container is the one choice. Append a `kit` or `house` command to the container's options to run it in that container.
|
|
18
17
|
Running the command for the other choice on a connected computer asks you to
|
|
19
|
-
confirm, then replaces its Environment, whose Agents end, with a new one.
|
|
18
|
+
confirm, then replaces its Environment, whose Agents end, with a new one. Running it with another `HOUSE_KIT_HOME`
|
|
19
|
+
than the installed Kit's asks you to confirm before it replaces that Kit, and leaves that Kit home as it was.
|
|
20
20
|
|
|
21
21
|
On your own Linux server, run the Linux command with `--linux` to
|
|
22
22
|
install House Kit natively, with its workspace at `/agents/house`; `kit` and
|
|
@@ -37,8 +37,8 @@ or another, continues on its own and keeps the Environment's credential in
|
|
|
37
37
|
reconnects an Environment you already have; `--rooms <ref>[,<ref>...]` gives it
|
|
38
38
|
only those Rooms and `--no-private` every Room but your Private Room; `--code <code>` connects immediately without a link.
|
|
39
39
|
A login also connects your own agent: outside an Agent conversation, `house <tool>` calls
|
|
40
|
-
House as you and `git` clones and pushes your Apps at `<house>/app/` as you,
|
|
41
|
-
that connection on Connections. `house help` lists the Tools. Codex sessions started after a
|
|
40
|
+
House as you and `git` clones and pushes your Apps at `<house>/app/` as you, each through the Kit
|
|
41
|
+
home `$HOUSE_KIT_HOME` names where it runs, until you revoke that connection on Connections. `house help` lists the Tools. Codex sessions started after a
|
|
42
42
|
login run a lone `house` command outside their sandbox. `kit logout` disconnects this
|
|
43
43
|
computer, stops House Kit and ends your own agent's connection, so `house` and `git` no
|
|
44
44
|
longer act as you here; a later `kit login` reconnects the same Environment, or connects
|
package/dist/agents.js
CHANGED
|
@@ -125,6 +125,7 @@ function reported(kind, reading) {
|
|
|
125
125
|
full_access: CLIS[kind].fullAccess,
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
const NOT_READY = "the Agent's CLI is not ready on this computer";
|
|
128
129
|
function tooOld(kind, release) {
|
|
129
130
|
const cli = CLIS[kind];
|
|
130
131
|
return `${cli.bin} ${release} is older than ${cli.minimum}, the oldest this Kit runs`;
|
|
@@ -213,7 +214,7 @@ export class Agents {
|
|
|
213
214
|
const resolved = await this.resolve(kind);
|
|
214
215
|
if (!('cli' in resolved)) {
|
|
215
216
|
this.update(kind, resolved);
|
|
216
|
-
throw new Error(resolved.failure
|
|
217
|
+
throw new Error(resolved.failure === null ? tooOld(kind, resolved.release) : NOT_READY);
|
|
217
218
|
}
|
|
218
219
|
const { cli, release } = resolved;
|
|
219
220
|
const known = this.readings.get(kind);
|
package/dist/api.js
CHANGED
|
@@ -46,7 +46,7 @@ export async function post(house, path, body, credential, signal) {
|
|
|
46
46
|
});
|
|
47
47
|
const text = await answer.text();
|
|
48
48
|
if (!answer.ok && tooOld(text))
|
|
49
|
-
updateRequired(
|
|
49
|
+
updateRequired();
|
|
50
50
|
if (!answer.ok)
|
|
51
51
|
throw new HouseRefusal(path, answer.status, text);
|
|
52
52
|
return JSON.parse(text);
|
package/dist/app-git.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { extname, resolve } from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
1
|
import { run } from './git.js';
|
|
4
2
|
import { kitHome, readOwnAgent } from './home.js';
|
|
5
|
-
|
|
3
|
+
const HELPER = '!kit git-credential';
|
|
6
4
|
function section(house) {
|
|
7
5
|
return `credential.${new URL('/app/', house).href}`;
|
|
8
6
|
}
|
|
9
|
-
function helper() {
|
|
10
|
-
const main = fileURLToPath(new URL(`./kit-main${extname(fileURLToPath(import.meta.url))}`, import.meta.url));
|
|
11
|
-
return `!HOUSE_KIT_HOME=${quoted(resolve(kitHome()))} ${quoted(process.execPath)} ${quoted(main)} git-credential`;
|
|
12
|
-
}
|
|
13
7
|
async function configured(args) {
|
|
14
8
|
try {
|
|
15
9
|
return await run(kitHome(), ['config', '--global', ...args]);
|
|
@@ -20,30 +14,18 @@ async function configured(args) {
|
|
|
20
14
|
throw error;
|
|
21
15
|
}
|
|
22
16
|
}
|
|
23
|
-
async function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const ran = await configured(['--add', `${section(house)}.helper`, value]);
|
|
17
|
+
export async function placeAppGit(ownAgent) {
|
|
18
|
+
if (ownAgent === null)
|
|
19
|
+
return;
|
|
20
|
+
await configured(['--remove-section', section(ownAgent.house)]);
|
|
21
|
+
for (const value of ['', HELPER]) {
|
|
22
|
+
const ran = await configured(['--add', `${section(ownAgent.house)}.helper`, value]);
|
|
30
23
|
if (ran === null)
|
|
31
24
|
return;
|
|
32
25
|
if (ran.status !== 0)
|
|
33
26
|
throw new Error(`git config failed: ${ran.stderr.trim()}`);
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
|
-
export async function placeAppGit(before, after) {
|
|
37
|
-
if (before !== null && before.house !== after?.house)
|
|
38
|
-
await forget(before.house);
|
|
39
|
-
if (after !== null)
|
|
40
|
-
await register(after.house);
|
|
41
|
-
}
|
|
42
|
-
export async function forgetAppGit() {
|
|
43
|
-
const ownAgent = await readOwnAgent();
|
|
44
|
-
if (ownAgent !== null)
|
|
45
|
-
await forget(ownAgent.house);
|
|
46
|
-
}
|
|
47
29
|
export async function answerGitCredential(operation) {
|
|
48
30
|
let asked = '';
|
|
49
31
|
for await (const chunk of process.stdin)
|
|
@@ -55,8 +37,10 @@ export async function answerGitCredential(operation) {
|
|
|
55
37
|
.filter((line) => line.includes('='))
|
|
56
38
|
.map((line) => [line.slice(0, line.indexOf('=')), line.slice(line.indexOf('=') + 1)]));
|
|
57
39
|
const ownAgent = await readOwnAgent();
|
|
58
|
-
if (ownAgent === null)
|
|
40
|
+
if (ownAgent === null) {
|
|
41
|
+
process.stderr.write(`kit: Kit home ${kitHome()} is not signed in; run kit login\n`);
|
|
59
42
|
return;
|
|
43
|
+
}
|
|
60
44
|
const house = new URL(ownAgent.house);
|
|
61
45
|
if (attributes.get('protocol') !== house.protocol.slice(0, -1) || attributes.get('host') !== house.host)
|
|
62
46
|
return;
|
package/dist/bootstrap.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-c64c31420c9bf",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"macos": {
|
|
6
6
|
"asset": {
|
|
7
7
|
"name": "connect-linux.sh",
|
|
8
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
8
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c64c31420c9bf/connect-linux.sh"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"linux": {
|
|
12
12
|
"asset": {
|
|
13
13
|
"name": "connect-linux.sh",
|
|
14
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
14
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c64c31420c9bf/connect-linux.sh"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"windows": {
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c64c31420c9bf/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/bridge.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
-
import { mkdir } from 'node:fs/promises';
|
|
5
4
|
import { createServer, request as httpRequest } from 'node:http';
|
|
6
5
|
import { request as httpsRequest } from 'node:https';
|
|
7
6
|
import { delimiter, dirname, join } from 'node:path';
|
|
@@ -9,7 +8,7 @@ import { parse } from 'yaml';
|
|
|
9
8
|
import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
|
|
10
9
|
import { loginPath } from './clis.js';
|
|
11
10
|
import { changedPaths, } from './copies.js';
|
|
12
|
-
import { kitHome, readEnrolment } from './home.js';
|
|
11
|
+
import { kitHome, readEnrolment, socketDirectory } from './home.js';
|
|
13
12
|
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
14
13
|
import { operationId } from './operation.js';
|
|
15
14
|
import { refusalOf, retryable } from './refusals.js';
|
|
@@ -167,9 +166,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
167
166
|
}
|
|
168
167
|
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
169
168
|
};
|
|
170
|
-
const
|
|
171
|
-
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
172
|
-
const socketPath = join(sockets, `${randomUUID()}.sock`);
|
|
169
|
+
const socketPath = join(socketDirectory(), `${randomUUID()}.sock`);
|
|
173
170
|
const bridge = createServer(async (request, response) => {
|
|
174
171
|
try {
|
|
175
172
|
const path = request.url ?? '';
|
package/dist/chatgpt.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function chatgpt(argv) {
|
|
|
24
24
|
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
25
25
|
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
26
26
|
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
27
|
-
await placeAppGit(
|
|
27
|
+
await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
28
28
|
if (before !== null && before.credential !== values.key) {
|
|
29
29
|
process.stdout.write('This replaced the key this Kit home held; give each session its own HOUSE_KIT_HOME to keep its own key.\n');
|
|
30
30
|
}
|
package/dist/conversations.js
CHANGED
|
@@ -18,6 +18,21 @@ import { transcribe } from './transcription.js';
|
|
|
18
18
|
import { workFor } from './work.js';
|
|
19
19
|
const KILLED = 'the conversation was killed';
|
|
20
20
|
const REFUSED = 'the CLI refused the prompt';
|
|
21
|
+
const NO_CONNECTION = "the Kit could not open this conversation's connection";
|
|
22
|
+
const NO_START = "the Agent's CLI did not start";
|
|
23
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
24
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
25
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
26
|
+
class NotStarted extends Error {
|
|
27
|
+
}
|
|
28
|
+
async function step(reason, work) {
|
|
29
|
+
try {
|
|
30
|
+
return await work;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new NotStarted(reason, { cause: error });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
21
36
|
const EXIT_PLAN_MODE = {
|
|
22
37
|
parse(value) {
|
|
23
38
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -36,6 +51,13 @@ function answerMessage(request, response) {
|
|
|
36
51
|
function logged(error) {
|
|
37
52
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
38
53
|
}
|
|
54
|
+
function refusal(conversation, error) {
|
|
55
|
+
if (conversation.kills > 0)
|
|
56
|
+
return KILLED;
|
|
57
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
58
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
59
|
+
return causeOf(error);
|
|
60
|
+
}
|
|
39
61
|
function marker(bridge) {
|
|
40
62
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
41
63
|
}
|
|
@@ -212,7 +234,7 @@ export class Conversations {
|
|
|
212
234
|
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null) };
|
|
213
235
|
}
|
|
214
236
|
catch (error) {
|
|
215
|
-
return { refused:
|
|
237
|
+
return { refused: refusal(conversation, error) };
|
|
216
238
|
}
|
|
217
239
|
}
|
|
218
240
|
async message(conversation, input) {
|
|
@@ -229,7 +251,7 @@ export class Conversations {
|
|
|
229
251
|
throw new Error(KILLED);
|
|
230
252
|
}
|
|
231
253
|
catch (error) {
|
|
232
|
-
return { ack: Promise.resolve({ refused: conversation
|
|
254
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
233
255
|
}
|
|
234
256
|
return {
|
|
235
257
|
ack: new Promise((settle) => {
|
|
@@ -274,7 +296,7 @@ export class Conversations {
|
|
|
274
296
|
conversation.session = null;
|
|
275
297
|
await this.kill(conversation);
|
|
276
298
|
}
|
|
277
|
-
waiting.settle({ refused: conversation
|
|
299
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
278
300
|
return;
|
|
279
301
|
}
|
|
280
302
|
this.prompt(conversation, [...prompt, ...waiting.content]);
|
|
@@ -360,7 +382,7 @@ export class Conversations {
|
|
|
360
382
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
361
383
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
362
384
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
363
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
385
|
+
const bridge = await step(NO_CONNECTION, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
364
386
|
if (conversation.kills > 0) {
|
|
365
387
|
bridge.close();
|
|
366
388
|
throw new Error(KILLED);
|
|
@@ -371,24 +393,25 @@ export class Conversations {
|
|
|
371
393
|
try {
|
|
372
394
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
373
395
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
374
|
-
const
|
|
396
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
397
|
+
const adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
375
398
|
work.read(message);
|
|
376
399
|
this.read(conversation, reader, message);
|
|
377
|
-
}, { ...bridge.env, ...
|
|
400
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
378
401
|
void adapter.exited.then(() => bridge.close());
|
|
379
402
|
const agent = adapter.connection.agent;
|
|
380
403
|
const cwd = route.working_directory;
|
|
381
404
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
382
405
|
const opened = sessionId === null
|
|
383
|
-
? await agent.request('session/new', { cwd, mcpServers: [], _meta })
|
|
384
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
406
|
+
? await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta }))
|
|
407
|
+
: { ...(await step(NO_RESUME, agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta }))), sessionId };
|
|
385
408
|
reader.session = opened.sessionId;
|
|
386
409
|
if (sessionId === null && CLIS[route.kind].renames) {
|
|
387
410
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
388
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
411
|
+
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
389
412
|
}
|
|
390
413
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
391
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
414
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
392
415
|
void work.settled().then(() => scope.record());
|
|
393
416
|
conversation.running = running;
|
|
394
417
|
conversation.session = running.sessionId;
|
package/dist/copies.js
CHANGED
|
@@ -15,6 +15,9 @@ const IDENTITY = { GIT_COMMITTER_NAME: 'House Kit', GIT_COMMITTER_EMAIL: 'house-
|
|
|
15
15
|
function causeOf(error) {
|
|
16
16
|
return error instanceof Error ? error.message : String(error);
|
|
17
17
|
}
|
|
18
|
+
function houseCause(error) {
|
|
19
|
+
return error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
20
|
+
}
|
|
18
21
|
function logged(error) {
|
|
19
22
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
20
23
|
}
|
|
@@ -261,7 +264,7 @@ export class LocalCopies {
|
|
|
261
264
|
}
|
|
262
265
|
catch (error) {
|
|
263
266
|
refused = true;
|
|
264
|
-
said.push(
|
|
267
|
+
said.push(houseCause(error));
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
270
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
@@ -549,7 +552,7 @@ export class LocalCopies {
|
|
|
549
552
|
changes = await this.derived(copy, base, commit);
|
|
550
553
|
}
|
|
551
554
|
catch (error) {
|
|
552
|
-
return { refused: true, text:
|
|
555
|
+
return { refused: true, text: houseCause(error) };
|
|
553
556
|
}
|
|
554
557
|
if (changes.length === 0)
|
|
555
558
|
return { refused: false, text: `Nothing to submit: ${short(commit)} matches House.` };
|
|
@@ -576,12 +579,17 @@ export class LocalCopies {
|
|
|
576
579
|
const after = await tree(copy.repository, commit);
|
|
577
580
|
const moved = movedPaths(copy.moves, before, after);
|
|
578
581
|
const arrived = new Set(moved.values());
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
582
|
+
const arriving = [...after].filter(([path, entry]) => before.get(path) !== entry);
|
|
583
|
+
if (arriving.length > 0) {
|
|
584
|
+
const { bounds } = await this.house.post('/kit/door/discover', { prefixes: [root] });
|
|
585
|
+
const binary = [];
|
|
586
|
+
for (const [path, entry] of arriving) {
|
|
587
|
+
if (!(await isText(copy.repository, entry, bounds.max_authored_file_bytes)))
|
|
588
|
+
binary.push(path);
|
|
589
|
+
}
|
|
590
|
+
if (binary.length > 0)
|
|
591
|
+
throw new Error(textOnly(binary));
|
|
592
|
+
}
|
|
585
593
|
const text = (entry) => blobText(copy.repository, entry);
|
|
586
594
|
const changes = [];
|
|
587
595
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
package/dist/end.js
CHANGED
|
@@ -13,8 +13,9 @@ export function end(reason) {
|
|
|
13
13
|
export function restart(reason) {
|
|
14
14
|
exit(reason, 1);
|
|
15
15
|
}
|
|
16
|
-
export function updateRequired(
|
|
17
|
-
|
|
16
|
+
export function updateRequired() {
|
|
17
|
+
const command = installCommand();
|
|
18
|
+
end(command === null ? 'This Kit is too old for House; install it again.' : `This Kit is too old for House. Update it: ${command}`);
|
|
18
19
|
}
|
|
19
20
|
export async function signInAgain(rejected) {
|
|
20
21
|
await forgetRejected(rejected);
|
package/dist/git.js
CHANGED
|
@@ -49,16 +49,15 @@ export async function tree(repository, commit) {
|
|
|
49
49
|
}
|
|
50
50
|
return entries;
|
|
51
51
|
}
|
|
52
|
-
const TEXT_MAX_BYTES = 16 * 1024 * 1024;
|
|
53
52
|
async function blob(repository, object) {
|
|
54
53
|
return (await run(repository, ['cat-file', 'blob', object])).stdout;
|
|
55
54
|
}
|
|
56
55
|
export async function blobText(repository, object) {
|
|
57
56
|
return (await blob(repository, object)).toString('utf8');
|
|
58
57
|
}
|
|
59
|
-
export async function isText(repository, object) {
|
|
58
|
+
export async function isText(repository, object, maxBytes) {
|
|
60
59
|
const bytes = await blob(repository, object);
|
|
61
|
-
return bytes.length <=
|
|
60
|
+
return bytes.length <= maxBytes && isUtf8(bytes) && !bytes.includes(0);
|
|
62
61
|
}
|
|
63
62
|
export function blobId(content) {
|
|
64
63
|
const bytes = Buffer.from(content, 'utf8');
|
package/dist/home.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { accessSync, chmodSync, constants, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { accessSync, chmodSync, constants, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync } from 'node:fs';
|
|
3
3
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir, tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
6
|
import { selectRelay } from './relay.js';
|
|
7
7
|
const ENROLMENT = 'credential.json';
|
|
8
8
|
const DISCONNECTED = 'disconnected.json';
|
|
@@ -19,24 +19,26 @@ function writable(directory) {
|
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
function
|
|
23
|
-
const home = join(tmpdir(), `house-kit-${process.getuid()}`);
|
|
22
|
+
function privateDirectory(path) {
|
|
24
23
|
try {
|
|
25
|
-
mkdirSync(
|
|
24
|
+
mkdirSync(path, { mode: 0o700 });
|
|
26
25
|
}
|
|
27
26
|
catch (error) {
|
|
28
27
|
if (error.code !== 'EEXIST')
|
|
29
28
|
throw error;
|
|
30
29
|
}
|
|
31
|
-
const found = lstatSync(
|
|
30
|
+
const found = lstatSync(path);
|
|
32
31
|
if (found.isSymbolicLink())
|
|
33
|
-
throw new Error(`${
|
|
32
|
+
throw new Error(`${path} is a symbolic link`);
|
|
34
33
|
if (!found.isDirectory())
|
|
35
|
-
throw new Error(`${
|
|
36
|
-
if (found.uid !== process.
|
|
37
|
-
throw new Error(`${
|
|
38
|
-
chmodSync(
|
|
39
|
-
return
|
|
34
|
+
throw new Error(`${path} is not a directory`);
|
|
35
|
+
if (found.uid !== process.geteuid())
|
|
36
|
+
throw new Error(`${path} is not owned by this User`);
|
|
37
|
+
chmodSync(path, 0o700);
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
function ownTemporaryHome() {
|
|
41
|
+
return privateDirectory(join(tmpdir(), `house-kit-${process.getuid()}`));
|
|
40
42
|
}
|
|
41
43
|
function defaultHome() {
|
|
42
44
|
const home = join(homedir(), '.house-kit');
|
|
@@ -45,6 +47,18 @@ function defaultHome() {
|
|
|
45
47
|
export function kitHome() {
|
|
46
48
|
return process.env.HOUSE_KIT_HOME || (defaulted ??= defaultHome());
|
|
47
49
|
}
|
|
50
|
+
export function socketDirectory() {
|
|
51
|
+
const sockets = privateDirectory(join(realpathSync('/tmp'), `hk-${process.geteuid()}`));
|
|
52
|
+
const home = kitHome();
|
|
53
|
+
const physical = existsSync(home) ? realpathSync(home) : resolve(home);
|
|
54
|
+
return privateDirectory(join(sockets, createHash('sha256').update(physical).digest('hex').slice(0, 12)));
|
|
55
|
+
}
|
|
56
|
+
export function ownerSocket() {
|
|
57
|
+
return join(socketDirectory(), 'owner.sock');
|
|
58
|
+
}
|
|
59
|
+
export function ownerLock() {
|
|
60
|
+
return join(socketDirectory(), 'owner.lock');
|
|
61
|
+
}
|
|
48
62
|
export function workspaceRoot() {
|
|
49
63
|
return process.env.HOUSE_KIT_WORKSPACE ?? '/agents/house';
|
|
50
64
|
}
|
package/dist/house-cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
|
4
4
|
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
5
5
|
import { verified } from './files.js';
|
|
6
6
|
import { HOUSE_REF } from './git.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ownerSocket, readOwnAgent } from './home.js';
|
|
8
8
|
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
9
9
|
import { refusalOf } from './refusals.js';
|
|
10
10
|
import { relayed, selectRelay } from './relay.js';
|
|
@@ -220,7 +220,7 @@ async function push(args) {
|
|
|
220
220
|
throw new Error('--owner is not for Agent conversations; run house push without it');
|
|
221
221
|
if (!owner && socketPath === undefined)
|
|
222
222
|
throw new Error('outside an Agent conversation, house push needs --owner');
|
|
223
|
-
const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit }, undefined, owner ?
|
|
223
|
+
const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit }, undefined, owner ? ownerSocket() : socketPath));
|
|
224
224
|
if (pushed.refused)
|
|
225
225
|
throw new Error(pushed.text);
|
|
226
226
|
return pushed.text;
|
|
@@ -228,7 +228,7 @@ async function push(args) {
|
|
|
228
228
|
async function fetched(args) {
|
|
229
229
|
if (accepted(args, ['--help'], 0, FETCH_USAGE).flags.has('--help'))
|
|
230
230
|
return FETCH_USAGE;
|
|
231
|
-
const answer = JSON.parse(await bridged('/git/fetch', {}, undefined, socketPath ??
|
|
231
|
+
const answer = JSON.parse(await bridged('/git/fetch', {}, undefined, socketPath ?? ownerSocket()));
|
|
232
232
|
if (answer.refused)
|
|
233
233
|
throw new Error(answer.text);
|
|
234
234
|
return answer.text;
|
package/dist/login.js
CHANGED
|
@@ -36,6 +36,13 @@ const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms
|
|
|
36
36
|
--no-private give this Environment every Room but your Private Room
|
|
37
37
|
--code <code> connect this machine with a login code
|
|
38
38
|
`;
|
|
39
|
+
function connected(house, named, environment) {
|
|
40
|
+
if (named === undefined)
|
|
41
|
+
return `Environment ${environment} is connected to ${house}.\n`;
|
|
42
|
+
if (named === environment)
|
|
43
|
+
return `Environment ${environment} is reconnected to ${house}.\n`;
|
|
44
|
+
return `Environment ${named} stays with its account; this computer is connected to ${house} as new Environment ${environment} of the account that confirmed.\n`;
|
|
45
|
+
}
|
|
39
46
|
function gone(error) {
|
|
40
47
|
return error instanceof HouseRefusal && refusalOf(error.text)?.code === 'kit_environment_not_found';
|
|
41
48
|
}
|
|
@@ -81,6 +88,7 @@ export async function login(argv) {
|
|
|
81
88
|
const environment = replaces === undefined ? (values.environment ?? remembered) : undefined;
|
|
82
89
|
const ownAgent = await readOwnAgent();
|
|
83
90
|
let held = false;
|
|
91
|
+
let named = environment;
|
|
84
92
|
let exchanged;
|
|
85
93
|
if (values.code !== undefined) {
|
|
86
94
|
exchanged = await post(house, '/kit/token', { code: values.code, label: hostname() });
|
|
@@ -88,7 +96,7 @@ export async function login(argv) {
|
|
|
88
96
|
else {
|
|
89
97
|
held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
|
|
90
98
|
const verifier = randomBytes(32).toString('base64url');
|
|
91
|
-
const start = (
|
|
99
|
+
const start = () => retrying(() => post(house, '/kit', {
|
|
92
100
|
label: hostname(),
|
|
93
101
|
...(named === undefined
|
|
94
102
|
? { act: 'new_environment', ...(replaces === undefined ? {} : { replaces }) }
|
|
@@ -99,14 +107,15 @@ export async function login(argv) {
|
|
|
99
107
|
}));
|
|
100
108
|
let started;
|
|
101
109
|
try {
|
|
102
|
-
started = await start(
|
|
110
|
+
started = await start();
|
|
103
111
|
}
|
|
104
112
|
catch (error) {
|
|
105
113
|
if (remembered === undefined || !gone(error))
|
|
106
114
|
throw error;
|
|
107
115
|
await forgetDisconnected();
|
|
108
116
|
process.stdout.write(`House no longer holds Environment ${remembered}; connecting this computer as a new one.\n`);
|
|
109
|
-
|
|
117
|
+
named = undefined;
|
|
118
|
+
started = await start();
|
|
110
119
|
}
|
|
111
120
|
process.stdout.write(`Open this link and confirm: ${started.start_url}\n`);
|
|
112
121
|
open(started.start_url);
|
|
@@ -121,7 +130,7 @@ export async function login(argv) {
|
|
|
121
130
|
environment: exchanged.environment,
|
|
122
131
|
credential: exchanged.credential,
|
|
123
132
|
});
|
|
124
|
-
process.stdout.write(
|
|
133
|
+
process.stdout.write(connected(house, named, exchanged.environment));
|
|
125
134
|
if (exchanged.headless_credential !== undefined) {
|
|
126
135
|
await writeOwnAgent({ house, ...relayRecord, user: exchanged.user, credential: exchanged.headless_credential });
|
|
127
136
|
}
|
|
@@ -132,7 +141,7 @@ export async function login(argv) {
|
|
|
132
141
|
else if (held && ownAgent !== null) {
|
|
133
142
|
await writeOwnAgent({ house, ...relayRecord, user: ownAgent.user, credential: ownAgent.credential });
|
|
134
143
|
}
|
|
135
|
-
await placeAppGit(
|
|
144
|
+
await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
136
145
|
try {
|
|
137
146
|
startService();
|
|
138
147
|
}
|
package/dist/logout.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { HouseRefusal, post } from './api.js';
|
|
2
|
-
import { forgetAppGit } from './app-git.js';
|
|
3
2
|
import { forgetEnrolment, forgetOwnAgent, readEnrolment } from './home.js';
|
|
4
3
|
export async function logout() {
|
|
5
4
|
const enrolled = await readEnrolment();
|
|
@@ -13,7 +12,6 @@ export async function logout() {
|
|
|
13
12
|
}
|
|
14
13
|
await forgetEnrolment(enrolled);
|
|
15
14
|
}
|
|
16
|
-
await forgetAppGit();
|
|
17
15
|
await forgetOwnAgent();
|
|
18
16
|
process.stdout.write(enrolled === null
|
|
19
17
|
? 'This computer is not connected to House.\n'
|
package/dist/placement.js
CHANGED
|
@@ -2,7 +2,6 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { run } from './clis.js';
|
|
4
4
|
import { kitHome } from './home.js';
|
|
5
|
-
import { relayOrigin } from './relay.js';
|
|
6
5
|
export function service() {
|
|
7
6
|
if (process.platform !== 'linux' || !process.env.INVOCATION_ID)
|
|
8
7
|
return null;
|
|
@@ -19,9 +18,9 @@ export async function updatesItself() {
|
|
|
19
18
|
// sudo answers at once, so ten seconds only bounds a stuck sudo.
|
|
20
19
|
return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
|
|
21
20
|
}
|
|
22
|
-
function
|
|
21
|
+
export function installCommand() {
|
|
23
22
|
try {
|
|
24
|
-
return readFileSync(join(kitHome(), '
|
|
23
|
+
return readFileSync(join(kitHome(), 'install-command'), 'utf8').trim();
|
|
25
24
|
}
|
|
26
25
|
catch (error) {
|
|
27
26
|
if (error.code === 'ENOENT')
|
|
@@ -29,20 +28,3 @@ function installed() {
|
|
|
29
28
|
throw error;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
function entries(house) {
|
|
33
|
-
const host = new URL(house).host;
|
|
34
|
-
const relay = relayOrigin();
|
|
35
|
-
if (relay === undefined)
|
|
36
|
-
return [`curl -fsSL ${host}/kit | sh`, `irm ${host}/kit.ps1 | iex`];
|
|
37
|
-
const entry = `${relay}/${host}/kit`;
|
|
38
|
-
return [`curl -fsSL ${entry} | HOUSE_KIT_RELAY=${relay} sh`, `$env:HOUSE_KIT_RELAY='${relay}'; irm ${entry}.ps1 | iex`];
|
|
39
|
-
}
|
|
40
|
-
export function installCommand(house) {
|
|
41
|
-
const [shell, windows] = entries(house);
|
|
42
|
-
const placement = installed();
|
|
43
|
-
if (placement === 'native')
|
|
44
|
-
return shell;
|
|
45
|
-
if (placement === 'server')
|
|
46
|
-
return `${shell} -s -- --linux`;
|
|
47
|
-
return `${shell} -s -- --container, or ${windows} on Windows`;
|
|
48
|
-
}
|
package/dist/resident.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { once } from 'node:events';
|
|
2
2
|
import { watch } from 'node:fs';
|
|
3
3
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { createServer } from 'node:http';
|
|
4
|
+
import { createServer, get } from 'node:http';
|
|
5
5
|
import { dirname, extname, join } from 'node:path';
|
|
6
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import { Agents } from './agents.js';
|
|
8
9
|
import { house } from './api.js';
|
|
@@ -10,7 +11,7 @@ import { received } from './bridge.js';
|
|
|
10
11
|
import { CONVERSATION_INPUTS, Conversations } from './conversations.js';
|
|
11
12
|
import { LocalCopies } from './copies.js';
|
|
12
13
|
import { restart } from './end.js';
|
|
13
|
-
import { agentBase, enrolled, forgetRestart, kitHome, restartAsked } from './home.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
14
15
|
import { killDescendants } from './scope.js';
|
|
15
16
|
import { quoted } from './shell.js';
|
|
16
17
|
import { SignIns } from './sign-in.js';
|
|
@@ -29,13 +30,32 @@ async function installShim(path, module) {
|
|
|
29
30
|
await mkdir(dirname(path), { recursive: true });
|
|
30
31
|
await writeFile(path, `#!/bin/sh\nexec ${quoted(process.execPath)} ${quoted(main)} "$@"\n`, { mode: 0o755 });
|
|
31
32
|
}
|
|
33
|
+
function claimed(lock) {
|
|
34
|
+
try {
|
|
35
|
+
lock.exec('BEGIN EXCLUSIVE');
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.errcode === 5)
|
|
40
|
+
return false;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
32
44
|
async function serveOwner(copies) {
|
|
33
|
-
const
|
|
34
|
-
|
|
45
|
+
const lock = new DatabaseSync(ownerLock());
|
|
46
|
+
if (!claimed(lock)) {
|
|
47
|
+
lock.close();
|
|
48
|
+
const running = await runningResident();
|
|
49
|
+
throw new Error(`${running === null ? 'another Kit' : `Kit ${running}`} already runs this Environment from Kit home ${kitHome()}; stop it first to run another here`);
|
|
50
|
+
}
|
|
35
51
|
const server = createServer(async (request, response) => {
|
|
36
52
|
try {
|
|
37
53
|
const text = await received(request);
|
|
38
54
|
let answered;
|
|
55
|
+
if (request.url === '/resident') {
|
|
56
|
+
response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ pid: process.pid }));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
39
59
|
if (request.url === '/git/fetch') {
|
|
40
60
|
answered = await copies.fetch();
|
|
41
61
|
}
|
|
@@ -53,9 +73,23 @@ async function serveOwner(copies) {
|
|
|
53
73
|
response.writeHead(502).end(error instanceof Error ? error.message : String(error));
|
|
54
74
|
}
|
|
55
75
|
});
|
|
56
|
-
server.
|
|
76
|
+
server.on('close', () => lock.close());
|
|
77
|
+
await rm(ownerSocket(), { force: true });
|
|
78
|
+
server.listen(ownerSocket());
|
|
57
79
|
await once(server, 'listening');
|
|
58
80
|
}
|
|
81
|
+
function runningResident() {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
get({ socketPath: ownerSocket(), path: '/resident' }, (answer) => {
|
|
84
|
+
received(answer).then((text) => resolve(JSON.parse(text).pid), reject);
|
|
85
|
+
}).on('error', (error) => {
|
|
86
|
+
if (error.code === 'ENOENT' || error.code === 'ECONNREFUSED')
|
|
87
|
+
resolve(null);
|
|
88
|
+
else
|
|
89
|
+
reject(error);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
59
93
|
function connected() {
|
|
60
94
|
return new Promise((resolve) => {
|
|
61
95
|
const watcher = watch(kitHome(), () => {
|
|
@@ -72,7 +106,7 @@ function connected() {
|
|
|
72
106
|
resolve();
|
|
73
107
|
});
|
|
74
108
|
}
|
|
75
|
-
|
|
109
|
+
async function started() {
|
|
76
110
|
await connected();
|
|
77
111
|
await forgetRestart();
|
|
78
112
|
watch(kitHome(), () => {
|
|
@@ -83,13 +117,20 @@ export async function resident() {
|
|
|
83
117
|
});
|
|
84
118
|
await placeSkillSet();
|
|
85
119
|
await mkdir(agentBase(), { recursive: true });
|
|
86
|
-
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
87
|
-
const agents = new Agents(house, () => changed());
|
|
88
|
-
const copies = new LocalCopies(house);
|
|
89
|
-
await copies.load();
|
|
90
120
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
91
121
|
await installShim(codexLauncher(), 'codex-main');
|
|
122
|
+
}
|
|
123
|
+
export async function resident() {
|
|
124
|
+
await mkdir(kitHome(), { recursive: true, mode: 0o700 });
|
|
125
|
+
const copies = new LocalCopies(house);
|
|
126
|
+
await copies.load();
|
|
92
127
|
await serveOwner(copies);
|
|
128
|
+
await started().catch((error) => {
|
|
129
|
+
report(error);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
133
|
+
const agents = new Agents(house, () => changed());
|
|
93
134
|
let stream = null;
|
|
94
135
|
let reported = false;
|
|
95
136
|
const idle = () => conversations.idle() && signIns.idle() && agents.idle();
|
package/dist/stream.js
CHANGED
|
@@ -81,7 +81,7 @@ export function holdStream(handlers) {
|
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
if (refused !== null && tooOld(refused.text))
|
|
84
|
-
updateRequired(
|
|
84
|
+
updateRequired();
|
|
85
85
|
if (finalRefusal(refused))
|
|
86
86
|
end(refused.message);
|
|
87
87
|
// A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused as retryable or left unanswered backs off on the jittered curve.
|
package/dist/work.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { rm } from 'node:fs/promises';
|
|
5
5
|
import { createServer } from 'node:net';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { RUNNING_JOB } from './events.js';
|
|
8
|
-
import { kitHome } from './home.js';
|
|
8
|
+
import { kitHome, socketDirectory } from './home.js';
|
|
9
9
|
const ENDED_TASK = new Set(['completed', 'failed', 'killed']);
|
|
10
10
|
const SCHEDULING = new Set(['CronCreate', 'CronDelete', 'ScheduleWakeup']);
|
|
11
11
|
const QUEUE_LIST = 'house-kit-queue-';
|
|
@@ -194,9 +194,7 @@ class CodexWork extends Work {
|
|
|
194
194
|
proving = -1;
|
|
195
195
|
proven = -1;
|
|
196
196
|
async open(cli) {
|
|
197
|
-
|
|
198
|
-
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
199
|
-
this.path = join(sockets, `${randomUUID()}.sock`);
|
|
197
|
+
this.path = join(socketDirectory(), `${randomUUID()}.sock`);
|
|
200
198
|
this.server = createServer((socket) => this.connected(socket));
|
|
201
199
|
this.server.listen(this.path);
|
|
202
200
|
await once(this.server, 'listening');
|