@agentshouse/kit 0.2.1-c5d51f1e4ce19 → 0.2.1-c5e51b4e66131
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 +28 -28
- package/dist/acp.js +85 -18
- package/dist/agents.js +38 -17
- package/dist/api.js +5 -5
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +4 -7
- package/dist/building.js +46 -0
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +1 -1
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +47 -15
- package/dist/{stream.js → connection.js} +45 -52
- package/dist/conversations.js +391 -152
- package/dist/copies.js +77 -50
- package/dist/end.js +9 -4
- package/dist/events.js +4 -3
- package/dist/files.js +139 -37
- package/dist/git.js +5 -6
- package/dist/home.js +27 -13
- package/dist/house-cli.js +96 -88
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -8
- package/dist/kit-cli.js +4 -4
- package/dist/kit-main.js +3 -3
- package/dist/login.js +18 -9
- package/dist/logout.js +0 -2
- package/dist/mcp.js +6 -2
- package/dist/placement.js +9 -18
- package/dist/relay.js +21 -0
- package/dist/resident.js +92 -38
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +22 -39
- package/dist/silence.js +28 -0
- package/dist/skills.js +42 -97
- package/dist/transcription.js +83 -43
- package/dist/translate.js +2 -8
- package/dist/update.js +89 -49
- package/dist/web-wizard.js +101 -0
- package/dist/wire.js +19 -3
- package/dist/work.js +3 -5
- package/package.json +20 -21
- package/dist/bootstrap.json +0 -24
- 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 {
|
|
3
|
-
import { request
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { request } from 'node:http';
|
|
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
|
-
import {
|
|
8
|
-
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
9
|
-
import { refusalOf } from './refusals.js';
|
|
10
|
-
import { relayed, selectRelay } from './relay.js';
|
|
11
|
-
import { parse
|
|
10
|
+
import { ownerSocket, readOwnAgent } from './home.js';
|
|
11
|
+
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
12
|
+
import { refusalOf, retryable } from './refusals.js';
|
|
13
|
+
import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
|
|
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,38 +67,37 @@ 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)
|
|
93
90
|
throw new Error(UNCONNECTED);
|
|
94
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
95
|
-
setGlobalProxyFromEnv();
|
|
96
92
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
97
|
-
throw new
|
|
93
|
+
throw new Unsettled(UNREACHABLE);
|
|
98
94
|
});
|
|
99
95
|
if (answer.status === 200)
|
|
100
96
|
return answer.text;
|
|
101
|
-
|
|
97
|
+
if (answer.status === 401)
|
|
98
|
+
throw new Error(DISCONNECTED);
|
|
99
|
+
const refusal = refusalOf(answer.text)?.text ?? unconfirmed(answer.status);
|
|
100
|
+
throw retryable(answer.text) === false ? new Error(refusal) : new Unsettled(refusal);
|
|
102
101
|
}
|
|
103
102
|
async function mcp(method, params) {
|
|
104
103
|
const message = rpc(method, params);
|
|
@@ -110,31 +109,43 @@ async function mcp(method, params) {
|
|
|
110
109
|
async function tools() {
|
|
111
110
|
return (await mcp('tools/list', {})).tools;
|
|
112
111
|
}
|
|
113
|
-
|
|
112
|
+
function unreadable(path, error) {
|
|
113
|
+
return new Error(error.code === 'EISDIR'
|
|
114
|
+
? `${path} is a folder; upload one file at a time`
|
|
115
|
+
: `${path} is not a readable file`);
|
|
116
|
+
}
|
|
117
|
+
function opened(url, method, headers, body) {
|
|
118
|
+
return new Promise((answered, failed) => {
|
|
119
|
+
const local = socketPath !== undefined;
|
|
120
|
+
const target = local ? null : relayed(url);
|
|
121
|
+
if (!local)
|
|
122
|
+
proxyUnlessRelayed();
|
|
123
|
+
const sent = target === null
|
|
124
|
+
? request({ socketPath, path: url, method, headers }, answered)
|
|
125
|
+
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
|
126
|
+
sent.on('error', (error) => failed(new Error(!local ? UNREACHABLE : error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
127
|
+
if (body === undefined)
|
|
128
|
+
sent.end();
|
|
129
|
+
else
|
|
130
|
+
pipeline(body, sent).catch(() => undefined);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function ranged(file, start, end) {
|
|
134
|
+
return end === start ? undefined : createReadStream(file, { start, end: end - 1 });
|
|
135
|
+
}
|
|
136
|
+
async function* read(file, start, end) {
|
|
114
137
|
try {
|
|
115
|
-
|
|
138
|
+
yield* ranged(file, start, end) ?? [];
|
|
116
139
|
}
|
|
117
140
|
catch (error) {
|
|
118
|
-
throw
|
|
119
|
-
? `${path} is a folder; upload one file at a time`
|
|
120
|
-
: `${path} is not a readable file`);
|
|
141
|
+
throw unreadable(file, error);
|
|
121
142
|
}
|
|
122
143
|
}
|
|
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');
|
|
144
|
+
async function sentPart(url, file, start, end, headers) {
|
|
145
|
+
const answer = await opened(url, 'PUT', { ...headers, 'content-length': String(end - start) }, ranged(file, start, end));
|
|
146
|
+
await answer.toArray();
|
|
147
|
+
if (answer.statusCode !== 200)
|
|
148
|
+
throw storeRefusal('upload', answer.statusCode);
|
|
138
149
|
}
|
|
139
150
|
async function download(args) {
|
|
140
151
|
const { flags, positionals: [argument] } = accepted(args, ['--help'], 1, DOWNLOAD_USAGE);
|
|
@@ -143,58 +154,55 @@ async function download(args) {
|
|
|
143
154
|
const { file, ...selected } = argumentsOf(argument);
|
|
144
155
|
if (typeof file !== 'string' || file === '')
|
|
145
156
|
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
157
|
const written = resolve(file);
|
|
151
|
-
await
|
|
152
|
-
|
|
158
|
+
await downloaded(async () => {
|
|
159
|
+
const file = inspectedFile(await called('inspect', selected));
|
|
160
|
+
if (file === null)
|
|
161
|
+
throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
|
|
162
|
+
return file;
|
|
163
|
+
}, async (url) => {
|
|
164
|
+
const answer = await opened(url, 'GET', {});
|
|
165
|
+
return { status: answer.statusCode, body: answer };
|
|
166
|
+
}, written, String(selected.path));
|
|
153
167
|
return written;
|
|
154
168
|
}
|
|
155
169
|
async function described(file) {
|
|
156
|
-
const
|
|
170
|
+
const digest = createHash('sha256');
|
|
171
|
+
let bytes = 0;
|
|
172
|
+
try {
|
|
173
|
+
for await (const chunk of createReadStream(file)) {
|
|
174
|
+
digest.update(chunk);
|
|
175
|
+
bytes += chunk.length;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw unreadable(file, error);
|
|
180
|
+
}
|
|
157
181
|
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
158
|
-
return {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
};
|
|
182
|
+
return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
|
|
183
|
+
}
|
|
184
|
+
async function finished(upload, room, partSha256) {
|
|
185
|
+
const text = await called('upload', { upload, room_ref: room, part_sha256: partSha256 });
|
|
186
|
+
const answer = parse(text);
|
|
187
|
+
return answer !== null && 'upload' in answer ? answer : { text };
|
|
166
188
|
}
|
|
167
189
|
async function upload({ file, ...destination }) {
|
|
168
190
|
if (typeof file !== 'string' || file === '')
|
|
169
191
|
throw new Error('upload needs "file", a local file');
|
|
170
|
-
const
|
|
192
|
+
const description = await described(file);
|
|
171
193
|
const answer = await called('upload', { ...destination, ...description });
|
|
172
194
|
const declared = parse(answer);
|
|
173
|
-
if (declared
|
|
195
|
+
if (declared?.upload === undefined)
|
|
174
196
|
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
|
-
}
|
|
197
|
+
const accepted = await uploaded(declared.upload, {
|
|
198
|
+
name: file,
|
|
199
|
+
bytes: description.bytes,
|
|
200
|
+
sha256: description.sha256,
|
|
201
|
+
read: (start, end) => read(file, start, end),
|
|
202
|
+
send: (url, start, end, headers) => sentPart(url, file, start, end, headers),
|
|
203
|
+
finish: (id, partSha256) => finished(id, destination.room_ref, partSha256),
|
|
204
|
+
});
|
|
205
|
+
return accepted.text;
|
|
198
206
|
}
|
|
199
207
|
function unexpected(argument, usage) {
|
|
200
208
|
return new Error(`unexpected argument ${argument}; ${usage}`);
|
|
@@ -220,7 +228,7 @@ async function push(args) {
|
|
|
220
228
|
throw new Error('--owner is not for Agent conversations; run house push without it');
|
|
221
229
|
if (!owner && socketPath === undefined)
|
|
222
230
|
throw new Error('outside an Agent conversation, house push needs --owner');
|
|
223
|
-
const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit },
|
|
231
|
+
const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit }, owner ? ownerSocket() : socketPath));
|
|
224
232
|
if (pushed.refused)
|
|
225
233
|
throw new Error(pushed.text);
|
|
226
234
|
return pushed.text;
|
|
@@ -228,7 +236,7 @@ async function push(args) {
|
|
|
228
236
|
async function fetched(args) {
|
|
229
237
|
if (accepted(args, ['--help'], 0, FETCH_USAGE).flags.has('--help'))
|
|
230
238
|
return FETCH_USAGE;
|
|
231
|
-
const answer = JSON.parse(await bridged('/git/fetch', {},
|
|
239
|
+
const answer = JSON.parse(await bridged('/git/fetch', {}, socketPath ?? ownerSocket()));
|
|
232
240
|
if (answer.refused)
|
|
233
241
|
throw new Error(answer.text);
|
|
234
242
|
return answer.text;
|
|
@@ -263,7 +271,7 @@ async function house(args) {
|
|
|
263
271
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
264
272
|
}
|
|
265
273
|
const parsed = argumentsOf(argument);
|
|
266
|
-
return verb === 'upload' ? upload(parsed) :
|
|
274
|
+
return verb === 'upload' ? upload(parsed) : called(verb, parsed);
|
|
267
275
|
}
|
|
268
276
|
async function called(name, args) {
|
|
269
277
|
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,11 +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
|
|
3
|
+
const OWNER_RULES = '/rooms/private/owner-rules.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 readOwnerRules(bridge) {
|
|
12
|
+
let document = '';
|
|
13
|
+
for (let command = `cat ${OWNER_RULES}`; 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
|
+
}
|
|
8
29
|
export async function instructions(bridge, base, copies) {
|
|
9
|
-
const lines =
|
|
10
|
-
return [lines.join('\n'), base, (await
|
|
30
|
+
const lines = agentInstructions(HOUSE_REF, copies);
|
|
31
|
+
return [lines.join('\n'), base, (await readOwnerRules(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
11
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([
|
|
@@ -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
|
},
|
|
@@ -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
|
@@ -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';
|
|
@@ -11,6 +10,7 @@ import { refusalOf } from './refusals.js';
|
|
|
11
10
|
import { selectRelay } from './relay.js';
|
|
12
11
|
import { startService } from './service.js';
|
|
13
12
|
import { placeSkillSet } from './skills.js';
|
|
13
|
+
import { endUnserved } from './wire.js';
|
|
14
14
|
export const DEFAULT_HOUSE = 'https://agents.house';
|
|
15
15
|
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
16
16
|
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
@@ -26,6 +26,7 @@ export async function accepted(house, credential) {
|
|
|
26
26
|
return true;
|
|
27
27
|
if (checked.status === 401)
|
|
28
28
|
return false;
|
|
29
|
+
endUnserved(checked.text);
|
|
29
30
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
30
31
|
}
|
|
31
32
|
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
@@ -36,6 +37,13 @@ const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms
|
|
|
36
37
|
--no-private give this Environment every Room but your Private Room
|
|
37
38
|
--code <code> connect this machine with a login code
|
|
38
39
|
`;
|
|
40
|
+
function connected(house, named, environment) {
|
|
41
|
+
if (named === undefined)
|
|
42
|
+
return `Environment ${environment} is connected to ${house}.\n`;
|
|
43
|
+
if (named === environment)
|
|
44
|
+
return `Environment ${environment} is reconnected to ${house}.\n`;
|
|
45
|
+
return `Environment ${named} stays with its account; this computer is connected to ${house} as new Environment ${environment} of the account that confirmed.\n`;
|
|
46
|
+
}
|
|
39
47
|
function gone(error) {
|
|
40
48
|
return error instanceof HouseRefusal && refusalOf(error.text)?.code === 'kit_environment_not_found';
|
|
41
49
|
}
|
|
@@ -81,15 +89,15 @@ export async function login(argv) {
|
|
|
81
89
|
const environment = replaces === undefined ? (values.environment ?? remembered) : undefined;
|
|
82
90
|
const ownAgent = await readOwnAgent();
|
|
83
91
|
let held = false;
|
|
92
|
+
let named = environment;
|
|
84
93
|
let exchanged;
|
|
85
94
|
if (values.code !== undefined) {
|
|
86
|
-
exchanged = await post(house, '/kit/token', { code: values.code
|
|
95
|
+
exchanged = await post(house, '/kit/token', { code: values.code });
|
|
87
96
|
}
|
|
88
97
|
else {
|
|
89
98
|
held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
|
|
90
99
|
const verifier = randomBytes(32).toString('base64url');
|
|
91
|
-
const start = (
|
|
92
|
-
label: hostname(),
|
|
100
|
+
const start = () => retrying(() => post(house, '/kit', {
|
|
93
101
|
...(named === undefined
|
|
94
102
|
? { act: 'new_environment', ...(replaces === undefined ? {} : { replaces }) }
|
|
95
103
|
: { act: 'existing_environment', environment_id: named }),
|
|
@@ -99,20 +107,21 @@ export async function login(argv) {
|
|
|
99
107
|
}));
|
|
100
108
|
let started;
|
|
101
109
|
try {
|
|
102
|
-
started = await start(
|
|
110
|
+
started = await start();
|
|
103
111
|
}
|
|
104
112
|
catch (error) {
|
|
105
113
|
if (remembered === undefined || !gone(error))
|
|
106
114
|
throw error;
|
|
107
115
|
await forgetDisconnected();
|
|
108
116
|
process.stdout.write(`House no longer holds Environment ${remembered}; connecting this computer as a new one.\n`);
|
|
109
|
-
|
|
117
|
+
named = undefined;
|
|
118
|
+
started = await start();
|
|
110
119
|
}
|
|
111
120
|
process.stdout.write(`Open this link and confirm: ${started.start_url}\n`);
|
|
112
121
|
open(started.start_url);
|
|
113
122
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
114
123
|
}
|
|
115
|
-
await placeSkillSet();
|
|
124
|
+
await placeSkillSet(house);
|
|
116
125
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
117
126
|
const relayRecord = relay === undefined ? {} : { relay };
|
|
118
127
|
await writeEnrolment({
|
|
@@ -121,7 +130,7 @@ export async function login(argv) {
|
|
|
121
130
|
environment: exchanged.environment,
|
|
122
131
|
credential: exchanged.credential,
|
|
123
132
|
});
|
|
124
|
-
process.stdout.write(
|
|
133
|
+
process.stdout.write(connected(house, named, exchanged.environment));
|
|
125
134
|
if (exchanged.headless_credential !== undefined) {
|
|
126
135
|
await writeOwnAgent({ house, ...relayRecord, user: exchanged.user, credential: exchanged.headless_credential });
|
|
127
136
|
}
|
|
@@ -132,7 +141,7 @@ export async function login(argv) {
|
|
|
132
141
|
else if (held && ownAgent !== null) {
|
|
133
142
|
await writeOwnAgent({ house, ...relayRecord, user: ownAgent.user, credential: ownAgent.credential });
|
|
134
143
|
}
|
|
135
|
-
await placeAppGit(
|
|
144
|
+
await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
136
145
|
try {
|
|
137
146
|
startService();
|
|
138
147
|
}
|
package/dist/logout.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { HouseRefusal, post } from './api.js';
|
|
2
|
-
import { forgetAppGit } from './app-git.js';
|
|
3
2
|
import { forgetEnrolment, forgetOwnAgent, readEnrolment } from './home.js';
|
|
4
3
|
export async function logout() {
|
|
5
4
|
const enrolled = await readEnrolment();
|
|
@@ -13,7 +12,6 @@ export async function logout() {
|
|
|
13
12
|
}
|
|
14
13
|
await forgetEnrolment(enrolled);
|
|
15
14
|
}
|
|
16
|
-
await forgetAppGit();
|
|
17
15
|
await forgetOwnAgent();
|
|
18
16
|
process.stdout.write(enrolled === null
|
|
19
17
|
? 'This computer is not connected to House.\n'
|
package/dist/mcp.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { KIT_RELEASE } from './clis.js';
|
|
3
3
|
import { relayed } from './relay.js';
|
|
4
|
+
import { KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
|
|
4
5
|
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
5
6
|
export const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
7
|
+
export class Unsettled extends Error {
|
|
8
|
+
}
|
|
6
9
|
export function rpc(method, params) {
|
|
7
10
|
return { jsonrpc: '2.0', id: randomUUID(), method, params };
|
|
8
11
|
}
|
|
@@ -13,7 +16,7 @@ export function mcpBody(message, operation) {
|
|
|
13
16
|
...message.params,
|
|
14
17
|
_meta: {
|
|
15
18
|
'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
|
|
16
|
-
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version:
|
|
19
|
+
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_RELEASE },
|
|
17
20
|
'io.modelcontextprotocol/clientCapabilities': {},
|
|
18
21
|
...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
|
|
19
22
|
},
|
|
@@ -31,6 +34,7 @@ export async function callHouse(origin, credential, message, operation = null, s
|
|
|
31
34
|
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
32
35
|
'mcp-method': message.method,
|
|
33
36
|
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
37
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
34
38
|
},
|
|
35
39
|
body: mcpBody(message, operation),
|
|
36
40
|
signal,
|
package/dist/placement.js
CHANGED
|
@@ -2,7 +2,6 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { run } from './clis.js';
|
|
4
4
|
import { kitHome } from './home.js';
|
|
5
|
-
import { relayOrigin } from './relay.js';
|
|
6
5
|
export function service() {
|
|
7
6
|
if (process.platform !== 'linux' || !process.env.INVOCATION_ID)
|
|
8
7
|
return null;
|
|
@@ -19,9 +18,9 @@ export async function updatesItself() {
|
|
|
19
18
|
// sudo answers at once, so ten seconds only bounds a stuck sudo.
|
|
20
19
|
return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
|
|
21
20
|
}
|
|
22
|
-
function
|
|
21
|
+
function recorded(name) {
|
|
23
22
|
try {
|
|
24
|
-
return readFileSync(join(kitHome(),
|
|
23
|
+
return readFileSync(join(kitHome(), name), 'utf8').trim();
|
|
25
24
|
}
|
|
26
25
|
catch (error) {
|
|
27
26
|
if (error.code === 'ENOENT')
|
|
@@ -29,20 +28,12 @@ function installed() {
|
|
|
29
28
|
throw error;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
const relay = relayOrigin();
|
|
35
|
-
if (relay === undefined)
|
|
36
|
-
return [`curl -fsSL ${host}/kit | sh`, `irm ${host}/kit.ps1 | iex`];
|
|
37
|
-
const entry = `${relay}/${host}/kit`;
|
|
38
|
-
return [`curl -fsSL ${entry} | HOUSE_KIT_RELAY=${relay} sh`, `$env:HOUSE_KIT_RELAY='${relay}'; irm ${entry}.ps1 | iex`];
|
|
31
|
+
export function installCommand() {
|
|
32
|
+
return recorded('install-command');
|
|
39
33
|
}
|
|
40
|
-
export function
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (placement === 'server')
|
|
46
|
-
return `${shell} -s -- --linux`;
|
|
47
|
-
return `${shell} -s -- --container, or ${windows} on Windows`;
|
|
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';
|
|
48
39
|
}
|
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();
|