@agentshouse/kit 0.2.1-alpha.9 → 0.2.1-c5262a8cc063d
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 +32 -14
- package/dist/abort.js +9 -0
- package/dist/acp.js +27 -7
- package/dist/agents.js +204 -52
- package/dist/api.js +4 -1
- package/dist/bootstrap.json +10 -14
- package/dist/bridge.js +85 -51
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +102 -32
- package/dist/conversations.js +104 -27
- package/dist/copies.js +47 -38
- package/dist/git-main.js +2 -2
- package/dist/git.js +6 -2
- package/dist/home.js +32 -3
- package/dist/house-main.js +96 -41
- package/dist/instructions.js +13 -12
- package/dist/kit-main.js +4 -1
- package/dist/login.js +24 -4
- package/dist/logout.js +18 -0
- package/dist/mcp.js +38 -0
- package/dist/operation.js +7 -4
- package/dist/refusals.js +41 -0
- package/dist/resident.js +34 -9
- package/dist/secret-input.js +4 -2
- package/dist/sign-in.js +17 -5
- package/dist/skills.js +5 -10
- package/dist/stream.js +2 -3
- package/dist/transcription.js +122 -0
- package/dist/update.js +117 -0
- package/how-we-work.md +10 -12
- package/package.json +4 -7
package/dist/stream.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { retryDelay } from './api.js';
|
|
1
2
|
import { readEnrolment } from './home.js';
|
|
2
3
|
const SUBPROTOCOL = 'house.kit.stream.1';
|
|
3
4
|
function streamUrl(house) {
|
|
@@ -29,9 +30,7 @@ export function holdStream(handlers) {
|
|
|
29
30
|
if (socket === opening)
|
|
30
31
|
socket = null;
|
|
31
32
|
process.stderr.write(`kit: control stream closed ${event.code} ${event.reason}\n`);
|
|
32
|
-
const delay = opened
|
|
33
|
-
? 1000 + Math.random() * 2000
|
|
34
|
-
: Math.min(30_000, 1000 * 2 ** failures++);
|
|
33
|
+
const delay = opened ? 1000 + Math.random() * 2000 : retryDelay(failures++);
|
|
35
34
|
setTimeout(() => void connect(), delay);
|
|
36
35
|
};
|
|
37
36
|
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { chmod, mkdir, mkdtemp, readdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
|
4
|
+
import { availableParallelism, tmpdir } from 'node:os';
|
|
5
|
+
import { basename, join } from 'node:path';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { unlessAborted } from './abort.js';
|
|
8
|
+
import { failureOf, run } from './clis.js';
|
|
9
|
+
import { kitHome } from './home.js';
|
|
10
|
+
const RELEASE = 'https://github.com/agentshouse/kit/releases/download/engine-2';
|
|
11
|
+
export const MODEL = {
|
|
12
|
+
url: 'https://huggingface.co/ggml-org/parakeet-GGUF/resolve/35156454d1a39de06863303dd209fd2bed6ee079/ggml-parakeet-tdt-0.6b-v3-q4_k.bin',
|
|
13
|
+
sha256: '8b205b8b39c6535e153de6fb11c51db46125d45c4f16ba496fe41a0fe71b885e',
|
|
14
|
+
executable: false,
|
|
15
|
+
};
|
|
16
|
+
export const ENGINES = {
|
|
17
|
+
'linux-x64': {
|
|
18
|
+
decoder: { url: `${RELEASE}/ffmpeg-linux-x86_64`, sha256: '1f8235d206428305afb89c6e8ab99d100425a0ba61e22bb4c7aff017db8c8de5', executable: true },
|
|
19
|
+
engine: { url: `${RELEASE}/parakeet-cli-linux-x86_64`, sha256: 'bb2585695992fe1927a33960002cdf51b8a90a96ad65593c3d0a9f1fbc0865c2', executable: true },
|
|
20
|
+
},
|
|
21
|
+
'linux-arm64': {
|
|
22
|
+
decoder: { url: `${RELEASE}/ffmpeg-linux-aarch64`, sha256: 'a7e08a0fae0179d1078312f6bb13d2a9aa4d4c71a1d82a022953f25dee452f5e', executable: true },
|
|
23
|
+
engine: { url: `${RELEASE}/parakeet-cli-linux-aarch64`, sha256: 'cbc0bedc8daae97f1ef9dcee4200c06226218e300e0fd62ead1306d22742b7ed', executable: true },
|
|
24
|
+
},
|
|
25
|
+
'darwin-arm64': {
|
|
26
|
+
decoder: { url: `${RELEASE}/ffmpeg-darwin-arm64`, sha256: '7abe154aaed3d6b49f5167ca7221bb66bed51388925d99a7e4261a41fe52fba6', executable: true },
|
|
27
|
+
engine: { url: `${RELEASE}/parakeet-cli-darwin-arm64`, sha256: '9a2aafc0922ad7c0b6db6eff1641e96572ba32ac40bf995a1df242ed2febf297', executable: true },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const CHUNK_SECONDS = 20;
|
|
31
|
+
let held = Promise.resolve();
|
|
32
|
+
function directory() {
|
|
33
|
+
return join(kitHome(), 'transcription');
|
|
34
|
+
}
|
|
35
|
+
function installed(artifact) {
|
|
36
|
+
return join(directory(), artifact.sha256);
|
|
37
|
+
}
|
|
38
|
+
async function executed(command, args, signal) {
|
|
39
|
+
const ran = await run(command, args, 0, false, signal);
|
|
40
|
+
if (ran.status !== 0)
|
|
41
|
+
throw new Error(failureOf(ran, basename(command)));
|
|
42
|
+
return ran.stdout;
|
|
43
|
+
}
|
|
44
|
+
function present(path) {
|
|
45
|
+
return stat(path).then(() => true, () => false);
|
|
46
|
+
}
|
|
47
|
+
async function cpus() {
|
|
48
|
+
const [quota, period] = (await readFile('/sys/fs/cgroup/cpu.max', 'utf8').catch(() => 'max')).split(' ');
|
|
49
|
+
const allowed = quota === 'max' ? Infinity : Math.ceil(Number(quota) / Number(period));
|
|
50
|
+
return Math.min(availableParallelism(), allowed);
|
|
51
|
+
}
|
|
52
|
+
async function sha256(path) {
|
|
53
|
+
const hash = createHash('sha256');
|
|
54
|
+
await pipeline(createReadStream(path), hash);
|
|
55
|
+
return hash.digest('hex');
|
|
56
|
+
}
|
|
57
|
+
async function install(artifacts, signal) {
|
|
58
|
+
await mkdir(directory(), { recursive: true });
|
|
59
|
+
const pinned = new Set(artifacts.map((artifact) => artifact.sha256));
|
|
60
|
+
for (const name of await readdir(directory())) {
|
|
61
|
+
if (!pinned.has(name))
|
|
62
|
+
await rm(join(directory(), name), { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
const missing = [];
|
|
65
|
+
for (const artifact of artifacts)
|
|
66
|
+
if (!(await present(installed(artifact))))
|
|
67
|
+
missing.push(artifact);
|
|
68
|
+
try {
|
|
69
|
+
for (const artifact of missing) {
|
|
70
|
+
const part = `${installed(artifact)}.part`;
|
|
71
|
+
await executed('curl', ['-fsSL', '--retry', '3', '-o', part, artifact.url], signal);
|
|
72
|
+
if ((await sha256(part)) !== artifact.sha256)
|
|
73
|
+
throw new Error(`${artifact.url} does not match its sha256`);
|
|
74
|
+
}
|
|
75
|
+
for (const artifact of missing) {
|
|
76
|
+
if (artifact.executable)
|
|
77
|
+
await chmod(`${installed(artifact)}.part`, 0o755);
|
|
78
|
+
await rename(`${installed(artifact)}.part`, installed(artifact));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
for (const artifact of missing)
|
|
83
|
+
await rm(`${installed(artifact)}.part`, { force: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function transcribed(path, signal) {
|
|
87
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
88
|
+
const pinned = ENGINES[platform];
|
|
89
|
+
if (pinned === undefined)
|
|
90
|
+
throw new Error(`no transcription engine is pinned for ${platform}`);
|
|
91
|
+
await install([pinned.decoder, pinned.engine, MODEL], signal);
|
|
92
|
+
const work = await mkdtemp(join(tmpdir(), 'kit-transcription-'));
|
|
93
|
+
try {
|
|
94
|
+
await executed(installed(pinned.decoder), ['-nostdin', '-hide_banner', '-loglevel', 'error', '-i', path, '-ar', '16000', '-ac', '1', '-c:a', 'pcm_s16le',
|
|
95
|
+
'-f', 'segment', '-segment_time', String(CHUNK_SECONDS), join(work, '%05d.wav')], signal);
|
|
96
|
+
const parts = (await readdir(work)).sort().flatMap((part) => ['-f', join(work, part)]);
|
|
97
|
+
const text = await executed(installed(pinned.engine), ['-t', String(await cpus()), '-np', '-m', installed(MODEL), ...parts], signal);
|
|
98
|
+
return text
|
|
99
|
+
.split('\n')
|
|
100
|
+
.map((line) => line.trim())
|
|
101
|
+
.filter((line) => line !== '')
|
|
102
|
+
.join(' ');
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await rm(work, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function transcribe(path, signal) {
|
|
109
|
+
const before = held;
|
|
110
|
+
let release;
|
|
111
|
+
const mine = new Promise((resolve) => {
|
|
112
|
+
release = resolve;
|
|
113
|
+
});
|
|
114
|
+
held = Promise.all([before, mine]);
|
|
115
|
+
try {
|
|
116
|
+
await unlessAborted(before, signal);
|
|
117
|
+
return await transcribed(path, signal);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
release();
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { run } from './clis.js';
|
|
7
|
+
import { kitHome } from './home.js';
|
|
8
|
+
const RECORD = 'outcome=$1; shift; bash "$0" --linux "$@"; printf %s "$?" > "$outcome"; systemctl start house-kit.service';
|
|
9
|
+
function logged(error) {
|
|
10
|
+
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
|
+
}
|
|
12
|
+
function directory() {
|
|
13
|
+
return join(kitHome(), 'update');
|
|
14
|
+
}
|
|
15
|
+
function pending() {
|
|
16
|
+
return join(directory(), 'pending.json');
|
|
17
|
+
}
|
|
18
|
+
function outcome() {
|
|
19
|
+
return join(directory(), 'outcome');
|
|
20
|
+
}
|
|
21
|
+
function unit(inputId) {
|
|
22
|
+
return `house-kit-update-${inputId}`;
|
|
23
|
+
}
|
|
24
|
+
async function pendingInput() {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(await readFile(pending(), 'utf8')).input_id;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function recorded() {
|
|
33
|
+
const status = await readFile(outcome(), 'utf8').catch(() => null);
|
|
34
|
+
if (status === null)
|
|
35
|
+
return null;
|
|
36
|
+
return status.trim() === '0' ? {} : { refused: `the Kit bootstrap exited with ${status.trim()}` };
|
|
37
|
+
}
|
|
38
|
+
async function finished(inputId) {
|
|
39
|
+
for (;;) {
|
|
40
|
+
const ack = await recorded();
|
|
41
|
+
if (ack !== null)
|
|
42
|
+
return ack;
|
|
43
|
+
if ((await run('systemctl', ['is-active', '--quiet', unit(inputId)], 10_000)).status !== 0) {
|
|
44
|
+
return (await recorded()) ?? { refused: 'the Kit bootstrap did not finish' };
|
|
45
|
+
}
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function rerun(script, inputId, flags) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const child = spawn('systemd-run', [
|
|
52
|
+
'--wait',
|
|
53
|
+
'--collect',
|
|
54
|
+
'--quiet',
|
|
55
|
+
`--unit=${unit(inputId)}`,
|
|
56
|
+
`--setenv=HOME=${homedir()}`,
|
|
57
|
+
'/bin/sh',
|
|
58
|
+
'-c',
|
|
59
|
+
RECORD,
|
|
60
|
+
script,
|
|
61
|
+
outcome(),
|
|
62
|
+
...flags,
|
|
63
|
+
], { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
64
|
+
child.on('error', () => resolve({ refused: 'no service manager runs this Kit' }));
|
|
65
|
+
child.on('close', () => resolve(null));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export class Updates {
|
|
69
|
+
house;
|
|
70
|
+
held = new Set();
|
|
71
|
+
queue = Promise.resolve();
|
|
72
|
+
constructor(house) {
|
|
73
|
+
this.house = house;
|
|
74
|
+
}
|
|
75
|
+
async resumed() {
|
|
76
|
+
const inputId = await pendingInput();
|
|
77
|
+
if (inputId === null)
|
|
78
|
+
return;
|
|
79
|
+
await this.acknowledge(inputId, await finished(inputId));
|
|
80
|
+
}
|
|
81
|
+
start(input) {
|
|
82
|
+
if (this.held.has(input.input_id))
|
|
83
|
+
return;
|
|
84
|
+
this.held.add(input.input_id);
|
|
85
|
+
this.queue = this.queue
|
|
86
|
+
.then(() => this.carry(input))
|
|
87
|
+
.then((ack) => this.acknowledge(input.input_id, ack))
|
|
88
|
+
.catch(logged)
|
|
89
|
+
.finally(() => this.held.delete(input.input_id));
|
|
90
|
+
}
|
|
91
|
+
async carry(input) {
|
|
92
|
+
if ((await pendingInput()) === input.input_id)
|
|
93
|
+
return finished(input.input_id);
|
|
94
|
+
const answer = await fetch(input.bootstrap);
|
|
95
|
+
if (!answer.ok)
|
|
96
|
+
return { refused: `the Kit bootstrap could not be downloaded: ${answer.status}` };
|
|
97
|
+
const script = Buffer.from(await answer.arrayBuffer());
|
|
98
|
+
if (createHash('sha256').update(script).digest('hex') !== input.sha256) {
|
|
99
|
+
return { refused: 'the Kit bootstrap does not match its sha256' };
|
|
100
|
+
}
|
|
101
|
+
await mkdir(directory(), { recursive: true, mode: 0o700 });
|
|
102
|
+
const path = join(directory(), 'connect-linux.sh');
|
|
103
|
+
await writeFile(path, script, { mode: 0o700 });
|
|
104
|
+
await rm(outcome(), { force: true });
|
|
105
|
+
await writeFile(pending(), `${JSON.stringify({ input_id: input.input_id })}\n`, { mode: 0o600 });
|
|
106
|
+
return (await rerun(path, input.input_id, input.update_clis ? ['--update-clis'] : [])) ?? (await recorded()) ?? {
|
|
107
|
+
refused: 'the Kit bootstrap did not finish',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async acknowledge(inputId, ack) {
|
|
111
|
+
await this.house.deliver(`/kit/inputs/${inputId}/ack`, ack);
|
|
112
|
+
if ((await pendingInput()) !== inputId)
|
|
113
|
+
return;
|
|
114
|
+
await rm(pending(), { force: true });
|
|
115
|
+
await rm(outcome(), { force: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
package/how-we-work.md
CHANGED
|
@@ -2,24 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
A Room is where information people share is kept; it is not the place for
|
|
4
4
|
private information. Work goes into the Room it belongs to, or the First Room
|
|
5
|
-
when none is named.
|
|
5
|
+
when none is named.
|
|
6
6
|
|
|
7
7
|
The skills are already set up; never run `setup-matt-pocock-skills`.
|
|
8
8
|
|
|
9
9
|
## Where skill output goes
|
|
10
10
|
|
|
11
|
-
- Glossary (`CONTEXT.md`): `
|
|
12
|
-
- Decisions (ADRs): `
|
|
13
|
-
- Plan (spec): `
|
|
14
|
-
- Tasks (tickets): `
|
|
11
|
+
- Glossary (`CONTEXT.md`): `glossary.md`
|
|
12
|
+
- Decisions (ADRs): `decisions/NNNN-<slug>.md`
|
|
13
|
+
- Plan (spec): `projects/<name>/plan.md`
|
|
14
|
+
- Tasks (tickets): `projects/<name>/tasks/NN-<slug>.md`, with a
|
|
15
15
|
`Status:` line (`needs-triage`, `needs-info`, `ready-for-agent`,
|
|
16
16
|
`ready-for-human`, `wontfix`), a `Blocked by:` line and comments under
|
|
17
17
|
`## Comments`
|
|
18
|
-
- Map (Wayfinding): `
|
|
18
|
+
- Map (Wayfinding): `projects/<name>/map.md`, its tasks under
|
|
19
19
|
`tasks/` with `Type:`, `Status: claimed|resolved` and `Blocked by:` lines
|
|
20
|
-
- Research: `
|
|
21
|
-
- Questionnaires: `
|
|
22
|
-
- Learning: `
|
|
23
|
-
-
|
|
24
|
-
linked from the document they belong to; a prototype is one HTML file with
|
|
25
|
-
every variant on one page.
|
|
20
|
+
- Research: `research/<slug>.md`
|
|
21
|
+
- Questionnaires: `questionnaires/<slug>.md`
|
|
22
|
+
- Learning: `learning/<topic>/`
|
|
23
|
+
- Prototype: one HTML file with every variant on one page.
|
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-c5262a8cc063d",
|
|
4
4
|
"description": "House Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc -p tsconfig.build.json && node scripts/bootstrap-manifest.mjs",
|
|
25
|
+
"engine": "mkdir -p tmp/engine && docker run --rm -v \"$PWD:/kit\" -w /kit alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 sh scripts/engine.sh tmp/engine",
|
|
25
26
|
"test": "tsc -p tsconfig.json && vitest run"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@agentclientprotocol/sdk": "1.7.0",
|
|
29
|
-
"@agentshouse/mdmodel": "0.1.0-alpha.
|
|
30
|
+
"@agentshouse/mdmodel": "0.1.0-alpha.19",
|
|
30
31
|
"yaml": "2.9.1"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"@agentclientprotocol/claude-agent-acp": "0.85.1",
|
|
34
|
-
"@agentclientprotocol/codex-acp": "2.1.1"
|
|
35
|
-
"@xai-official/grok": "1.0.46"
|
|
35
|
+
"@agentclientprotocol/codex-acp": "2.1.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@agentclientprotocol/claude-agent-acp": {
|
|
@@ -40,9 +40,6 @@
|
|
|
40
40
|
},
|
|
41
41
|
"@agentclientprotocol/codex-acp": {
|
|
42
42
|
"optional": true
|
|
43
|
-
},
|
|
44
|
-
"@xai-official/grok": {
|
|
45
|
-
"optional": true
|
|
46
43
|
}
|
|
47
44
|
},
|
|
48
45
|
"devDependencies": {
|