@agentshouse/kit 0.2.1-c031c7457a661 → 0.2.1-c0535b8294007
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/dist/acp.js +52 -5
- package/dist/authority-main.js +2 -2
- package/dist/building.js +46 -0
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +32 -12
- package/dist/connection.js +19 -29
- package/dist/conversations.js +6 -1
- package/dist/git.js +2 -2
- package/dist/house-cli.js +3 -4
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +4 -4
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +1 -1
- package/dist/relay.js +21 -0
- package/dist/resident.js +7 -4
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +38 -12
- package/dist/transcription.js +32 -23
- package/dist/update.js +51 -8
- package/dist/web-wizard.js +3 -2
- package/package.json +16 -17
- package/dist/bootstrap.json +0 -24
package/dist/acp.js
CHANGED
|
@@ -1,31 +1,70 @@
|
|
|
1
1
|
import { ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { Readable, Writable } from 'node:stream';
|
|
4
|
-
import { CLIS, KIT_RELEASE, adapterCommand
|
|
4
|
+
import { CLIS, KIT_RELEASE, adapterCommand } from './clis.js';
|
|
5
|
+
import { childEnvironment } from './relay.js';
|
|
5
6
|
import { killTree } from './scope.js';
|
|
7
|
+
import { Silence } from './silence.js';
|
|
6
8
|
const RETIRED = 'the CLI was retired';
|
|
9
|
+
// Claude Code ends a shell command or a model request after ten minutes, so a CLI that owes the Kit an answer and sends nothing for half an hour has hung.
|
|
10
|
+
export const CLI_SILENCE_MS = 30 * 60_000;
|
|
11
|
+
const SILENT = `the CLI sent nothing for ${CLI_SILENCE_MS / 60_000} minutes`;
|
|
7
12
|
export async function startAdapter(kind, cli, cwd, app, events = () => undefined, added = {}, signal, scope = null) {
|
|
8
13
|
const adapter = CLIS[kind].adapter;
|
|
9
14
|
const started = [adapter === null ? cli : adapterCommand(kind), CLIS[kind].args];
|
|
10
15
|
const [command, args] = scope?.command(...started) ?? started;
|
|
11
16
|
const child = spawn(command, args, {
|
|
12
17
|
cwd,
|
|
13
|
-
env: { ...
|
|
18
|
+
env: { ...childEnvironment(), ...(adapter === null ? {} : { ...adapter.env, [adapter.executable]: cli }), ...added },
|
|
14
19
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
15
20
|
detached: true,
|
|
16
21
|
});
|
|
17
22
|
let owned = true;
|
|
18
23
|
let retired = false;
|
|
24
|
+
let silent = false;
|
|
25
|
+
let over = false;
|
|
26
|
+
let open = false;
|
|
19
27
|
let stderr = '';
|
|
28
|
+
const owed = new Set();
|
|
29
|
+
const asked = new Set();
|
|
30
|
+
const silence = new Silence(CLI_SILENCE_MS, () => {
|
|
31
|
+
silent = true;
|
|
32
|
+
connection.close(new Error(SILENT));
|
|
33
|
+
if (child.pid !== undefined)
|
|
34
|
+
killTree(child);
|
|
35
|
+
});
|
|
36
|
+
const watch = () => (!over && (owed.size > 0 || open) && asked.size === 0 ? silence.heard() : silence.stop());
|
|
20
37
|
child.stderr.on('data', (chunk) => {
|
|
21
38
|
stderr = (stderr + chunk.toString('utf8')).slice(-4000);
|
|
22
39
|
});
|
|
23
40
|
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
41
|
+
const output = stream.writable.getWriter();
|
|
24
42
|
const connection = app.connect({
|
|
25
|
-
writable:
|
|
43
|
+
writable: new WritableStream({
|
|
44
|
+
write(message) {
|
|
45
|
+
const sent = message;
|
|
46
|
+
if (sent.id !== undefined) {
|
|
47
|
+
if (sent.method === undefined)
|
|
48
|
+
asked.delete(sent.id);
|
|
49
|
+
else
|
|
50
|
+
owed.add(sent.id);
|
|
51
|
+
watch();
|
|
52
|
+
}
|
|
53
|
+
return output.write(message);
|
|
54
|
+
},
|
|
55
|
+
close: () => output.close(),
|
|
56
|
+
abort: (reason) => output.abort(reason),
|
|
57
|
+
}),
|
|
26
58
|
readable: stream.readable.pipeThrough(new TransformStream({
|
|
27
59
|
transform(message, controller) {
|
|
28
60
|
const read = message;
|
|
61
|
+
if (read.id !== undefined) {
|
|
62
|
+
if (read.method === undefined)
|
|
63
|
+
owed.delete(read.id);
|
|
64
|
+
else
|
|
65
|
+
asked.add(read.id);
|
|
66
|
+
}
|
|
67
|
+
watch();
|
|
29
68
|
if (owned)
|
|
30
69
|
events(read);
|
|
31
70
|
if (read.id !== undefined || (read.method !== 'session/update' && !read.method.startsWith('_'))) {
|
|
@@ -37,6 +76,8 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
37
76
|
const { promise: lost, resolve: lose } = Promise.withResolvers();
|
|
38
77
|
const exited = new Promise((resolve) => {
|
|
39
78
|
const end = (cause) => {
|
|
79
|
+
over = true;
|
|
80
|
+
silence.stop();
|
|
40
81
|
if (owned)
|
|
41
82
|
lose(cause);
|
|
42
83
|
owned = false;
|
|
@@ -46,11 +87,13 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
46
87
|
child.on('error', (error) => end(`the CLI did not start: ${error.message}`));
|
|
47
88
|
child.on('exit', (code, signal) => {
|
|
48
89
|
const last = stderr.trim().split('\n').at(-1);
|
|
49
|
-
end(`the CLI exited with ${code ?? signal}${last ? `: ${last}` : ''}`);
|
|
90
|
+
end(silent ? SILENT : `the CLI exited with ${code ?? signal}${last ? `: ${last}` : ''}`);
|
|
50
91
|
});
|
|
51
92
|
});
|
|
52
93
|
const retire = () => {
|
|
53
94
|
owned = false;
|
|
95
|
+
over = true;
|
|
96
|
+
silence.stop();
|
|
54
97
|
if (retired)
|
|
55
98
|
return;
|
|
56
99
|
retired = true;
|
|
@@ -74,7 +117,11 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
74
117
|
clientInfo: { name: '@agentshouse/kit', version: KIT_RELEASE },
|
|
75
118
|
});
|
|
76
119
|
scope?.entered(child.pid);
|
|
77
|
-
|
|
120
|
+
const turn = (opened) => {
|
|
121
|
+
open = opened;
|
|
122
|
+
watch();
|
|
123
|
+
};
|
|
124
|
+
return { connection, initialized, exited, lost, silent: () => silent, turn, retire };
|
|
78
125
|
}
|
|
79
126
|
catch (error) {
|
|
80
127
|
retire();
|
package/dist/authority-main.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { HouseRefusal, post } from './api.js';
|
|
3
2
|
import { readEnrolment } from './home.js';
|
|
3
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
4
4
|
function unanswered(error) {
|
|
5
5
|
return error instanceof HouseRefusal ? error.status >= 500 : error instanceof TypeError && error.message === 'fetch failed';
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
proxyUnlessRelayed();
|
|
8
8
|
try {
|
|
9
9
|
const { house, credential } = (await readEnrolment());
|
|
10
10
|
try {
|
package/dist/building.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
import { Transform } from 'node:stream';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { readEnrolment } from './home.js';
|
|
7
|
+
import { relayed } from './relay.js';
|
|
8
|
+
const MANIFEST = 'bootstrap.json';
|
|
9
|
+
async function served(name, signal) {
|
|
10
|
+
const enrolment = await readEnrolment();
|
|
11
|
+
if (enrolment === null)
|
|
12
|
+
throw new Error('this Kit is connected to no House');
|
|
13
|
+
const answer = await fetch(relayed(new URL(`/kit-installer/${encodeURIComponent(name)}`, enrolment.house)), { signal });
|
|
14
|
+
if (answer.status === 200 && answer.body !== null)
|
|
15
|
+
return answer;
|
|
16
|
+
await answer.body?.cancel();
|
|
17
|
+
throw new Error(`House answered ${answer.status} for ${name}`);
|
|
18
|
+
}
|
|
19
|
+
export async function servedDigest(name, signal) {
|
|
20
|
+
const manifest = (await (await served(MANIFEST, signal)).json());
|
|
21
|
+
const sha256 = manifest.files?.[name]?.sha256;
|
|
22
|
+
return typeof sha256 === 'string' ? sha256 : null;
|
|
23
|
+
}
|
|
24
|
+
export async function downloadServed(name, sha256, destination, signal, most = Infinity) {
|
|
25
|
+
const answer = await served(name, signal);
|
|
26
|
+
const digest = createHash('sha256');
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
const hashed = new Transform({
|
|
29
|
+
transform(chunk, _encoding, done) {
|
|
30
|
+
bytes += chunk.length;
|
|
31
|
+
if (bytes > most)
|
|
32
|
+
return done(new Error(`${name} is larger than its pinned ${most} bytes`));
|
|
33
|
+
digest.update(chunk);
|
|
34
|
+
done(null, chunk);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await pipeline(answer.body, hashed, createWriteStream(destination), { signal });
|
|
39
|
+
if (digest.digest('hex') !== sha256)
|
|
40
|
+
throw new Error(`${name} does not match its sha256`);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await rm(destination, { force: true });
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/clis-main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { post } from './api.js';
|
|
3
2
|
import { CLIS, below, locate, versionOf } from './clis.js';
|
|
4
3
|
import { readEnrolment } from './home.js';
|
|
5
|
-
|
|
4
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
5
|
+
proxyUnlessRelayed();
|
|
6
6
|
try {
|
|
7
7
|
const { house, credential } = (await readEnrolment());
|
|
8
8
|
const { agents } = await post(house, '/kit/agents/desired', {}, credential);
|
package/dist/clis.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
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, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { access, mkdir, readFile, rename, rm, 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 { downloadServed, servedDigest } from './building.js';
|
|
7
8
|
import { kitHome } from './home.js';
|
|
9
|
+
import { childEnvironment } from './relay.js';
|
|
8
10
|
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
9
11
|
function houseRule(houses) {
|
|
10
12
|
return `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`;
|
|
@@ -69,7 +71,7 @@ function bridgeInClaudeSandbox(bridge) {
|
|
|
69
71
|
export const CLIS = {
|
|
70
72
|
'codex-acp': {
|
|
71
73
|
bin: 'codex',
|
|
72
|
-
minimum: '0.
|
|
74
|
+
minimum: '0.160.1',
|
|
73
75
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
74
76
|
adapter: {
|
|
75
77
|
package: '@agentclientprotocol/codex-acp',
|
|
@@ -92,7 +94,7 @@ export const CLIS = {
|
|
|
92
94
|
},
|
|
93
95
|
'claude-agent-acp': {
|
|
94
96
|
bin: 'claude',
|
|
95
|
-
minimum: '2.1.
|
|
97
|
+
minimum: '2.1.303',
|
|
96
98
|
install: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
97
99
|
adapter: {
|
|
98
100
|
package: '@agentclientprotocol/claude-agent-acp',
|
|
@@ -138,7 +140,6 @@ export const CLIS = {
|
|
|
138
140
|
};
|
|
139
141
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
140
142
|
export const KIT_RELEASE = KIT_PACKAGE.version;
|
|
141
|
-
const PROXY = /^https?_proxy$/i;
|
|
142
143
|
const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
|
|
143
144
|
// An install still running after fifteen minutes is ended and reported as failed, so a hung download cannot hold its CLI's report.
|
|
144
145
|
const INSTALL_MS = 15 * 60_000;
|
|
@@ -160,15 +161,12 @@ async function installedAdapter(kind) {
|
|
|
160
161
|
return null;
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
|
-
export function withoutProxy() {
|
|
164
|
-
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
165
|
-
}
|
|
166
164
|
export function run(command, args, timeoutMs, detached = false, signal) {
|
|
167
165
|
return new Promise((resolve) => {
|
|
168
166
|
const child = spawn(command, args, {
|
|
169
167
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
170
168
|
timeout: timeoutMs,
|
|
171
|
-
env:
|
|
169
|
+
env: childEnvironment(),
|
|
172
170
|
detached,
|
|
173
171
|
signal: detached ? undefined : signal,
|
|
174
172
|
});
|
|
@@ -202,15 +200,37 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
202
200
|
export function failureOf(ran, program) {
|
|
203
201
|
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
204
202
|
}
|
|
203
|
+
export function adapterArchive(kind) {
|
|
204
|
+
return `${kind}-${KIT_PACKAGE.peerDependencies[CLIS[kind].adapter.package]}.tar.gz`;
|
|
205
|
+
}
|
|
205
206
|
export async function installAdapter(kind, signal) {
|
|
206
207
|
const adapter = CLIS[kind].adapter;
|
|
207
208
|
if (adapter === null)
|
|
208
209
|
return null;
|
|
209
|
-
|
|
210
|
-
if ((await installedAdapter(kind)) === release)
|
|
210
|
+
if ((await installedAdapter(kind)) === KIT_PACKAGE.peerDependencies[adapter.package])
|
|
211
211
|
return null;
|
|
212
|
-
const
|
|
213
|
-
|
|
212
|
+
const name = adapterArchive(kind);
|
|
213
|
+
const archive = `${prefix(kind)}.tar.gz`;
|
|
214
|
+
const deadline = AbortSignal.timeout(INSTALL_MS);
|
|
215
|
+
const bounded = signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
|
|
216
|
+
try {
|
|
217
|
+
const sha256 = await servedDigest(name, bounded);
|
|
218
|
+
if (sha256 === null)
|
|
219
|
+
return `House serves no ${name}`;
|
|
220
|
+
await rm(prefix(kind), { recursive: true, force: true });
|
|
221
|
+
await mkdir(prefix(kind), { recursive: true });
|
|
222
|
+
await downloadServed(name, sha256, archive, bounded);
|
|
223
|
+
const ran = await run('tar', ['-xzf', archive, '-C', prefix(kind)], INSTALL_MS, true, signal);
|
|
224
|
+
return ran.status === 0 ? null : failureOf(ran, 'tar');
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (deadline.aborted && signal?.aborted !== true)
|
|
228
|
+
return `${name} did not arrive within ${INSTALL_MS / 1000} seconds`;
|
|
229
|
+
return error instanceof Error ? error.message : String(error);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
await rm(archive, { force: true });
|
|
233
|
+
}
|
|
214
234
|
}
|
|
215
235
|
export async function installCli(kind, signal) {
|
|
216
236
|
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
|
package/dist/connection.js
CHANGED
|
@@ -3,40 +3,13 @@ import { HouseRefusal, retryDelay } from './api.js';
|
|
|
3
3
|
import { end, REPLACED, signInAgain } from './end.js';
|
|
4
4
|
import { readEnrolment } from './home.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
|
+
import { Silence } from './silence.js';
|
|
6
7
|
import { endUnserved, KIT_WIRE } from './wire.js';
|
|
7
8
|
const PATH = '/kit/stream';
|
|
8
9
|
const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
|
|
9
10
|
const REFUSAL_HEADER = 'x-house-refusal';
|
|
10
11
|
// House beats each Kit connection every thirty seconds, so two silent beats mean this Kit process cannot hear House.
|
|
11
12
|
export const CONNECTION_SILENCE_MS = 60_000;
|
|
12
|
-
const SYSTEM_CONNECTION_CLOCK = {
|
|
13
|
-
later: (delay, callback) => setTimeout(callback, delay),
|
|
14
|
-
clear: (timer) => clearTimeout(timer),
|
|
15
|
-
};
|
|
16
|
-
export class ConnectionSilence {
|
|
17
|
-
timer = null;
|
|
18
|
-
silent;
|
|
19
|
-
clock;
|
|
20
|
-
interval;
|
|
21
|
-
constructor(silent, clock = SYSTEM_CONNECTION_CLOCK, interval = CONNECTION_SILENCE_MS) {
|
|
22
|
-
this.silent = silent;
|
|
23
|
-
this.clock = clock;
|
|
24
|
-
this.interval = interval;
|
|
25
|
-
}
|
|
26
|
-
heard() {
|
|
27
|
-
this.stop();
|
|
28
|
-
this.timer = this.clock.later(this.interval, () => {
|
|
29
|
-
this.timer = null;
|
|
30
|
-
this.silent();
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
stop() {
|
|
34
|
-
if (this.timer === null)
|
|
35
|
-
return;
|
|
36
|
-
this.clock.clear(this.timer);
|
|
37
|
-
this.timer = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
13
|
function connectionUrl(house) {
|
|
41
14
|
const url = new URL(PATH, house);
|
|
42
15
|
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
@@ -58,19 +31,26 @@ subscribe('undici:request:headers', (message) => {
|
|
|
58
31
|
});
|
|
59
32
|
export function holdConnection(handlers) {
|
|
60
33
|
let connection = null;
|
|
34
|
+
let socket = null;
|
|
35
|
+
let paused = false;
|
|
61
36
|
let failures = 0;
|
|
62
37
|
const connect = async () => {
|
|
63
38
|
const { house, credential } = (await readEnrolment());
|
|
39
|
+
if (paused || socket !== null)
|
|
40
|
+
return;
|
|
64
41
|
refused = null;
|
|
65
42
|
const opening = new WebSocket(relayed(connectionUrl(house)), {
|
|
66
43
|
protocols: [SUBPROTOCOL],
|
|
67
44
|
headers: { authorization: `Bearer ${credential}` },
|
|
68
45
|
});
|
|
46
|
+
socket = opening;
|
|
69
47
|
let opened = false;
|
|
70
48
|
let silence = null;
|
|
71
49
|
const closed = (event) => {
|
|
72
50
|
silence?.stop();
|
|
73
51
|
silence = null;
|
|
52
|
+
if (socket === opening)
|
|
53
|
+
socket = null;
|
|
74
54
|
if (connection === opening)
|
|
75
55
|
connection = null;
|
|
76
56
|
handlers.closed();
|
|
@@ -85,6 +65,8 @@ export function holdConnection(handlers) {
|
|
|
85
65
|
endUnserved(refused.text);
|
|
86
66
|
if (!opened && refused !== null)
|
|
87
67
|
process.stderr.write(`kit: ${refused.message}\n`);
|
|
68
|
+
if (paused)
|
|
69
|
+
return;
|
|
88
70
|
// 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.
|
|
89
71
|
setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
|
|
90
72
|
};
|
|
@@ -92,7 +74,7 @@ export function holdConnection(handlers) {
|
|
|
92
74
|
opened = true;
|
|
93
75
|
failures = 0;
|
|
94
76
|
connection = opening;
|
|
95
|
-
silence = new
|
|
77
|
+
silence = new Silence(CONNECTION_SILENCE_MS, () => {
|
|
96
78
|
if (connection !== opening)
|
|
97
79
|
return;
|
|
98
80
|
connection = null;
|
|
@@ -122,5 +104,13 @@ export function holdConnection(handlers) {
|
|
|
122
104
|
connection.send(JSON.stringify(notification));
|
|
123
105
|
return true;
|
|
124
106
|
},
|
|
107
|
+
pause() {
|
|
108
|
+
paused = true;
|
|
109
|
+
socket?.close();
|
|
110
|
+
return () => {
|
|
111
|
+
paused = false;
|
|
112
|
+
void connect();
|
|
113
|
+
};
|
|
114
|
+
},
|
|
125
115
|
};
|
|
126
116
|
}
|
package/dist/conversations.js
CHANGED
|
@@ -73,7 +73,9 @@ function answeredWith(error) {
|
|
|
73
73
|
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
74
74
|
}
|
|
75
75
|
function exitedWith(running, cause) {
|
|
76
|
-
|
|
76
|
+
if (running.killed)
|
|
77
|
+
return { failure: 'killed', cause: KILLED };
|
|
78
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
77
79
|
}
|
|
78
80
|
function answerMessage(request, response) {
|
|
79
81
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
@@ -585,6 +587,7 @@ export class Conversations {
|
|
|
585
587
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
586
588
|
void work.settled().then(() => scope.record());
|
|
587
589
|
conversation.running = running;
|
|
590
|
+
adapter.turn(conversation.open > 0);
|
|
588
591
|
conversation.session = running.sessionId;
|
|
589
592
|
conversation.options = options;
|
|
590
593
|
this.facts(conversation);
|
|
@@ -702,6 +705,7 @@ export class Conversations {
|
|
|
702
705
|
begin(conversation) {
|
|
703
706
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
704
707
|
conversation.open++;
|
|
708
|
+
conversation.running?.adapter.turn(true);
|
|
705
709
|
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
706
710
|
conversation.turn = turn;
|
|
707
711
|
if (conversation.context !== null)
|
|
@@ -724,6 +728,7 @@ export class Conversations {
|
|
|
724
728
|
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
725
729
|
turn.ended = true;
|
|
726
730
|
conversation.open--;
|
|
731
|
+
conversation.running?.adapter.turn(conversation.open > 0);
|
|
727
732
|
if (conversation.turn === turn)
|
|
728
733
|
conversation.turn = null;
|
|
729
734
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
package/dist/git.js
CHANGED
|
@@ -2,8 +2,8 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { isUtf8 } from 'node:buffer';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { withoutProxy } from './clis.js';
|
|
6
5
|
import { workspaceRoot } from './home.js';
|
|
6
|
+
import { childEnvironment } from './relay.js';
|
|
7
7
|
export function localCopies() {
|
|
8
8
|
return join(workspaceRoot(), 'local-copies');
|
|
9
9
|
}
|
|
@@ -11,7 +11,7 @@ export const HOUSE_REF = 'refs/house/received';
|
|
|
11
11
|
const COMMITTER = 'Kit <house-kit@localhost>';
|
|
12
12
|
export function run(cwd, args, input, env = {}) {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
|
-
const child = spawn('git', args, { cwd, env: { ...
|
|
14
|
+
const child = spawn('git', args, { cwd, env: { ...childEnvironment(), ...env } });
|
|
15
15
|
const stdout = [];
|
|
16
16
|
let stderr = '';
|
|
17
17
|
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
package/dist/house-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import { request
|
|
3
|
+
import { request } from 'node:http';
|
|
4
4
|
import { request as httpsRequest } from 'node:https';
|
|
5
5
|
import { extname, join, resolve } from 'node:path';
|
|
6
6
|
import { Readable } from 'node:stream';
|
|
@@ -10,7 +10,7 @@ import { HOUSE_REF } from './git.js';
|
|
|
10
10
|
import { ownerSocket, readOwnAgent } from './home.js';
|
|
11
11
|
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
12
12
|
import { refusalOf, retryable } from './refusals.js';
|
|
13
|
-
import { relayed, selectRelay } from './relay.js';
|
|
13
|
+
import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
|
|
14
14
|
import { parse } from 'yaml';
|
|
15
15
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
16
16
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
@@ -89,7 +89,6 @@ async function direct(message) {
|
|
|
89
89
|
if (ownAgent === null)
|
|
90
90
|
throw new Error(UNCONNECTED);
|
|
91
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
92
|
-
setGlobalProxyFromEnv();
|
|
93
92
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
94
93
|
throw new Unsettled(UNREACHABLE);
|
|
95
94
|
});
|
|
@@ -120,7 +119,7 @@ function opened(url, method, headers, body) {
|
|
|
120
119
|
const local = socketPath !== undefined;
|
|
121
120
|
const target = local ? null : relayed(url);
|
|
122
121
|
if (!local)
|
|
123
|
-
|
|
122
|
+
proxyUnlessRelayed();
|
|
124
123
|
const sent = target === null
|
|
125
124
|
? request({ socketPath, path: url, method, headers }, answered)
|
|
126
125
|
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
package/dist/house-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`house: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`house: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/instructions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentInstructions } from '@agentshouse/skills';
|
|
2
2
|
import { HOUSE_REF } from './git.js';
|
|
3
|
-
const
|
|
3
|
+
const OWNER_RULES = '/rooms/private/owner-rules.md';
|
|
4
4
|
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
5
5
|
const FAILED = /^exit: [1-9]\d*$/;
|
|
6
6
|
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
@@ -8,9 +8,9 @@ function cutOf(lines) {
|
|
|
8
8
|
const cut = CUT.exec(lines[0] ?? '');
|
|
9
9
|
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
10
10
|
}
|
|
11
|
-
async function
|
|
11
|
+
async function readOwnerRules(bridge) {
|
|
12
12
|
let document = '';
|
|
13
|
-
for (let command = `cat ${
|
|
13
|
+
for (let command = `cat ${OWNER_RULES}`; command !== undefined;) {
|
|
14
14
|
const read = await bridge.tool('shell', { command });
|
|
15
15
|
const text = read.content.map((content) => content.text).join('\n');
|
|
16
16
|
const lines = text.split('\n');
|
|
@@ -28,5 +28,5 @@ async function readHowWeWork(bridge) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function instructions(bridge, base, copies) {
|
|
30
30
|
const lines = agentInstructions(HOUSE_REF, copies);
|
|
31
|
-
return [lines.join('\n'), base, (await
|
|
31
|
+
return [lines.join('\n'), base, (await readOwnerRules(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
32
32
|
}
|
package/dist/kit-cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { parseArgs } from 'node:util';
|
|
3
2
|
import { answerGitCredential } from './app-git.js';
|
|
4
3
|
import { chatgpt } from './chatgpt.js';
|
|
5
4
|
import { login } from './login.js';
|
|
6
5
|
import { logout } from './logout.js';
|
|
6
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
7
7
|
import { resident } from './resident.js';
|
|
8
8
|
const USAGE = 'usage: kit <login|logout|resident|chatgpt>\n';
|
|
9
9
|
const COMMANDS = new Map([
|
|
@@ -36,7 +36,7 @@ const COMMANDS = new Map([
|
|
|
36
36
|
],
|
|
37
37
|
]);
|
|
38
38
|
const [command, ...argv] = process.argv.slice(2);
|
|
39
|
-
|
|
39
|
+
proxyUnlessRelayed();
|
|
40
40
|
async function run() {
|
|
41
41
|
if (command === 'login')
|
|
42
42
|
return login(argv);
|
package/dist/kit-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`kit: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`kit: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/login.js
CHANGED
|
@@ -119,7 +119,7 @@ export async function login(argv) {
|
|
|
119
119
|
open(started.start_url);
|
|
120
120
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
121
121
|
}
|
|
122
|
-
await placeSkillSet();
|
|
122
|
+
await placeSkillSet(house);
|
|
123
123
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
124
124
|
const relayRecord = relay === undefined ? {} : { relay };
|
|
125
125
|
await writeEnrolment({
|
package/dist/relay.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
const PROXY = /^https?_proxy$/i;
|
|
3
|
+
const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
1
4
|
let selected;
|
|
5
|
+
let restoreProxy;
|
|
2
6
|
export function selectRelay(relay) {
|
|
3
7
|
selected = relay === undefined || relay === '' ? null : relay;
|
|
8
|
+
proxyUnlessRelayed();
|
|
4
9
|
}
|
|
5
10
|
export function relayOrigin() {
|
|
6
11
|
if (selected !== undefined)
|
|
@@ -8,6 +13,22 @@ export function relayOrigin() {
|
|
|
8
13
|
const ambient = process.env.HOUSE_KIT_RELAY;
|
|
9
14
|
return ambient === undefined || ambient === '' ? undefined : ambient;
|
|
10
15
|
}
|
|
16
|
+
function unlessRelayed() {
|
|
17
|
+
if (relayOrigin() === undefined)
|
|
18
|
+
return { ...process.env };
|
|
19
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
20
|
+
}
|
|
21
|
+
export function proxyUnlessRelayed() {
|
|
22
|
+
restoreProxy?.();
|
|
23
|
+
restoreProxy = setGlobalProxyFromEnv(unlessRelayed());
|
|
24
|
+
}
|
|
25
|
+
export function childEnvironment() {
|
|
26
|
+
const environment = unlessRelayed();
|
|
27
|
+
if (!Object.keys(environment).some((name) => PROXY.test(name)))
|
|
28
|
+
return environment;
|
|
29
|
+
const bypassed = [environment.NO_PROXY ?? environment.no_proxy, ...LOOPBACK].filter((entry) => entry !== undefined && entry !== '').join(',');
|
|
30
|
+
return { ...environment, NO_PROXY: bypassed, no_proxy: bypassed };
|
|
31
|
+
}
|
|
11
32
|
export function relayed(target) {
|
|
12
33
|
const url = new URL(target);
|
|
13
34
|
const relay = relayOrigin();
|
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 } from './end.js';
|
|
14
|
-
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, readEnrolment, restartAsked } from './home.js';
|
|
15
15
|
import { Calls } from './calls.js';
|
|
16
16
|
import { killDescendants } from './scope.js';
|
|
17
17
|
import { quoted } from './shell.js';
|
|
@@ -117,7 +117,7 @@ async function started() {
|
|
|
117
117
|
killDescendants();
|
|
118
118
|
process.exit(1);
|
|
119
119
|
});
|
|
120
|
-
await placeSkillSet();
|
|
120
|
+
await placeSkillSet((await readEnrolment()).house);
|
|
121
121
|
await mkdir(agentBase(), { recursive: true });
|
|
122
122
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
123
123
|
await installShim(codexLauncher(), 'codex-main');
|
|
@@ -143,6 +143,9 @@ export async function resident() {
|
|
|
143
143
|
else if (restartAsked() && conversations.quiet()) {
|
|
144
144
|
restart('restarting for an updated CLI');
|
|
145
145
|
}
|
|
146
|
+
else if (update.held() && conversations.quiet()) {
|
|
147
|
+
update.apply(connection.pause());
|
|
148
|
+
}
|
|
146
149
|
else if (!reported) {
|
|
147
150
|
reported = true;
|
|
148
151
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
@@ -158,7 +161,7 @@ export async function resident() {
|
|
|
158
161
|
changed,
|
|
159
162
|
});
|
|
160
163
|
const signIns = new SignIns(house, agents, calls);
|
|
161
|
-
const update = new KitUpdate(calls);
|
|
164
|
+
const update = new KitUpdate(calls, changed);
|
|
162
165
|
const wizards = new WebWizards(calls, (notification) => connection?.send(notification) ?? false);
|
|
163
166
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
164
167
|
process.once(signal, () => {
|
|
@@ -180,7 +183,7 @@ export async function resident() {
|
|
|
180
183
|
if (call.kind === 'sign_in')
|
|
181
184
|
signIns.start(call);
|
|
182
185
|
else if (call.kind === 'update')
|
|
183
|
-
update.
|
|
186
|
+
update.hold(call);
|
|
184
187
|
else if (call.kind === 'run_wizard')
|
|
185
188
|
wizards.start(call);
|
|
186
189
|
else if (CONVERSATION_METHODS.has(call.kind))
|
package/dist/sign-in.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
-
import { CLIS
|
|
3
|
+
import { CLIS } from './clis.js';
|
|
4
|
+
import { childEnvironment } from './relay.js';
|
|
4
5
|
import { killTree } from './scope.js';
|
|
5
6
|
import { holdSecretInput } from './secret-input.js';
|
|
6
7
|
const ESCAPE = /\u001b\[[\d;]*m/g;
|
|
@@ -56,7 +57,7 @@ export class SignIns {
|
|
|
56
57
|
const child = spawn(cli, login.args, {
|
|
57
58
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
58
59
|
detached: true,
|
|
59
|
-
env:
|
|
60
|
+
env: childEnvironment(),
|
|
60
61
|
});
|
|
61
62
|
const running = () => child.exitCode === null && child.signalCode === null;
|
|
62
63
|
const stop = () => killTree(child);
|
package/dist/silence.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const SYSTEM_CLOCK = {
|
|
2
|
+
later: (delay, callback) => setTimeout(callback, delay),
|
|
3
|
+
clear: (timer) => clearTimeout(timer),
|
|
4
|
+
};
|
|
5
|
+
export class Silence {
|
|
6
|
+
timer = null;
|
|
7
|
+
interval;
|
|
8
|
+
silent;
|
|
9
|
+
clock;
|
|
10
|
+
constructor(interval, silent, clock = SYSTEM_CLOCK) {
|
|
11
|
+
this.interval = interval;
|
|
12
|
+
this.silent = silent;
|
|
13
|
+
this.clock = clock;
|
|
14
|
+
}
|
|
15
|
+
heard() {
|
|
16
|
+
this.stop();
|
|
17
|
+
this.timer = this.clock.later(this.interval, () => {
|
|
18
|
+
this.timer = null;
|
|
19
|
+
this.silent();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
stop() {
|
|
23
|
+
if (this.timer === null)
|
|
24
|
+
return;
|
|
25
|
+
this.clock.clear(this.timer);
|
|
26
|
+
this.timer = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/skills.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { cp, lstat, mkdir, realpath, rm, symlink } from 'node:fs/promises';
|
|
1
|
+
import { cp, lstat, mkdir, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
|
-
import { listSkills } from '@agentshouse/skills';
|
|
4
|
+
import { GUIDE, listSkills } from '@agentshouse/skills';
|
|
5
|
+
import { parse } from 'yaml';
|
|
5
6
|
import { KIT_RELEASE } from './clis.js';
|
|
6
7
|
import { kitHome, readConfiguration, readHome, writeHome } from './home.js';
|
|
7
8
|
const RECORD = 'skill-set.json';
|
|
9
|
+
const ENTRY = 'SKILL.md';
|
|
10
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---\n/u;
|
|
11
|
+
const ORIGIN = '<origin>';
|
|
8
12
|
function skillsDirectory() {
|
|
9
13
|
return join(homedir(), '.agents', 'skills');
|
|
10
14
|
}
|
|
@@ -22,9 +26,31 @@ async function present(path) {
|
|
|
22
26
|
throw error;
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
|
-
|
|
29
|
+
function skillName(text) {
|
|
30
|
+
const front = FRONTMATTER.exec(text);
|
|
31
|
+
const fields = (front === null ? null : parse(front[1]));
|
|
32
|
+
return typeof fields?.name === 'string' && typeof fields.description === 'string' ? fields.name : undefined;
|
|
33
|
+
}
|
|
34
|
+
function skillSet(origin) {
|
|
35
|
+
const upstream = listSkills().map(({ name, directory }) => ({
|
|
36
|
+
name,
|
|
37
|
+
place: (destination) => cp(directory, destination, { recursive: true }),
|
|
38
|
+
}));
|
|
39
|
+
const pages = GUIDE.flatMap(({ text }) => {
|
|
40
|
+
const name = skillName(text);
|
|
41
|
+
if (name === undefined)
|
|
42
|
+
return [];
|
|
43
|
+
const place = async (destination) => {
|
|
44
|
+
await mkdir(destination);
|
|
45
|
+
await writeFile(join(destination, ENTRY), text.replaceAll(ORIGIN, origin));
|
|
46
|
+
};
|
|
47
|
+
return [{ name, place }];
|
|
48
|
+
});
|
|
49
|
+
return [...upstream, ...pages].sort((left, right) => (left.name < right.name ? -1 : 1));
|
|
50
|
+
}
|
|
51
|
+
async function install(origin) {
|
|
26
52
|
const placed = [];
|
|
27
|
-
for (const skill of
|
|
53
|
+
for (const skill of skillSet(origin)) {
|
|
28
54
|
if (!(await present(join(skillsDirectory(), skill.name))) && !(await present(join(claudeDirectory(), skill.name)))) {
|
|
29
55
|
placed.push(skill);
|
|
30
56
|
}
|
|
@@ -35,32 +61,32 @@ async function install() {
|
|
|
35
61
|
await mkdir(claudeDirectory(), { recursive: true });
|
|
36
62
|
const skills = await realpath(skillsDirectory());
|
|
37
63
|
const claude = await realpath(claudeDirectory());
|
|
38
|
-
for (const { name,
|
|
39
|
-
await
|
|
64
|
+
for (const { name, place } of placed) {
|
|
65
|
+
await place(join(skillsDirectory(), name));
|
|
40
66
|
if (claude !== skills)
|
|
41
67
|
await symlink(relative(claude, join(skills, name)), join(claudeDirectory(), name));
|
|
42
68
|
}
|
|
43
|
-
await writeHome(RECORD, { version: KIT_RELEASE, names });
|
|
69
|
+
await writeHome(RECORD, { version: KIT_RELEASE, origin, names });
|
|
44
70
|
}
|
|
45
|
-
export async function placeSkillSet() {
|
|
71
|
+
export async function placeSkillSet(origin) {
|
|
46
72
|
try {
|
|
47
|
-
await replaceSkillSet();
|
|
73
|
+
await replaceSkillSet(origin);
|
|
48
74
|
}
|
|
49
75
|
catch (error) {
|
|
50
76
|
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
|
-
async function replaceSkillSet() {
|
|
79
|
+
async function replaceSkillSet(origin) {
|
|
54
80
|
const accepted = (await readConfiguration()).skills;
|
|
55
81
|
const installed = await readHome(RECORD);
|
|
56
|
-
if (accepted && installed?.version === KIT_RELEASE)
|
|
82
|
+
if (accepted && installed?.version === KIT_RELEASE && installed.origin === origin)
|
|
57
83
|
return;
|
|
58
84
|
for (const name of installed?.names ?? []) {
|
|
59
85
|
await rm(join(claudeDirectory(), name), { recursive: true, force: true });
|
|
60
86
|
await rm(join(skillsDirectory(), name), { recursive: true, force: true });
|
|
61
87
|
}
|
|
62
88
|
if (accepted)
|
|
63
|
-
await install();
|
|
89
|
+
await install(origin);
|
|
64
90
|
else
|
|
65
91
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
66
92
|
}
|
package/dist/transcription.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { createReadStream } from 'node:fs';
|
|
3
1
|
import { chmod, mkdir, mkdtemp, readdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
|
4
2
|
import { availableParallelism, tmpdir } from 'node:os';
|
|
5
3
|
import { basename, join } from 'node:path';
|
|
6
|
-
import { pipeline } from 'node:stream/promises';
|
|
7
4
|
import { unlessAborted } from './abort.js';
|
|
5
|
+
import { downloadServed } from './building.js';
|
|
8
6
|
import { failureOf, run } from './clis.js';
|
|
9
7
|
import { downloaded, fetched } from './files.js';
|
|
10
8
|
import { kitHome } from './home.js';
|
|
11
9
|
const RELEASE = 'https://github.com/agentshouse/kit/releases/download/engine-2';
|
|
12
10
|
export const MODEL = {
|
|
13
|
-
|
|
11
|
+
name: 'ggml-parakeet-tdt-0.6b-v3-q4_k.bin',
|
|
12
|
+
from: 'https://huggingface.co/ggml-org/parakeet-GGUF/resolve/35156454d1a39de06863303dd209fd2bed6ee079/ggml-parakeet-tdt-0.6b-v3-q4_k.bin',
|
|
14
13
|
sha256: '8b205b8b39c6535e153de6fb11c51db46125d45c4f16ba496fe41a0fe71b885e',
|
|
14
|
+
bytes: 415_611_879,
|
|
15
15
|
executable: false,
|
|
16
16
|
};
|
|
17
17
|
export const ENGINES = {
|
|
18
18
|
'linux-x64': {
|
|
19
|
-
decoder: {
|
|
20
|
-
engine: {
|
|
19
|
+
decoder: { name: 'ffmpeg-linux-x86_64', from: `${RELEASE}/ffmpeg-linux-x86_64`, sha256: '1f8235d206428305afb89c6e8ab99d100425a0ba61e22bb4c7aff017db8c8de5', bytes: 5_175_832, executable: true },
|
|
20
|
+
engine: { name: 'parakeet-cli-linux-x86_64', from: `${RELEASE}/parakeet-cli-linux-x86_64`, sha256: 'bb2585695992fe1927a33960002cdf51b8a90a96ad65593c3d0a9f1fbc0865c2', bytes: 3_801_616, executable: true },
|
|
21
21
|
},
|
|
22
22
|
'linux-arm64': {
|
|
23
|
-
decoder: {
|
|
24
|
-
engine: {
|
|
23
|
+
decoder: { name: 'ffmpeg-linux-aarch64', from: `${RELEASE}/ffmpeg-linux-aarch64`, sha256: 'a7e08a0fae0179d1078312f6bb13d2a9aa4d4c71a1d82a022953f25dee452f5e', bytes: 4_590_208, executable: true },
|
|
24
|
+
engine: { name: 'parakeet-cli-linux-aarch64', from: `${RELEASE}/parakeet-cli-linux-aarch64`, sha256: 'cbc0bedc8daae97f1ef9dcee4200c06226218e300e0fd62ead1306d22742b7ed', bytes: 3_477_624, executable: true },
|
|
25
25
|
},
|
|
26
26
|
'darwin-arm64': {
|
|
27
|
-
decoder: {
|
|
28
|
-
engine: {
|
|
27
|
+
decoder: { name: 'ffmpeg-darwin-arm64', from: `${RELEASE}/ffmpeg-darwin-arm64`, sha256: '7abe154aaed3d6b49f5167ca7221bb66bed51388925d99a7e4261a41fe52fba6', bytes: 3_696_768, executable: true },
|
|
28
|
+
engine: { name: 'parakeet-cli-darwin-arm64', from: `${RELEASE}/parakeet-cli-darwin-arm64`, sha256: '9a2aafc0922ad7c0b6db6eff1641e96572ba32ac40bf995a1df242ed2febf297', bytes: 1_939_728, executable: true },
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
const CHUNK_SECONDS = 20;
|
|
@@ -46,7 +46,7 @@ function installed(artifact) {
|
|
|
46
46
|
return join(directory(), artifact.sha256);
|
|
47
47
|
}
|
|
48
48
|
async function executed(command, args, signal) {
|
|
49
|
-
// A
|
|
49
|
+
// A decode or a transcription runs as long as its audio needs; the setting turning off or the turn's interrupt or end aborts it instead.
|
|
50
50
|
const ran = await run(command, args, 0, false, signal);
|
|
51
51
|
if (ran.status !== 0)
|
|
52
52
|
throw new Error(failureOf(ran, basename(command)));
|
|
@@ -60,11 +60,6 @@ async function threads() {
|
|
|
60
60
|
const allowed = quota === 'max' ? Infinity : Math.ceil(Number(quota) / Number(period));
|
|
61
61
|
return Math.max(1, Math.floor(Math.min(availableParallelism(), allowed) / 2));
|
|
62
62
|
}
|
|
63
|
-
async function sha256(path) {
|
|
64
|
-
const hash = createHash('sha256');
|
|
65
|
-
await pipeline(createReadStream(path), hash);
|
|
66
|
-
return hash.digest('hex');
|
|
67
|
-
}
|
|
68
63
|
function causeOf(error) {
|
|
69
64
|
return error instanceof Error ? error.message : String(error);
|
|
70
65
|
}
|
|
@@ -85,6 +80,25 @@ function ready() {
|
|
|
85
80
|
function pinnedHere() {
|
|
86
81
|
return ENGINES[`${process.platform}-${process.arch}`];
|
|
87
82
|
}
|
|
83
|
+
const DOWNLOADS = 3;
|
|
84
|
+
async function downloadedAgain(artifact, signal) {
|
|
85
|
+
// An artifact arrives at a hundred kilobytes a second or its download ends, so a stalled Building cannot hold the install open.
|
|
86
|
+
const deadlineMs = Math.ceil(artifact.bytes / 100);
|
|
87
|
+
const deadline = AbortSignal.timeout(deadlineMs);
|
|
88
|
+
for (let attempt = 1;; attempt++) {
|
|
89
|
+
try {
|
|
90
|
+
return await downloadServed(artifact.name, artifact.sha256, `${installed(artifact)}.part`, AbortSignal.any([signal, deadline]), artifact.bytes);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (signal.aborted)
|
|
94
|
+
throw error;
|
|
95
|
+
if (deadline.aborted)
|
|
96
|
+
throw new Error(`${artifact.name} did not arrive within ${Math.ceil(deadlineMs / 1000)} seconds`);
|
|
97
|
+
if (attempt === DOWNLOADS)
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
88
102
|
async function install(artifacts, signal) {
|
|
89
103
|
await mkdir(directory(), { recursive: true });
|
|
90
104
|
const pinned = new Set(artifacts.map((artifact) => artifact.sha256));
|
|
@@ -97,13 +111,8 @@ async function install(artifacts, signal) {
|
|
|
97
111
|
if (!(await present(installed(artifact))))
|
|
98
112
|
missing.push(artifact);
|
|
99
113
|
try {
|
|
100
|
-
for (const artifact of missing)
|
|
101
|
-
|
|
102
|
-
// Three retries carry a large download past a dropped connection before the download fails.
|
|
103
|
-
await executed('curl', ['-fsSL', '--retry', '3', '-o', part, artifact.url], signal);
|
|
104
|
-
if ((await sha256(part)) !== artifact.sha256)
|
|
105
|
-
throw new Error(`${artifact.url} does not match its sha256`);
|
|
106
|
-
}
|
|
114
|
+
for (const artifact of missing)
|
|
115
|
+
await downloadedAgain(artifact, signal);
|
|
107
116
|
for (const artifact of missing) {
|
|
108
117
|
if (artifact.executable)
|
|
109
118
|
await chmod(`${installed(artifact)}.part`, 0o755);
|
package/dist/update.js
CHANGED
|
@@ -6,6 +6,13 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { run } from './clis.js';
|
|
7
7
|
import { kitHome, workspaceRoot } from './home.js';
|
|
8
8
|
import { service, throughSudo } from './placement.js';
|
|
9
|
+
import { relayed } from './relay.js';
|
|
10
|
+
const BOOTSTRAP_BYTES = 1_000_000;
|
|
11
|
+
// A bootstrap is at most a million bytes, so two minutes carry it over ten kilobytes a second and no stalled host holds the update queue.
|
|
12
|
+
const BOOTSTRAP_MS = 120_000;
|
|
13
|
+
// A bootstrap's downloads and CLI updates take minutes, so one still running after an hour has hung and is ended before it keeps the Kit offline.
|
|
14
|
+
const RERUN_MS = 60 * 60_000;
|
|
15
|
+
const TIMED_OUT = '124';
|
|
9
16
|
function directory() {
|
|
10
17
|
return join(kitHome(), 'update');
|
|
11
18
|
}
|
|
@@ -31,7 +38,7 @@ function placement() {
|
|
|
31
38
|
return service() === 'user' ? ['--workspace', workspaceRoot()] : ['--linux'];
|
|
32
39
|
}
|
|
33
40
|
function record() {
|
|
34
|
-
return `outcome=$1; shift; bash "$0" "$@"; printf %s "$?" > "$outcome"; ${[...elevated(), 'systemctl', ...manager(), 'start', 'house-kit.service'].join(' ')}`;
|
|
41
|
+
return `outcome=$1; shift; timeout ${RERUN_MS / 1000} bash "$0" "$@"; printf %s "$?" > "$outcome"; ${[...elevated(), 'systemctl', ...manager(), 'start', 'house-kit.service'].join(' ')}`;
|
|
35
42
|
}
|
|
36
43
|
async function pendingCall() {
|
|
37
44
|
try {
|
|
@@ -45,6 +52,8 @@ async function recorded() {
|
|
|
45
52
|
const status = await readFile(outcome(), 'utf8').catch(() => null);
|
|
46
53
|
if (status === null)
|
|
47
54
|
return null;
|
|
55
|
+
if (status.trim() === TIMED_OUT)
|
|
56
|
+
return { refused: `the Kit bootstrap did not finish within ${RERUN_MS / 1000} seconds` };
|
|
48
57
|
return status.trim() === '0' ? {} : { refused: `the Kit bootstrap exited with ${status.trim()}` };
|
|
49
58
|
}
|
|
50
59
|
async function forget(callId) {
|
|
@@ -66,6 +75,17 @@ async function finished(callId) {
|
|
|
66
75
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
67
76
|
}
|
|
68
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
|
+
}
|
|
69
89
|
function rerun(script, callId, flags) {
|
|
70
90
|
return new Promise((resolve) => {
|
|
71
91
|
const [command, ...args] = [
|
|
@@ -94,9 +114,12 @@ function rerun(script, callId, flags) {
|
|
|
94
114
|
}
|
|
95
115
|
export class KitUpdate {
|
|
96
116
|
calls;
|
|
117
|
+
changed;
|
|
118
|
+
waiting = new Map();
|
|
97
119
|
queue = Promise.resolve();
|
|
98
|
-
constructor(calls) {
|
|
120
|
+
constructor(calls, changed) {
|
|
99
121
|
this.calls = calls;
|
|
122
|
+
this.changed = changed;
|
|
100
123
|
}
|
|
101
124
|
async resumed() {
|
|
102
125
|
const callId = await pendingCall();
|
|
@@ -104,8 +127,17 @@ export class KitUpdate {
|
|
|
104
127
|
return;
|
|
105
128
|
await this.enqueue(callId, () => finished(callId));
|
|
106
129
|
}
|
|
107
|
-
|
|
108
|
-
|
|
130
|
+
hold(call) {
|
|
131
|
+
this.waiting.set(call.input_id, call);
|
|
132
|
+
this.changed();
|
|
133
|
+
}
|
|
134
|
+
held() {
|
|
135
|
+
return this.waiting.size > 0;
|
|
136
|
+
}
|
|
137
|
+
apply(resume) {
|
|
138
|
+
const held = [...this.waiting.values()];
|
|
139
|
+
this.waiting.clear();
|
|
140
|
+
void Promise.all(held.map((call) => this.enqueue(call.input_id, () => this.carry(call)))).then(resume);
|
|
109
141
|
}
|
|
110
142
|
enqueue(callId, work) {
|
|
111
143
|
const before = this.queue;
|
|
@@ -119,10 +151,21 @@ export class KitUpdate {
|
|
|
119
151
|
async carry(call) {
|
|
120
152
|
if ((await pendingCall()) === call.input_id)
|
|
121
153
|
return finished(call.input_id);
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
const deadline = AbortSignal.timeout(BOOTSTRAP_MS);
|
|
155
|
+
let script;
|
|
156
|
+
try {
|
|
157
|
+
const answer = await fetch(relayed(call.bootstrap), { signal: deadline });
|
|
158
|
+
if (!answer.ok)
|
|
159
|
+
return { refused: `the Kit bootstrap could not be downloaded: ${answer.status}` };
|
|
160
|
+
script = await bounded(answer.body, BOOTSTRAP_BYTES);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (!deadline.aborted)
|
|
164
|
+
throw error;
|
|
165
|
+
return { refused: `the Kit bootstrap did not arrive within ${BOOTSTRAP_MS / 1000} seconds` };
|
|
166
|
+
}
|
|
167
|
+
if (script === null)
|
|
168
|
+
return { refused: `the Kit bootstrap is larger than ${BOOTSTRAP_BYTES} bytes` };
|
|
126
169
|
if (createHash('sha256').update(script).digest('hex') !== call.sha256) {
|
|
127
170
|
return { refused: 'the Kit bootstrap does not match its sha256' };
|
|
128
171
|
}
|
package/dist/web-wizard.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { loginPath, onLoginPath
|
|
1
|
+
import { loginPath, onLoginPath } from './clis.js';
|
|
2
|
+
import { childEnvironment } from './relay.js';
|
|
2
3
|
import { killTree } from './scope.js';
|
|
3
4
|
const TERMINAL = 'xterm-256color';
|
|
4
5
|
const SIGNALLED = 128;
|
|
@@ -62,7 +63,7 @@ export class WebWizards {
|
|
|
62
63
|
name: TERMINAL,
|
|
63
64
|
...run.size,
|
|
64
65
|
cwd: process.cwd(),
|
|
65
|
-
env: { ...
|
|
66
|
+
env: { ...childEnvironment(), PATH: path },
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
catch (error) {
|
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-c0535b8294007",
|
|
4
4
|
"description": "Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,28 +12,27 @@
|
|
|
12
12
|
"kit": "dist/kit-main.js",
|
|
13
13
|
"house": "dist/house-main.js"
|
|
14
14
|
},
|
|
15
|
-
"exports": {
|
|
16
|
-
"./bootstrap": "./dist/bootstrap.json"
|
|
17
|
-
},
|
|
18
15
|
"files": [
|
|
19
16
|
"dist"
|
|
20
17
|
],
|
|
21
18
|
"scripts": {
|
|
22
|
-
"
|
|
19
|
+
"bootstrap-manifest": "node scripts/bootstrap-manifest.mjs",
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
21
|
"engine": "mkdir -p tmp/engine && docker run --rm -v \"$PWD:/kit\" -w /kit alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 sh scripts/engine.sh tmp/engine",
|
|
24
22
|
"follow-skills": "node scripts/follow-skills.mjs",
|
|
23
|
+
"release-files": "node scripts/release-files.mjs",
|
|
25
24
|
"test": "tsc -p tsconfig.json && vitest run"
|
|
26
25
|
},
|
|
27
26
|
"dependencies": {
|
|
28
|
-
"@agentclientprotocol/sdk": "
|
|
29
|
-
"@agentshouse/mdmodel": "0.1.0-
|
|
30
|
-
"@agentshouse/skills": "0.1.0-
|
|
27
|
+
"@agentclientprotocol/sdk": "2.0.1",
|
|
28
|
+
"@agentshouse/mdmodel": "0.1.0-c4f4d37bc9d6a",
|
|
29
|
+
"@agentshouse/skills": "0.1.0-c08575ab21b57",
|
|
31
30
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
32
31
|
"yaml": "2.9.1"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
36
|
-
"@agentclientprotocol/codex-acp": "2.
|
|
34
|
+
"@agentclientprotocol/claude-agent-acp": "0.94.0",
|
|
35
|
+
"@agentclientprotocol/codex-acp": "2.2.2"
|
|
37
36
|
},
|
|
38
37
|
"peerDependenciesMeta": {
|
|
39
38
|
"@agentclientprotocol/claude-agent-acp": {
|
|
@@ -44,26 +43,26 @@
|
|
|
44
43
|
}
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
|
-
"@types/node": "24.
|
|
46
|
+
"@types/node": "24.19.2",
|
|
48
47
|
"@types/ws": "8.18.2",
|
|
49
48
|
"typescript": "7.0.2",
|
|
50
|
-
"vitest": "5.0.
|
|
49
|
+
"vitest": "5.0.5",
|
|
51
50
|
"ws": "8.22.0"
|
|
52
51
|
},
|
|
53
|
-
"packageManager": "npm@11.19.
|
|
52
|
+
"packageManager": "npm@11.19.1",
|
|
54
53
|
"engines": {
|
|
55
|
-
"node": "24.
|
|
56
|
-
"npm": "11.19.
|
|
54
|
+
"node": "24.22.1",
|
|
55
|
+
"npm": "11.19.1"
|
|
57
56
|
},
|
|
58
57
|
"devEngines": {
|
|
59
58
|
"runtime": {
|
|
60
59
|
"name": "node",
|
|
61
|
-
"version": "24.
|
|
60
|
+
"version": "24.22.1",
|
|
62
61
|
"onFail": "error"
|
|
63
62
|
},
|
|
64
63
|
"packageManager": {
|
|
65
64
|
"name": "npm",
|
|
66
|
-
"version": "11.19.
|
|
65
|
+
"version": "11.19.1",
|
|
67
66
|
"onFail": "error"
|
|
68
67
|
}
|
|
69
68
|
}
|
package/dist/bootstrap.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": 2,
|
|
3
|
-
"version": "0.2.1-c031c7457a661",
|
|
4
|
-
"hosts": {
|
|
5
|
-
"macos": {
|
|
6
|
-
"asset": {
|
|
7
|
-
"name": "connect-linux.sh",
|
|
8
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c031c7457a661/connect-linux.sh"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"linux": {
|
|
12
|
-
"asset": {
|
|
13
|
-
"name": "connect-linux.sh",
|
|
14
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c031c7457a661/connect-linux.sh"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"windows": {
|
|
18
|
-
"asset": {
|
|
19
|
-
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c031c7457a661/connect-windows.ps1"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|