@agentshouse/kit 0.2.1-c9c41d009d46a → 0.2.1-ca0e1517cce79
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 +24 -24
- package/dist/acp.js +36 -16
- package/dist/agents.js +36 -16
- package/dist/api.js +5 -5
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +2 -2
- package/dist/calls.js +63 -0
- package/dist/clis.js +15 -3
- package/dist/{stream.js → connection.js} +33 -30
- package/dist/conversations.js +364 -153
- package/dist/copies.js +62 -43
- package/dist/end.js +6 -2
- package/dist/events.js +4 -3
- package/dist/files.js +139 -37
- package/dist/git.js +1 -1
- package/dist/house-cli.js +93 -84
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +28 -8
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +1 -3
- package/dist/mcp.js +4 -2
- package/dist/placement.js +11 -2
- package/dist/resident.js +38 -28
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +19 -37
- package/dist/skills.js +12 -93
- package/dist/transcription.js +51 -20
- package/dist/translate.js +2 -8
- package/dist/update.js +42 -45
- package/dist/web-wizard.js +100 -0
- package/dist/wire.js +19 -3
- package/package.json +12 -12
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/house-cli.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
3
|
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { request as httpsRequest } from 'node:https';
|
|
5
|
+
import { extname, join, resolve } from 'node:path';
|
|
6
|
+
import { Readable } from 'node:stream';
|
|
7
|
+
import { pipeline } from 'node:stream/promises';
|
|
8
|
+
import { downloaded, inspectedFile, storeRefusal, uploaded } from './files.js';
|
|
6
9
|
import { HOUSE_REF } from './git.js';
|
|
7
10
|
import { ownerSocket, readOwnAgent } from './home.js';
|
|
8
|
-
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
9
|
-
import { refusalOf } from './refusals.js';
|
|
11
|
+
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
12
|
+
import { refusalOf, retryable } from './refusals.js';
|
|
10
13
|
import { relayed, selectRelay } from './relay.js';
|
|
11
|
-
import { parse
|
|
14
|
+
import { parse } from 'yaml';
|
|
12
15
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
13
16
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
14
17
|
const PUSH = "push [commit]: sends the Local copy's committed changes to House";
|
|
@@ -51,15 +54,12 @@ const MEDIA_TYPES = new Map([
|
|
|
51
54
|
['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
52
55
|
['zip', 'application/zip'],
|
|
53
56
|
]);
|
|
54
|
-
function refused(path,
|
|
57
|
+
function refused(path, status, text, local) {
|
|
55
58
|
const refusal = refusalOf(text);
|
|
56
59
|
if (refusal !== null)
|
|
57
60
|
return refusal.text;
|
|
58
61
|
if (status === 401)
|
|
59
62
|
return REVOKED;
|
|
60
|
-
const act = method === 'GET' ? 'download' : 'upload';
|
|
61
|
-
if (URL.canParse(path))
|
|
62
|
-
return `the ${act} answered ${status}; ${act} the file again`;
|
|
63
63
|
if (status === 502 && local)
|
|
64
64
|
return text;
|
|
65
65
|
return path === '/' ? unconfirmed(status) : `${path} answered ${status}`;
|
|
@@ -67,26 +67,23 @@ function refused(path, method, status, text, local) {
|
|
|
67
67
|
function unconfirmed(status) {
|
|
68
68
|
return `House answered ${status} ${UNCONFIRMED}`;
|
|
69
69
|
}
|
|
70
|
-
function
|
|
70
|
+
function bridged(path, body, to = socketPath) {
|
|
71
71
|
return new Promise((resolve, reject) => {
|
|
72
|
-
const sent = request({ socketPath: to, path, method, headers }, (answer) => {
|
|
72
|
+
const sent = request({ socketPath: to, path, method: 'POST', headers: { 'content-type': 'application/json' } }, (answer) => {
|
|
73
73
|
const chunks = [];
|
|
74
74
|
answer.on('data', (chunk) => chunks.push(chunk));
|
|
75
75
|
answer.on('end', () => {
|
|
76
|
-
const
|
|
76
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
77
77
|
if (answer.statusCode === 200)
|
|
78
|
-
resolve(
|
|
78
|
+
resolve(text);
|
|
79
79
|
else
|
|
80
|
-
reject(new Error(refused(path,
|
|
80
|
+
reject(new Error(refused(path, answer.statusCode, text, answer.headers['content-type'] === undefined)));
|
|
81
81
|
});
|
|
82
82
|
});
|
|
83
83
|
sent.on('error', (error) => reject(new Error(error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
84
|
-
sent.end(
|
|
84
|
+
sent.end(JSON.stringify(body));
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
-
async function bridged(path, body, headers, to = socketPath) {
|
|
88
|
-
return (await exchanged(path, 'POST', body, headers, to)).toString('utf8');
|
|
89
|
-
}
|
|
90
87
|
async function direct(message) {
|
|
91
88
|
const ownAgent = await readOwnAgent();
|
|
92
89
|
if (ownAgent === null)
|
|
@@ -94,11 +91,14 @@ async function direct(message) {
|
|
|
94
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
95
92
|
setGlobalProxyFromEnv();
|
|
96
93
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
97
|
-
throw new
|
|
94
|
+
throw new Unsettled(UNREACHABLE);
|
|
98
95
|
});
|
|
99
96
|
if (answer.status === 200)
|
|
100
97
|
return answer.text;
|
|
101
|
-
|
|
98
|
+
if (answer.status === 401)
|
|
99
|
+
throw new Error(DISCONNECTED);
|
|
100
|
+
const refusal = refusalOf(answer.text)?.text ?? unconfirmed(answer.status);
|
|
101
|
+
throw retryable(answer.text) === false ? new Error(refusal) : new Unsettled(refusal);
|
|
102
102
|
}
|
|
103
103
|
async function mcp(method, params) {
|
|
104
104
|
const message = rpc(method, params);
|
|
@@ -110,31 +110,43 @@ async function mcp(method, params) {
|
|
|
110
110
|
async function tools() {
|
|
111
111
|
return (await mcp('tools/list', {})).tools;
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
function unreadable(path, error) {
|
|
114
|
+
return new Error(error.code === 'EISDIR'
|
|
115
|
+
? `${path} is a folder; upload one file at a time`
|
|
116
|
+
: `${path} is not a readable file`);
|
|
117
|
+
}
|
|
118
|
+
function opened(url, method, headers, body) {
|
|
119
|
+
return new Promise((answered, failed) => {
|
|
120
|
+
const local = socketPath !== undefined;
|
|
121
|
+
const target = local ? null : relayed(url);
|
|
122
|
+
if (!local)
|
|
123
|
+
setGlobalProxyFromEnv();
|
|
124
|
+
const sent = target === null
|
|
125
|
+
? request({ socketPath, path: url, method, headers }, answered)
|
|
126
|
+
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
|
127
|
+
sent.on('error', (error) => failed(new Error(!local ? UNREACHABLE : error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
128
|
+
if (body === undefined)
|
|
129
|
+
sent.end();
|
|
130
|
+
else
|
|
131
|
+
pipeline(body, sent).catch(() => undefined);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function ranged(file, start, end) {
|
|
135
|
+
return end === start ? undefined : createReadStream(file, { start, end: end - 1 });
|
|
136
|
+
}
|
|
137
|
+
async function* read(file, start, end) {
|
|
114
138
|
try {
|
|
115
|
-
|
|
139
|
+
yield* ranged(file, start, end) ?? [];
|
|
116
140
|
}
|
|
117
141
|
catch (error) {
|
|
118
|
-
throw
|
|
119
|
-
? `${path} is a folder; upload one file at a time`
|
|
120
|
-
: `${path} is not a readable file`);
|
|
142
|
+
throw unreadable(file, error);
|
|
121
143
|
}
|
|
122
144
|
}
|
|
123
|
-
async function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
throw new Error(UNREACHABLE);
|
|
129
|
-
});
|
|
130
|
-
const received = Buffer.from(await answer.arrayBuffer());
|
|
131
|
-
if (answer.status === 200)
|
|
132
|
-
return received;
|
|
133
|
-
throw new Error(refused(url, method, answer.status, received.toString('utf8'), false));
|
|
134
|
-
}
|
|
135
|
-
async function sent(url, bytes, operation) {
|
|
136
|
-
const headers = { 'content-type': 'application/octet-stream', 'x-house-byte-operation': operation };
|
|
137
|
-
return (await transferred(url, 'POST', bytes, headers)).toString('utf8');
|
|
145
|
+
async function sentPart(url, file, start, end, headers) {
|
|
146
|
+
const answer = await opened(url, 'PUT', { ...headers, 'content-length': String(end - start) }, ranged(file, start, end));
|
|
147
|
+
await answer.toArray();
|
|
148
|
+
if (answer.statusCode !== 200)
|
|
149
|
+
throw storeRefusal('upload', answer.statusCode);
|
|
138
150
|
}
|
|
139
151
|
async function download(args) {
|
|
140
152
|
const { flags, positionals: [argument] } = accepted(args, ['--help'], 1, DOWNLOAD_USAGE);
|
|
@@ -143,58 +155,55 @@ async function download(args) {
|
|
|
143
155
|
const { file, ...selected } = argumentsOf(argument);
|
|
144
156
|
if (typeof file !== 'string' || file === '')
|
|
145
157
|
throw new Error('download needs "file", the local file to write');
|
|
146
|
-
const inspected = parse(await called('inspect', selected));
|
|
147
|
-
if (typeof inspected?.download !== 'string')
|
|
148
|
-
throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
|
|
149
|
-
const bytes = verified(await transferred(inspected.download, 'GET'), String(inspected.sha256), String(selected.path));
|
|
150
158
|
const written = resolve(file);
|
|
151
|
-
await
|
|
152
|
-
|
|
159
|
+
await downloaded(async () => {
|
|
160
|
+
const file = inspectedFile(await called('inspect', selected));
|
|
161
|
+
if (file === null)
|
|
162
|
+
throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
|
|
163
|
+
return file;
|
|
164
|
+
}, async (url) => {
|
|
165
|
+
const answer = await opened(url, 'GET', {});
|
|
166
|
+
return { status: answer.statusCode, body: answer };
|
|
167
|
+
}, written, String(selected.path));
|
|
153
168
|
return written;
|
|
154
169
|
}
|
|
155
170
|
async function described(file) {
|
|
156
|
-
const
|
|
171
|
+
const digest = createHash('sha256');
|
|
172
|
+
let bytes = 0;
|
|
173
|
+
try {
|
|
174
|
+
for await (const chunk of createReadStream(file)) {
|
|
175
|
+
digest.update(chunk);
|
|
176
|
+
bytes += chunk.length;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
throw unreadable(file, error);
|
|
181
|
+
}
|
|
157
182
|
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
158
|
-
return {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
};
|
|
183
|
+
return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
|
|
184
|
+
}
|
|
185
|
+
async function finished(upload, room, partSha256) {
|
|
186
|
+
const text = await called('upload', { upload, room_ref: room, part_sha256: partSha256 });
|
|
187
|
+
const answer = parse(text);
|
|
188
|
+
return answer !== null && 'upload' in answer ? answer : { text };
|
|
166
189
|
}
|
|
167
190
|
async function upload({ file, ...destination }) {
|
|
168
191
|
if (typeof file !== 'string' || file === '')
|
|
169
192
|
throw new Error('upload needs "file", a local file');
|
|
170
|
-
const
|
|
193
|
+
const description = await described(file);
|
|
171
194
|
const answer = await called('upload', { ...destination, ...description });
|
|
172
195
|
const declared = parse(answer);
|
|
173
|
-
if (declared
|
|
196
|
+
if (declared?.upload === undefined)
|
|
174
197
|
return answer;
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const { bytes, description } = await described(attachment);
|
|
185
|
-
held.set(description.sha256, bytes);
|
|
186
|
-
return { name: basename(attachment), ...description };
|
|
187
|
-
}))
|
|
188
|
-
: args.attachments;
|
|
189
|
-
const record = { ...args, attachments };
|
|
190
|
-
for (;;) {
|
|
191
|
-
const answer = await called('append_record', record);
|
|
192
|
-
const { upload } = parse(answer);
|
|
193
|
-
if (upload === undefined)
|
|
194
|
-
return answer;
|
|
195
|
-
for (const grant of upload)
|
|
196
|
-
await sent(grant.url, held.get(grant.sha256), grant.operation);
|
|
197
|
-
}
|
|
198
|
+
const accepted = await uploaded(declared.upload, {
|
|
199
|
+
name: file,
|
|
200
|
+
bytes: description.bytes,
|
|
201
|
+
sha256: description.sha256,
|
|
202
|
+
read: (start, end) => read(file, start, end),
|
|
203
|
+
send: (url, start, end, headers) => sentPart(url, file, start, end, headers),
|
|
204
|
+
finish: (id, partSha256) => finished(id, destination.room_ref, partSha256),
|
|
205
|
+
});
|
|
206
|
+
return accepted.text;
|
|
198
207
|
}
|
|
199
208
|
function unexpected(argument, usage) {
|
|
200
209
|
return new Error(`unexpected argument ${argument}; ${usage}`);
|
|
@@ -220,7 +229,7 @@ async function push(args) {
|
|
|
220
229
|
throw new Error('--owner is not for Agent conversations; run house push without it');
|
|
221
230
|
if (!owner && socketPath === undefined)
|
|
222
231
|
throw new Error('outside an Agent conversation, house push needs --owner');
|
|
223
|
-
const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit },
|
|
232
|
+
const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit }, owner ? ownerSocket() : socketPath));
|
|
224
233
|
if (pushed.refused)
|
|
225
234
|
throw new Error(pushed.text);
|
|
226
235
|
return pushed.text;
|
|
@@ -228,7 +237,7 @@ async function push(args) {
|
|
|
228
237
|
async function fetched(args) {
|
|
229
238
|
if (accepted(args, ['--help'], 0, FETCH_USAGE).flags.has('--help'))
|
|
230
239
|
return FETCH_USAGE;
|
|
231
|
-
const answer = JSON.parse(await bridged('/git/fetch', {},
|
|
240
|
+
const answer = JSON.parse(await bridged('/git/fetch', {}, socketPath ?? ownerSocket()));
|
|
232
241
|
if (answer.refused)
|
|
233
242
|
throw new Error(answer.text);
|
|
234
243
|
return answer.text;
|
|
@@ -263,7 +272,7 @@ async function house(args) {
|
|
|
263
272
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
264
273
|
}
|
|
265
274
|
const parsed = argumentsOf(argument);
|
|
266
|
-
return verb === 'upload' ? upload(parsed) :
|
|
275
|
+
return verb === 'upload' ? upload(parsed) : called(verb, parsed);
|
|
267
276
|
}
|
|
268
277
|
async function called(name, args) {
|
|
269
278
|
const result = await mcp('tools/call', { name, arguments: args });
|
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,12 +1,32 @@
|
|
|
1
|
+
import { agentInstructions } from '@agentshouse/skills';
|
|
1
2
|
import { HOUSE_REF } from './git.js';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
3
|
+
const HOW_WE_WORK = '/rooms/private/how-we-work.md';
|
|
4
|
+
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
5
|
+
const FAILED = /^exit: [1-9]\d*$/;
|
|
6
|
+
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
7
|
+
function cutOf(lines) {
|
|
8
|
+
const cut = CUT.exec(lines[0] ?? '');
|
|
9
|
+
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
10
|
+
}
|
|
11
|
+
async function readHowWeWork(bridge) {
|
|
12
|
+
let document = '';
|
|
13
|
+
for (let command = `cat ${HOW_WE_WORK}`; command !== undefined;) {
|
|
14
|
+
const read = await bridge.tool('shell', { command });
|
|
15
|
+
const text = read.content.map((content) => content.text).join('\n');
|
|
16
|
+
const lines = text.split('\n');
|
|
17
|
+
if (read.isError === true || (FAILED.test(lines[0] ?? '') && lines[1]?.startsWith('stderr: '))) {
|
|
18
|
+
const refusal = read.isError === true ? text : lines.slice(1).join('\n').replace(/^stderr: /gm, '');
|
|
19
|
+
if (UNREADABLE.test(refusal))
|
|
20
|
+
return '';
|
|
21
|
+
throw new Error(refusal);
|
|
22
|
+
}
|
|
23
|
+
const cut = cutOf(lines);
|
|
24
|
+
document += lines.slice(cut === null ? 0 : 1).join('\n');
|
|
25
|
+
command = cut?.[2];
|
|
26
|
+
}
|
|
27
|
+
return document;
|
|
28
|
+
}
|
|
9
29
|
export async function instructions(bridge, base, copies) {
|
|
10
|
-
const lines =
|
|
30
|
+
const lines = agentInstructions(HOUSE_REF, copies);
|
|
11
31
|
return [lines.join('\n'), base, (await readHowWeWork(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
12
32
|
}
|
package/dist/kit-cli.js
CHANGED
|
@@ -11,7 +11,7 @@ const COMMANDS = new Map([
|
|
|
11
11
|
'logout',
|
|
12
12
|
{
|
|
13
13
|
usage: 'usage: kit logout',
|
|
14
|
-
about: "disconnects this computer, stops
|
|
14
|
+
about: "disconnects this computer, stops the Kit process and ends your own agent's connection",
|
|
15
15
|
operands: 0,
|
|
16
16
|
run: logout,
|
|
17
17
|
},
|
|
@@ -20,7 +20,7 @@ const COMMANDS = new Map([
|
|
|
20
20
|
'resident',
|
|
21
21
|
{
|
|
22
22
|
usage: 'usage: kit resident',
|
|
23
|
-
about: "keeps this Environment connected;
|
|
23
|
+
about: "keeps this Environment connected; Kit's service runs it",
|
|
24
24
|
operands: 0,
|
|
25
25
|
run: resident,
|
|
26
26
|
},
|
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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
-
import { hostname } from 'node:os';
|
|
4
3
|
import { parseArgs } from 'node:util';
|
|
5
4
|
import { HouseRefusal, post, retrying } from './api.js';
|
|
6
5
|
import { placeAppGit } from './app-git.js';
|
|
@@ -91,13 +90,12 @@ export async function login(argv) {
|
|
|
91
90
|
let named = environment;
|
|
92
91
|
let exchanged;
|
|
93
92
|
if (values.code !== undefined) {
|
|
94
|
-
exchanged = await post(house, '/kit/token', { code: values.code
|
|
93
|
+
exchanged = await post(house, '/kit/token', { code: values.code });
|
|
95
94
|
}
|
|
96
95
|
else {
|
|
97
96
|
held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
|
|
98
97
|
const verifier = randomBytes(32).toString('base64url');
|
|
99
98
|
const start = () => retrying(() => post(house, '/kit', {
|
|
100
|
-
label: hostname(),
|
|
101
99
|
...(named === undefined
|
|
102
100
|
? { act: 'new_environment', ...(replaces === undefined ? {} : { replaces }) }
|
|
103
101
|
: { act: 'existing_environment', environment_id: named }),
|
package/dist/mcp.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { KIT_RELEASE } from './clis.js';
|
|
3
3
|
import { relayed } from './relay.js';
|
|
4
4
|
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
5
5
|
export const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
6
|
+
export class Unsettled extends Error {
|
|
7
|
+
}
|
|
6
8
|
export function rpc(method, params) {
|
|
7
9
|
return { jsonrpc: '2.0', id: randomUUID(), method, params };
|
|
8
10
|
}
|
|
@@ -13,7 +15,7 @@ export function mcpBody(message, operation) {
|
|
|
13
15
|
...message.params,
|
|
14
16
|
_meta: {
|
|
15
17
|
'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
|
|
16
|
-
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version:
|
|
18
|
+
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_RELEASE },
|
|
17
19
|
'io.modelcontextprotocol/clientCapabilities': {},
|
|
18
20
|
...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
|
|
19
21
|
},
|
package/dist/placement.js
CHANGED
|
@@ -18,9 +18,9 @@ export async function updatesItself() {
|
|
|
18
18
|
// sudo answers at once, so ten seconds only bounds a stuck sudo.
|
|
19
19
|
return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
function recorded(name) {
|
|
22
22
|
try {
|
|
23
|
-
return readFileSync(join(kitHome(),
|
|
23
|
+
return readFileSync(join(kitHome(), name), 'utf8').trim();
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
if (error.code === 'ENOENT')
|
|
@@ -28,3 +28,12 @@ export function installCommand() {
|
|
|
28
28
|
throw error;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
export function installCommand() {
|
|
32
|
+
return recorded('install-command');
|
|
33
|
+
}
|
|
34
|
+
export function machine() {
|
|
35
|
+
const placed = recorded('placement');
|
|
36
|
+
if (placed === 'container' || placed === 'server' || placed === 'windows')
|
|
37
|
+
return placed;
|
|
38
|
+
return process.platform === 'darwin' ? 'macos' : 'linux';
|
|
39
|
+
}
|
package/dist/resident.js
CHANGED
|
@@ -8,16 +8,18 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { Agents } from './agents.js';
|
|
9
9
|
import { house } from './api.js';
|
|
10
10
|
import { received } from './bridge.js';
|
|
11
|
-
import {
|
|
11
|
+
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
13
|
import { restart } from './end.js';
|
|
14
14
|
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
15
|
+
import { Calls } from './calls.js';
|
|
15
16
|
import { killDescendants } from './scope.js';
|
|
16
17
|
import { quoted } from './shell.js';
|
|
17
18
|
import { SignIns } from './sign-in.js';
|
|
18
19
|
import { placeSkillSet } from './skills.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import { holdConnection } from './connection.js';
|
|
21
|
+
import { KitUpdate } from './update.js';
|
|
22
|
+
import { WebWizards } from './web-wizard.js';
|
|
21
23
|
import { codexLauncher } from './work.js';
|
|
22
24
|
function report(error) {
|
|
23
25
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -46,7 +48,7 @@ async function serveOwner(copies) {
|
|
|
46
48
|
if (!claimed(lock)) {
|
|
47
49
|
lock.close();
|
|
48
50
|
const running = await runningResident();
|
|
49
|
-
throw new Error(`${running === null ? 'another Kit' : `Kit ${running}`} already runs this Environment from Kit home ${kitHome()}; stop it first to run another here`);
|
|
51
|
+
throw new Error(`${running === null ? 'another Kit process' : `Kit process ${running}`} already runs this Environment from Kit home ${kitHome()}; stop it first to run another Kit process here`);
|
|
50
52
|
}
|
|
51
53
|
const server = createServer(async (request, response) => {
|
|
52
54
|
try {
|
|
@@ -131,9 +133,9 @@ export async function resident() {
|
|
|
131
133
|
});
|
|
132
134
|
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
133
135
|
const agents = new Agents(house, () => changed());
|
|
134
|
-
let
|
|
136
|
+
let connection = null;
|
|
135
137
|
let reported = false;
|
|
136
|
-
const idle = () =>
|
|
138
|
+
const idle = () => calls.idle() && conversations.idle() && agents.idle();
|
|
137
139
|
const changed = () => {
|
|
138
140
|
if (!idle()) {
|
|
139
141
|
reported = false;
|
|
@@ -146,15 +148,18 @@ export async function resident() {
|
|
|
146
148
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
147
149
|
}
|
|
148
150
|
};
|
|
151
|
+
const calls = new Calls(house, changed);
|
|
149
152
|
const conversations = new Conversations({
|
|
150
153
|
house,
|
|
151
154
|
agents,
|
|
152
155
|
copies,
|
|
153
|
-
|
|
156
|
+
calls,
|
|
157
|
+
send: (notification) => connection?.send(notification) ?? false,
|
|
154
158
|
changed,
|
|
155
159
|
});
|
|
156
|
-
const signIns = new SignIns(house, agents,
|
|
157
|
-
const
|
|
160
|
+
const signIns = new SignIns(house, agents, calls);
|
|
161
|
+
const update = new KitUpdate(calls);
|
|
162
|
+
const wizards = new WebWizards(calls, (notification) => connection?.send(notification) ?? false);
|
|
158
163
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
159
164
|
process.once(signal, () => {
|
|
160
165
|
killDescendants();
|
|
@@ -162,32 +167,37 @@ export async function resident() {
|
|
|
162
167
|
});
|
|
163
168
|
}
|
|
164
169
|
await house.deliver('/kit/restarted', {}).catch(report);
|
|
165
|
-
await
|
|
170
|
+
await update.resumed().catch(report);
|
|
166
171
|
logged(agents.refresh());
|
|
167
|
-
|
|
172
|
+
connection = holdConnection({
|
|
168
173
|
opened: () => {
|
|
169
174
|
conversations.opened();
|
|
170
175
|
changed();
|
|
171
176
|
logged(copies.connected());
|
|
172
177
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
else {
|
|
187
|
-
const refused = { refused: `This Kit does not know ${arrived.kind}; update the Kit` };
|
|
188
|
-
logged(house.deliver(`/kit/inputs/${arrived.input_id}/ack`, refused).finally(changed));
|
|
189
|
-
}
|
|
178
|
+
closed: () => wizards.disconnected(),
|
|
179
|
+
call: (call) => {
|
|
180
|
+
if (call.kind === 'sign_in')
|
|
181
|
+
signIns.start(call);
|
|
182
|
+
else if (call.kind === 'update')
|
|
183
|
+
update.start(call);
|
|
184
|
+
else if (call.kind === 'run_wizard')
|
|
185
|
+
wizards.start(call);
|
|
186
|
+
else if (CONVERSATION_METHODS.has(call.kind))
|
|
187
|
+
conversations.call(call);
|
|
188
|
+
else {
|
|
189
|
+
const refused = { refused: `This Kit does not know ${call.kind}; update the Kit` };
|
|
190
|
+
void calls.carry(call.input_id, async () => ({ result: refused }));
|
|
190
191
|
}
|
|
191
192
|
},
|
|
193
|
+
notification: (notification) => {
|
|
194
|
+
if (notification.type === 'watching')
|
|
195
|
+
conversations.watching(notification.watching === true);
|
|
196
|
+
if (notification.type === 'authority_changed')
|
|
197
|
+
logged(copies.changed(notification.rooms));
|
|
198
|
+
if (notification.type === 'work_available' && notification.subject === 'agents')
|
|
199
|
+
logged(agents.refresh());
|
|
200
|
+
wizards.notified(notification);
|
|
201
|
+
},
|
|
192
202
|
});
|
|
193
203
|
}
|
package/dist/scope.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
const LISTING_BYTES = 1 << 28;
|
|
5
5
|
const CGROUPS = '/sys/fs/cgroup';
|
|
6
6
|
const GONE = new Set(['ENOENT', 'ESRCH']);
|
|
7
|
+
const EXITED = new Set(['Z', 'X']);
|
|
7
8
|
function failure(error) {
|
|
8
9
|
return error.code ?? '';
|
|
9
10
|
}
|
|
@@ -24,6 +25,8 @@ function read(pid) {
|
|
|
24
25
|
try {
|
|
25
26
|
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
26
27
|
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
28
|
+
if (EXITED.has(fields[0]))
|
|
29
|
+
return null;
|
|
27
30
|
const command = readFileSync(`/proc/${pid}/cmdline`, 'utf8').replaceAll('\0', ' ');
|
|
28
31
|
return { parent: Number(fields[1]), session: fields[3], command };
|
|
29
32
|
}
|
package/dist/service.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { kitHome } from './home.js';
|
|
2
5
|
const UNIT = 'house-kit.service';
|
|
3
6
|
// launchctl and systemctl answer at once, so ten seconds only bounds a stuck service manager.
|
|
4
7
|
const MANAGER_MS = 10_000;
|
|
@@ -10,7 +13,21 @@ function start(command, args) {
|
|
|
10
13
|
if (started.status === 0)
|
|
11
14
|
return;
|
|
12
15
|
const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
|
|
13
|
-
throw new Error(`the
|
|
16
|
+
throw new Error(`the Kit service could not be started: ${cause}`);
|
|
17
|
+
}
|
|
18
|
+
function physical(path) {
|
|
19
|
+
return existsSync(path) ? realpathSync(path) : resolve(path);
|
|
20
|
+
}
|
|
21
|
+
function systemdHome(environment) {
|
|
22
|
+
for (const [, quoted, bare] of environment.matchAll(/"((?:[^"\\]|\\.)*)"|(\S+)/g)) {
|
|
23
|
+
const assignment = bare ?? quoted.replace(/\\(.)/g, '$1');
|
|
24
|
+
if (assignment.startsWith('HOUSE_KIT_HOME='))
|
|
25
|
+
return assignment.slice('HOUSE_KIT_HOME='.length);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function runsThisHome(served) {
|
|
30
|
+
return served !== undefined && physical(served) === physical(kitHome());
|
|
14
31
|
}
|
|
15
32
|
export function startService() {
|
|
16
33
|
if (process.platform === 'darwin') {
|
|
@@ -18,12 +35,17 @@ export function startService() {
|
|
|
18
35
|
const printed = manage('launchctl', ['print', service]);
|
|
19
36
|
if (printed.status !== 0 || /^\tstate = running$/m.test(printed.stdout))
|
|
20
37
|
return false;
|
|
38
|
+
if (!runsThisHome(/^\t+HOUSE_KIT_HOME => (.+)$/m.exec(printed.stdout)?.[1]))
|
|
39
|
+
return false;
|
|
21
40
|
start('launchctl', ['kickstart', service]);
|
|
22
41
|
return true;
|
|
23
42
|
}
|
|
24
43
|
for (const scope of [['--user'], []]) {
|
|
25
44
|
if (manage('systemctl', [...scope, 'is-enabled', '--quiet', UNIT]).status !== 0)
|
|
26
45
|
continue;
|
|
46
|
+
const environment = manage('systemctl', [...scope, 'show', '--property=Environment', '--value', UNIT]).stdout;
|
|
47
|
+
if (!runsThisHome(systemdHome(environment)))
|
|
48
|
+
continue;
|
|
27
49
|
if (manage('systemctl', [...scope, 'is-active', '--quiet', UNIT]).status === 0)
|
|
28
50
|
return false;
|
|
29
51
|
start('systemctl', [...scope, 'start', UNIT]);
|