@agentshouse/kit 0.2.1-c39de3ed26c1b → 0.2.1-c3db1f18aed8f
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 +2 -2
- package/dist/agents.js +1 -0
- package/dist/bootstrap.json +4 -4
- package/dist/chatgpt.js +2 -1
- package/dist/clis.js +3 -2
- package/dist/home.js +38 -4
- package/dist/house-main.js +50 -19
- package/dist/instructions.js +2 -27
- package/dist/login.js +8 -9
- package/dist/skills.js +80 -12
- package/dist/transcription.js +3 -3
- package/dist/update.js +1 -0
- package/how-we-work.md +11 -3
- package/package.json +2 -1
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ them out; running it again without the flag brings them back.
|
|
|
33
33
|
|
|
34
34
|
`kit login` opens or prints a link you confirm in any browser, on this computer
|
|
35
35
|
or another, continues on its own and keeps the Environment's credential in
|
|
36
|
-
`~/.house-kit`, or in
|
|
36
|
+
`$HOUSE_KIT_HOME` when it is set, else in `~/.house-kit`, or in `house-kit-<uid>` under the temporary directory when your home is read-only. `--environment <id>`
|
|
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
|
|
@@ -52,7 +52,7 @@ both, without `--owner`. `house upload` and `house download` move a Room's other
|
|
|
52
52
|
|
|
53
53
|
House Kit turns each audio file of your message to an Agent into text on this
|
|
54
54
|
computer before the Agent reads it. While transcription is on, the speech
|
|
55
|
-
engine and model use about 425 MB
|
|
55
|
+
engine and model use about 425 MB beside the Environment's credential; turning it off deletes them.
|
|
56
56
|
|
|
57
57
|
## License
|
|
58
58
|
|
package/dist/agents.js
CHANGED
|
@@ -277,6 +277,7 @@ export class Agents {
|
|
|
277
277
|
if (known !== undefined && known.failure === null && known.minimum === undefined)
|
|
278
278
|
return;
|
|
279
279
|
await this.examine(kind, SIGNED_OUT, offer);
|
|
280
|
+
await this.report();
|
|
280
281
|
}
|
|
281
282
|
async send() {
|
|
282
283
|
const agents = this.desired.agents.flatMap((kind) => {
|
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-c3db1f18aed8f",
|
|
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-c3db1f18aed8f/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-c3db1f18aed8f/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-c3db1f18aed8f/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/chatgpt.js
CHANGED
|
@@ -20,6 +20,7 @@ export async function chatgpt(argv) {
|
|
|
20
20
|
const house = values.house ?? (await readOwnAgent())?.house ?? DEFAULT_HOUSE;
|
|
21
21
|
if (!(await accepted(house, values.key)))
|
|
22
22
|
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
23
|
-
|
|
23
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
24
|
+
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
24
25
|
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
25
26
|
}
|
package/dist/clis.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { constants, readFileSync } from 'node:fs';
|
|
3
4
|
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { homedir, userInfo } from 'node:os';
|
|
@@ -54,7 +55,7 @@ export async function allowHouseInCodex(path) {
|
|
|
54
55
|
houses.push(house);
|
|
55
56
|
}
|
|
56
57
|
await mkdir(rules, { recursive: true });
|
|
57
|
-
const temporary = `${file}.${
|
|
58
|
+
const temporary = `${file}.${randomUUID()}`;
|
|
58
59
|
await writeFile(temporary, houseRule(houses));
|
|
59
60
|
await rename(temporary, file);
|
|
60
61
|
}
|
|
@@ -199,7 +200,7 @@ export async function installAdapter(kind, signal) {
|
|
|
199
200
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
200
201
|
if ((await installedAdapter(kind)) === release)
|
|
201
202
|
return null;
|
|
202
|
-
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
203
|
+
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--cache', join(kitHome(), 'npm-cache'), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
203
204
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
204
205
|
}
|
|
205
206
|
export async function installCli(kind, signal) {
|
package/dist/home.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { accessSync, chmodSync, constants, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
2
3
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
6
|
import { selectRelay } from './relay.js';
|
|
6
7
|
const ENROLMENT = 'credential.json';
|
|
@@ -8,8 +9,41 @@ const DISCONNECTED = 'disconnected.json';
|
|
|
8
9
|
const CONFIGURATION = 'kit.json';
|
|
9
10
|
const OWN_AGENT = 'own-agent.json';
|
|
10
11
|
const RESTART = 'restart';
|
|
12
|
+
let defaulted;
|
|
13
|
+
function writable(directory) {
|
|
14
|
+
try {
|
|
15
|
+
accessSync(directory, constants.W_OK | constants.X_OK);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function ownTemporaryHome() {
|
|
23
|
+
const home = join(tmpdir(), `house-kit-${process.getuid()}`);
|
|
24
|
+
try {
|
|
25
|
+
mkdirSync(home, { mode: 0o700 });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.code !== 'EEXIST')
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const found = lstatSync(home);
|
|
32
|
+
if (found.isSymbolicLink())
|
|
33
|
+
throw new Error(`${home} is a symbolic link`);
|
|
34
|
+
if (!found.isDirectory())
|
|
35
|
+
throw new Error(`${home} is not a directory`);
|
|
36
|
+
if (found.uid !== process.getuid())
|
|
37
|
+
throw new Error(`${home} is not owned by this User`);
|
|
38
|
+
chmodSync(home, 0o700);
|
|
39
|
+
return home;
|
|
40
|
+
}
|
|
41
|
+
function defaultHome() {
|
|
42
|
+
const home = join(homedir(), '.house-kit');
|
|
43
|
+
return existsSync(home) || writable(homedir()) ? home : ownTemporaryHome();
|
|
44
|
+
}
|
|
11
45
|
export function kitHome() {
|
|
12
|
-
return process.env.HOUSE_KIT_HOME
|
|
46
|
+
return process.env.HOUSE_KIT_HOME || (defaulted ??= defaultHome());
|
|
13
47
|
}
|
|
14
48
|
export function workspaceRoot() {
|
|
15
49
|
return process.env.HOUSE_KIT_WORKSPACE ?? '/agents/house';
|
|
@@ -30,7 +64,7 @@ export async function readHome(name) {
|
|
|
30
64
|
export async function writeHome(name, value) {
|
|
31
65
|
const home = kitHome();
|
|
32
66
|
await mkdir(home, { recursive: true, mode: 0o700 });
|
|
33
|
-
const temporary = join(home, `${name}.${
|
|
67
|
+
const temporary = join(home, `${name}.${randomUUID()}`);
|
|
34
68
|
await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
35
69
|
await rename(temporary, join(home, name));
|
|
36
70
|
}
|
package/dist/house-main.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
5
|
-
import { dirname, extname, join, resolve } from 'node:path';
|
|
5
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
6
6
|
import { verified } from './files.js';
|
|
7
7
|
import { HOUSE_REF } from './git.js';
|
|
8
|
-
import { kitHome,
|
|
8
|
+
import { kitHome, readOwnAgent } from './home.js';
|
|
9
9
|
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
10
10
|
import { refusalOf } from './refusals.js';
|
|
11
|
-
import { relayed } from './relay.js';
|
|
11
|
+
import { relayed, selectRelay } from './relay.js';
|
|
12
12
|
import { parse, stringify } from 'yaml';
|
|
13
13
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
14
14
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
@@ -23,6 +23,7 @@ const SANDBOXED = "your CLI's sandbox kept house from House; run house directly,
|
|
|
23
23
|
const REVOKED = 'this conversation no longer has House access; nothing to do from here';
|
|
24
24
|
const UNCONNECTED = "this computer's own agent has no House connection; run kit login";
|
|
25
25
|
const DISCONNECTED = "House no longer accepts this computer's own agent; run kit login";
|
|
26
|
+
const UNCONFIRMED = 'with no result; a write may or may not have taken effect, so read before you repeat it';
|
|
26
27
|
const socketPath = process.env.HOUSE_BRIDGE;
|
|
27
28
|
const MEDIA_TYPES = new Map([
|
|
28
29
|
['avif', 'image/avif'],
|
|
@@ -51,7 +52,7 @@ const MEDIA_TYPES = new Map([
|
|
|
51
52
|
['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
52
53
|
['zip', 'application/zip'],
|
|
53
54
|
]);
|
|
54
|
-
function refused(path, method, status, text) {
|
|
55
|
+
function refused(path, method, status, text, local) {
|
|
55
56
|
const refusal = refusalOf(text);
|
|
56
57
|
if (refusal !== null)
|
|
57
58
|
return refusal.text;
|
|
@@ -60,9 +61,12 @@ function refused(path, method, status, text) {
|
|
|
60
61
|
const act = method === 'GET' ? 'download' : 'upload';
|
|
61
62
|
if (URL.canParse(path))
|
|
62
63
|
return `the ${act} answered ${status}; ${act} the file again`;
|
|
63
|
-
if (status === 502 &&
|
|
64
|
+
if (status === 502 && local)
|
|
64
65
|
return text;
|
|
65
|
-
return `${path} answered ${status}`;
|
|
66
|
+
return path === '/' ? unconfirmed(status) : `${path} answered ${status}`;
|
|
67
|
+
}
|
|
68
|
+
function unconfirmed(status) {
|
|
69
|
+
return `House answered ${status} ${UNCONFIRMED}`;
|
|
66
70
|
}
|
|
67
71
|
function exchanged(path, method, body, headers = { 'content-type': 'application/json' }, to = socketPath) {
|
|
68
72
|
return new Promise((resolve, reject) => {
|
|
@@ -74,7 +78,7 @@ function exchanged(path, method, body, headers = { 'content-type': 'application/
|
|
|
74
78
|
if (answer.statusCode === 200)
|
|
75
79
|
resolve(bytes);
|
|
76
80
|
else
|
|
77
|
-
reject(new Error(refused(path, method, answer.statusCode, bytes.toString('utf8'))));
|
|
81
|
+
reject(new Error(refused(path, method, answer.statusCode, bytes.toString('utf8'), answer.headers['content-type'] === undefined)));
|
|
78
82
|
});
|
|
79
83
|
});
|
|
80
84
|
sent.on('error', (error) => reject(new Error(error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
@@ -88,14 +92,14 @@ async function direct(message) {
|
|
|
88
92
|
const ownAgent = await readOwnAgent();
|
|
89
93
|
if (ownAgent === null)
|
|
90
94
|
throw new Error(UNCONNECTED);
|
|
91
|
-
|
|
95
|
+
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
92
96
|
setGlobalProxyFromEnv();
|
|
93
97
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
94
98
|
throw new Error(UNREACHABLE);
|
|
95
99
|
});
|
|
96
100
|
if (answer.status === 200)
|
|
97
101
|
return answer.text;
|
|
98
|
-
throw new Error(answer.status === 401 ? DISCONNECTED : (refusalOf(answer.text)?.text ??
|
|
102
|
+
throw new Error(answer.status === 401 ? DISCONNECTED : (refusalOf(answer.text)?.text ?? unconfirmed(answer.status)));
|
|
99
103
|
}
|
|
100
104
|
async function mcp(method, params) {
|
|
101
105
|
const message = rpc(method, params);
|
|
@@ -127,7 +131,7 @@ async function transferred(url, method, bytes, headers) {
|
|
|
127
131
|
const received = Buffer.from(await answer.arrayBuffer());
|
|
128
132
|
if (answer.status === 200)
|
|
129
133
|
return received;
|
|
130
|
-
throw new Error(refused(url, method, answer.status, received.toString('utf8')));
|
|
134
|
+
throw new Error(refused(url, method, answer.status, received.toString('utf8'), false));
|
|
131
135
|
}
|
|
132
136
|
async function sent(url, bytes, operation) {
|
|
133
137
|
const headers = { 'content-type': 'application/octet-stream', 'x-house-byte-operation': operation };
|
|
@@ -149,23 +153,50 @@ async function download(args) {
|
|
|
149
153
|
await writeFile(written, bytes);
|
|
150
154
|
return written;
|
|
151
155
|
}
|
|
156
|
+
async function described(file) {
|
|
157
|
+
const bytes = await localFile(file);
|
|
158
|
+
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
159
|
+
return {
|
|
160
|
+
bytes,
|
|
161
|
+
description: {
|
|
162
|
+
bytes: bytes.length,
|
|
163
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
164
|
+
...(mediaType === undefined ? {} : { media_type: mediaType }),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
152
168
|
async function upload({ file, ...destination }) {
|
|
153
169
|
if (typeof file !== 'string' || file === '')
|
|
154
170
|
throw new Error('upload needs "file", a local file');
|
|
155
|
-
const bytes = await
|
|
156
|
-
const
|
|
157
|
-
const answer = await called('upload', {
|
|
158
|
-
...destination,
|
|
159
|
-
bytes: bytes.length,
|
|
160
|
-
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
161
|
-
...(mediaType === undefined ? {} : { media_type: mediaType }),
|
|
162
|
-
});
|
|
171
|
+
const { bytes, description } = await described(file);
|
|
172
|
+
const answer = await called('upload', { ...destination, ...description });
|
|
163
173
|
const declared = parse(answer);
|
|
164
174
|
if (declared.upload === undefined)
|
|
165
175
|
return answer;
|
|
166
176
|
const written = JSON.parse(await sent(declared.upload.url, bytes, declared.upload.operation));
|
|
167
177
|
return stringify(written, { lineWidth: 0 }).trimEnd();
|
|
168
178
|
}
|
|
179
|
+
async function appendRecord(args) {
|
|
180
|
+
const held = new Map();
|
|
181
|
+
const attachments = Array.isArray(args.attachments)
|
|
182
|
+
? await Promise.all(args.attachments.map(async (attachment) => {
|
|
183
|
+
if (typeof attachment !== 'string')
|
|
184
|
+
return attachment;
|
|
185
|
+
const { bytes, description } = await described(attachment);
|
|
186
|
+
held.set(description.sha256, bytes);
|
|
187
|
+
return { name: basename(attachment), ...description };
|
|
188
|
+
}))
|
|
189
|
+
: args.attachments;
|
|
190
|
+
const record = { ...args, attachments };
|
|
191
|
+
for (;;) {
|
|
192
|
+
const answer = await called('append_record', record);
|
|
193
|
+
const { upload } = parse(answer);
|
|
194
|
+
if (upload === undefined)
|
|
195
|
+
return answer;
|
|
196
|
+
for (const grant of upload)
|
|
197
|
+
await sent(grant.url, held.get(grant.sha256), grant.operation);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
169
200
|
function unexpected(argument, usage) {
|
|
170
201
|
return new Error(`unexpected argument ${argument}; ${usage}`);
|
|
171
202
|
}
|
|
@@ -233,7 +264,7 @@ async function house(args) {
|
|
|
233
264
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
234
265
|
}
|
|
235
266
|
const parsed = argumentsOf(argument);
|
|
236
|
-
return verb === 'upload' ? upload(parsed) : called(verb, parsed);
|
|
267
|
+
return verb === 'upload' ? upload(parsed) : verb === 'append_record' ? appendRecord(parsed) : called(verb, parsed);
|
|
237
268
|
}
|
|
238
269
|
async function called(name, args) {
|
|
239
270
|
const result = await mcp('tools/call', { name, arguments: args });
|
package/dist/instructions.js
CHANGED
|
@@ -1,36 +1,11 @@
|
|
|
1
1
|
import { HOUSE_REF } from './git.js';
|
|
2
|
-
import {
|
|
2
|
+
import { readHowWeWork } from './skills.js';
|
|
3
3
|
const SOURCE_LINE = "From House:";
|
|
4
4
|
const HOUSE_LINE = "Work with House through the `house` CLI: run `house --help`, and `house find_command` for House's guides and formats.";
|
|
5
5
|
const FILE_LINE = 'To give the User a file, upload it with `house upload` and link it; for an App, a Site or a website, follow `house find_command \'{"query":"app"}\'`. Never start a server.';
|
|
6
6
|
const READ_LINE = "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.";
|
|
7
7
|
const COPIES_LINE = `House Local copies; before work run \`house fetch\` and rebase onto \`${HOUSE_REF}\`; after it commit, then \`house push\`:`;
|
|
8
|
-
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
9
|
-
const FAILED = /^exit: [1-9]\d*$/;
|
|
10
|
-
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
11
|
-
function cutOf(lines) {
|
|
12
|
-
const cut = CUT.exec(lines[0] ?? '');
|
|
13
|
-
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
14
|
-
}
|
|
15
|
-
async function howWeWork(bridge) {
|
|
16
|
-
let document = '';
|
|
17
|
-
for (let command = `cat ${HOW_WE_WORK}`; command !== undefined;) {
|
|
18
|
-
const read = await bridge.tool('shell', { command });
|
|
19
|
-
const text = read.content.map((content) => content.text).join('\n');
|
|
20
|
-
const lines = text.split('\n');
|
|
21
|
-
if (read.isError === true || (FAILED.test(lines[0] ?? '') && lines[1]?.startsWith('stderr: '))) {
|
|
22
|
-
const refusal = read.isError === true ? text : lines.slice(1).join('\n').replace(/^stderr: /gm, '');
|
|
23
|
-
if (UNREADABLE.test(refusal))
|
|
24
|
-
return '';
|
|
25
|
-
throw new Error(refusal);
|
|
26
|
-
}
|
|
27
|
-
const cut = cutOf(lines);
|
|
28
|
-
document += lines.slice(cut === null ? 0 : 1).join('\n');
|
|
29
|
-
command = cut?.[2];
|
|
30
|
-
}
|
|
31
|
-
return document.replace(/\n$/, '');
|
|
32
|
-
}
|
|
33
8
|
export async function instructions(bridge, base, copies) {
|
|
34
9
|
const lines = [SOURCE_LINE, HOUSE_LINE, FILE_LINE, READ_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
|
|
35
|
-
return [lines.join('\n'), base, await
|
|
10
|
+
return [lines.join('\n'), base, (await readHowWeWork(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
36
11
|
}
|
package/dist/login.js
CHANGED
|
@@ -96,28 +96,27 @@ export async function login(argv) {
|
|
|
96
96
|
open(started.start_url);
|
|
97
97
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
98
98
|
}
|
|
99
|
-
|
|
100
|
-
await placeSkillSet();
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
104
|
-
}
|
|
99
|
+
await placeSkillSet();
|
|
105
100
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
101
|
+
const relayRecord = relay === undefined ? {} : { relay };
|
|
106
102
|
await writeEnrolment({
|
|
107
103
|
house,
|
|
108
|
-
...
|
|
104
|
+
...relayRecord,
|
|
109
105
|
environment: exchanged.environment,
|
|
110
106
|
credential: exchanged.credential,
|
|
111
107
|
});
|
|
112
108
|
process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
|
|
113
109
|
if (exchanged.headless_credential !== undefined) {
|
|
114
|
-
await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
|
|
110
|
+
await writeOwnAgent({ house, ...relayRecord, user: exchanged.user, credential: exchanged.headless_credential });
|
|
115
111
|
}
|
|
116
112
|
else if (held && ownAgent !== null && ownAgent.user !== exchanged.user) {
|
|
117
113
|
await forgetOwnAgent();
|
|
118
114
|
process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
|
|
119
115
|
}
|
|
120
|
-
|
|
116
|
+
else if (held && ownAgent !== null) {
|
|
117
|
+
await writeOwnAgent({ house, ...relayRecord, user: ownAgent.user, credential: ownAgent.credential });
|
|
118
|
+
}
|
|
119
|
+
await placeAppGit(ownAgent, await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
121
120
|
try {
|
|
122
121
|
startService();
|
|
123
122
|
}
|
package/dist/skills.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { cp, lstat, mkdir, readdir, realpath, rm, symlink
|
|
3
|
+
import { cp, lstat, mkdir, readdir, realpath, rm, symlink } from 'node:fs/promises';
|
|
3
4
|
import { homedir } from 'node:os';
|
|
4
5
|
import { join, relative } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { parse } from 'yaml';
|
|
6
8
|
import { KIT_VERSION } from './clis.js';
|
|
7
9
|
import { kitHome, readConfiguration, readHome, writeHome } from './home.js';
|
|
8
10
|
import { refusalOf } from './refusals.js';
|
|
9
|
-
|
|
11
|
+
const HOW_WE_WORK = '/rooms/private/how-we-work.md';
|
|
10
12
|
const SKILL_SET = fileURLToPath(new URL('../skills/', import.meta.url));
|
|
11
13
|
const HOW_WE_WORK_TEXT = readFileSync(new URL('../how-we-work.md', import.meta.url), 'utf8');
|
|
14
|
+
const SHIPPED_HOW_WE_WORK = [
|
|
15
|
+
'ae4dda60da7b174f22dc5fa9bf07fc27c25864932a74230de4f9f509ddd91f41',
|
|
16
|
+
'0bdcd43e1603ba6673a3cae56d70bbf90657cbeefd124e91a47be2ca1cbd6c93',
|
|
17
|
+
'4106a87ec66da6f7b2a10e74d18e6559d68d5f4fe7415dae68f5c7bdb9081968',
|
|
18
|
+
'ac814a2197038f037b657bb719340395f89f319ac9fcdd386629c3fe2cec68f1',
|
|
19
|
+
];
|
|
12
20
|
const RECORD = 'skill-set.json';
|
|
13
|
-
const
|
|
21
|
+
const WRITTEN = 'how-we-work.json';
|
|
14
22
|
const UNWRITABLE = new Set(['room_not_found', 'operation_denied']);
|
|
23
|
+
const PRESENT = new Set(['path_exists']);
|
|
24
|
+
const CHANGED = new Set(['base_revision_stale', 'path_absent']);
|
|
25
|
+
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
26
|
+
const FAILED = /^exit: [1-9]\d*$/;
|
|
27
|
+
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
15
28
|
function skillsDirectory() {
|
|
16
29
|
return join(homedir(), '.agents', 'skills');
|
|
17
30
|
}
|
|
@@ -49,6 +62,14 @@ async function install() {
|
|
|
49
62
|
await writeHome(RECORD, { version: KIT_VERSION, names });
|
|
50
63
|
}
|
|
51
64
|
export async function placeSkillSet() {
|
|
65
|
+
try {
|
|
66
|
+
await replaceSkillSet();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function replaceSkillSet() {
|
|
52
73
|
const accepted = (await readConfiguration()).skills;
|
|
53
74
|
const installed = await readHome(RECORD);
|
|
54
75
|
if (accepted && installed?.version === KIT_VERSION)
|
|
@@ -62,17 +83,64 @@ export async function placeSkillSet() {
|
|
|
62
83
|
else
|
|
63
84
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
64
85
|
}
|
|
86
|
+
function sha256(text) {
|
|
87
|
+
return createHash('sha256').update(text).digest('hex');
|
|
88
|
+
}
|
|
89
|
+
function cutOf(lines) {
|
|
90
|
+
const cut = CUT.exec(lines[0] ?? '');
|
|
91
|
+
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
92
|
+
}
|
|
93
|
+
export async function readHowWeWork(bridge) {
|
|
94
|
+
let document = '';
|
|
95
|
+
for (let command = `cat ${HOW_WE_WORK}`; command !== undefined;) {
|
|
96
|
+
const read = await bridge.tool('shell', { command });
|
|
97
|
+
const text = read.content.map((content) => content.text).join('\n');
|
|
98
|
+
const lines = text.split('\n');
|
|
99
|
+
if (read.isError === true || (FAILED.test(lines[0] ?? '') && lines[1]?.startsWith('stderr: '))) {
|
|
100
|
+
const refusal = read.isError === true ? text : lines.slice(1).join('\n').replace(/^stderr: /gm, '');
|
|
101
|
+
if (UNREADABLE.test(refusal))
|
|
102
|
+
return '';
|
|
103
|
+
throw new Error(refusal);
|
|
104
|
+
}
|
|
105
|
+
const cut = cutOf(lines);
|
|
106
|
+
document += lines.slice(cut === null ? 0 : 1).join('\n');
|
|
107
|
+
command = cut?.[2];
|
|
108
|
+
}
|
|
109
|
+
return document;
|
|
110
|
+
}
|
|
111
|
+
async function currentRevision(bridge) {
|
|
112
|
+
const inspected = await bridge.tool('inspect', { path: HOW_WE_WORK });
|
|
113
|
+
const text = inspected.content.map((content) => content.text).join('\n');
|
|
114
|
+
if (inspected.isError === true)
|
|
115
|
+
throw new Error(text);
|
|
116
|
+
return parse(text).revisions[0].revision;
|
|
117
|
+
}
|
|
118
|
+
async function edit(bridge, change, kept) {
|
|
119
|
+
const edited = await bridge.tool('edit', { changes: [{ ...change, path: HOW_WE_WORK, content: HOW_WE_WORK_TEXT }] });
|
|
120
|
+
if (edited.isError !== true)
|
|
121
|
+
return 'written';
|
|
122
|
+
const text = edited.content.map((content) => content.text).join('\n');
|
|
123
|
+
const refusal = refusalOf(text);
|
|
124
|
+
if (UNWRITABLE.has(refusal?.code ?? ''))
|
|
125
|
+
return 'unwritable';
|
|
126
|
+
if (refusal?.code === 'edit_conflict' && refusal.conflicts.some((conflict) => kept.has(conflict)))
|
|
127
|
+
return 'kept';
|
|
128
|
+
throw new Error(text);
|
|
129
|
+
}
|
|
65
130
|
export async function createHowWeWork(bridge) {
|
|
66
|
-
if ((await
|
|
131
|
+
if (!(await readConfiguration()).skills || (await readHome(RECORD))?.version === undefined)
|
|
132
|
+
return;
|
|
133
|
+
const current = sha256(HOW_WE_WORK_TEXT);
|
|
134
|
+
if ((await readHome(WRITTEN)) === current)
|
|
135
|
+
return;
|
|
136
|
+
const created = await edit(bridge, { op: 'create' }, PRESENT);
|
|
137
|
+
if (created === 'unwritable')
|
|
67
138
|
return;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
if (UNWRITABLE.has(refusal?.code ?? ''))
|
|
139
|
+
if (created === 'kept') {
|
|
140
|
+
const base = await currentRevision(bridge);
|
|
141
|
+
const held = sha256(await readHowWeWork(bridge));
|
|
142
|
+
if (held !== current && SHIPPED_HOW_WE_WORK.includes(held) && (await edit(bridge, { op: 'replace', base }, CHANGED)) === 'unwritable')
|
|
73
143
|
return;
|
|
74
|
-
if (refusal?.code !== 'edit_conflict' || !refusal.conflicts.includes('path_exists'))
|
|
75
|
-
throw new Error(text);
|
|
76
144
|
}
|
|
77
|
-
await
|
|
145
|
+
await writeHome(WRITTEN, current);
|
|
78
146
|
}
|
package/dist/transcription.js
CHANGED
|
@@ -49,10 +49,10 @@ async function executed(command, args, signal) {
|
|
|
49
49
|
function present(path) {
|
|
50
50
|
return stat(path).then(() => true, () => false);
|
|
51
51
|
}
|
|
52
|
-
async function
|
|
52
|
+
async function threads() {
|
|
53
53
|
const [quota, period] = (await readFile('/sys/fs/cgroup/cpu.max', 'utf8').catch(() => 'max')).split(' ');
|
|
54
54
|
const allowed = quota === 'max' ? Infinity : Math.ceil(Number(quota) / Number(period));
|
|
55
|
-
return Math.min(availableParallelism(), allowed);
|
|
55
|
+
return Math.max(1, Math.floor(Math.min(availableParallelism(), allowed) / 2));
|
|
56
56
|
}
|
|
57
57
|
async function sha256(path) {
|
|
58
58
|
const hash = createHash('sha256');
|
|
@@ -140,7 +140,7 @@ async function transcribed(path, signal) {
|
|
|
140
140
|
await executed(installed(pinned.decoder), ['-nostdin', '-hide_banner', '-loglevel', 'error', '-i', path, '-ar', '16000', '-ac', '1', '-c:a', 'pcm_s16le',
|
|
141
141
|
'-f', 'segment', '-segment_time', String(CHUNK_SECONDS), join(work, '%05d.wav')], signal);
|
|
142
142
|
const parts = (await readdir(work)).sort().flatMap((part) => ['-f', join(work, part)]);
|
|
143
|
-
const text = await executed(installed(pinned.engine), ['-t', String(await
|
|
143
|
+
const text = await executed(installed(pinned.engine), ['-t', String(await threads()), '-np', '-m', installed(MODEL), ...parts], signal);
|
|
144
144
|
return text
|
|
145
145
|
.split('\n')
|
|
146
146
|
.map((line) => line.trim())
|
package/dist/update.js
CHANGED
package/how-we-work.md
CHANGED
|
@@ -8,16 +8,24 @@ The skills are already set up; never run `setup-matt-pocock-skills`.
|
|
|
8
8
|
|
|
9
9
|
## Where skill output goes
|
|
10
10
|
|
|
11
|
-
- Glossary (`
|
|
11
|
+
- Glossary (`GLOSSARY.md`): `glossary.md`
|
|
12
12
|
- Decisions (ADRs): `decisions/NNNN-<slug>.md`
|
|
13
13
|
- Plan (spec): `projects/<name>/plan.md`
|
|
14
14
|
- Tasks (tickets): `projects/<name>/tasks/NN-<slug>.md`, with a
|
|
15
15
|
`Status:` line (`needs-triage`, `needs-info`, `ready-for-agent`,
|
|
16
|
-
`ready-for-human`, `wontfix
|
|
17
|
-
|
|
16
|
+
`ready-for-human`, `wontfix`, or `done` once implemented), a
|
|
17
|
+
`Category:` line (`bug`, `enhancement`), a `Blocked by:` line and comments
|
|
18
|
+
under `## Comments`; triage takes no pull requests
|
|
19
|
+
- Rejected requests (triage): `out-of-scope/<concept>.md`
|
|
20
|
+
- Implementation notes (implement-spec): `projects/<name>/notes/`
|
|
21
|
+
- PR bodies (pr): `projects/<name>/prs/<branch>.md`
|
|
18
22
|
- Map (Wayfinding): `projects/<name>/map.md`, its tasks under
|
|
19
23
|
`tasks/` with `Type:`, `Status: claimed|resolved` and `Blocked by:` lines
|
|
20
24
|
- Research: `research/<slug>.md`
|
|
25
|
+
- Architecture reports (improve-codebase-architecture): `architecture/<slug>.html`
|
|
26
|
+
- Handoffs: `handoffs/<slug>.md`
|
|
21
27
|
- Questionnaires: `questionnaires/<slug>.md`
|
|
28
|
+
- Retrospectives (retro): `retros/<date>-<slug>.md`
|
|
29
|
+
- Wizards: `wizards/<slug>.sh`; the values they capture stay out of the Room
|
|
22
30
|
- Learning: `learning/<topic>/`
|
|
23
31
|
- 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-c3db1f18aed8f",
|
|
4
4
|
"description": "House Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc -p tsconfig.build.json && node scripts/bootstrap-manifest.mjs",
|
|
25
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",
|
|
26
|
+
"sync-skills": "node scripts/sync-skills.mjs",
|
|
26
27
|
"test": "tsc -p tsconfig.json && vitest run"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|