@agentshouse/kit 0.2.1-ce3d2bb67177c → 0.2.1-ceeaf32286734
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 +6 -6
- package/dist/acp.js +57 -6
- package/dist/agents.js +6 -9
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +46 -0
- package/dist/calls.js +11 -3
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +39 -15
- package/dist/connection.js +20 -29
- package/dist/conversations.js +149 -47
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/files.js +33 -17
- package/dist/git.js +2 -2
- package/dist/house-cli.js +27 -55
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +3 -1
- package/dist/mcp.js +2 -0
- package/dist/relay.js +21 -0
- package/dist/resident.js +17 -6
- package/dist/scripts.js +255 -0
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +41 -96
- package/dist/transcription.js +32 -23
- package/dist/update.js +51 -8
- package/package.json +19 -20
- 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,9 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import {
|
|
4
|
-
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
3
|
+
import { request } from 'node:http';
|
|
5
4
|
import { request as httpsRequest } from 'node:https';
|
|
6
|
-
import {
|
|
5
|
+
import { extname, join, resolve } from 'node:path';
|
|
7
6
|
import { Readable } from 'node:stream';
|
|
8
7
|
import { pipeline } from 'node:stream/promises';
|
|
9
8
|
import { downloaded, inspectedFile, storeRefusal, uploaded } from './files.js';
|
|
@@ -11,7 +10,7 @@ import { HOUSE_REF } from './git.js';
|
|
|
11
10
|
import { ownerSocket, readOwnAgent } from './home.js';
|
|
12
11
|
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
13
12
|
import { refusalOf, retryable } from './refusals.js';
|
|
14
|
-
import { relayed, selectRelay } from './relay.js';
|
|
13
|
+
import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
|
|
15
14
|
import { parse } from 'yaml';
|
|
16
15
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
17
16
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
@@ -90,7 +89,6 @@ async function direct(message) {
|
|
|
90
89
|
if (ownAgent === null)
|
|
91
90
|
throw new Error(UNCONNECTED);
|
|
92
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
93
|
-
setGlobalProxyFromEnv();
|
|
94
92
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
95
93
|
throw new Unsettled(UNREACHABLE);
|
|
96
94
|
});
|
|
@@ -116,43 +114,35 @@ function unreadable(path, error) {
|
|
|
116
114
|
? `${path} is a folder; upload one file at a time`
|
|
117
115
|
: `${path} is not a readable file`);
|
|
118
116
|
}
|
|
119
|
-
async function localFile(path) {
|
|
120
|
-
try {
|
|
121
|
-
return await readFile(path);
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
throw unreadable(path, error);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
117
|
function opened(url, method, headers, body) {
|
|
128
118
|
return new Promise((answered, failed) => {
|
|
129
119
|
const local = socketPath !== undefined;
|
|
130
120
|
const target = local ? null : relayed(url);
|
|
131
121
|
if (!local)
|
|
132
|
-
|
|
122
|
+
proxyUnlessRelayed();
|
|
133
123
|
const sent = target === null
|
|
134
124
|
? request({ socketPath, path: url, method, headers }, answered)
|
|
135
125
|
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
|
136
126
|
sent.on('error', (error) => failed(new Error(!local ? UNREACHABLE : error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
137
|
-
if (body
|
|
138
|
-
|
|
127
|
+
if (body === undefined)
|
|
128
|
+
sent.end();
|
|
139
129
|
else
|
|
140
|
-
sent.
|
|
130
|
+
pipeline(body, sent).catch(() => undefined);
|
|
141
131
|
});
|
|
142
132
|
}
|
|
143
|
-
|
|
144
|
-
return
|
|
133
|
+
function ranged(file, start, end) {
|
|
134
|
+
return end === start ? undefined : createReadStream(file, { start, end: end - 1 });
|
|
145
135
|
}
|
|
146
|
-
async function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
async function* read(file, start, end) {
|
|
137
|
+
try {
|
|
138
|
+
yield* ranged(file, start, end) ?? [];
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw unreadable(file, error);
|
|
142
|
+
}
|
|
153
143
|
}
|
|
154
|
-
async function sentPart(url, file, start, end) {
|
|
155
|
-
const answer = await opened(url, 'PUT', { 'content-length': String(end - start) },
|
|
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));
|
|
156
146
|
await answer.toArray();
|
|
157
147
|
if (answer.statusCode !== 200)
|
|
158
148
|
throw storeRefusal('upload', answer.statusCode);
|
|
@@ -191,10 +181,10 @@ async function described(file) {
|
|
|
191
181
|
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
192
182
|
return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
|
|
193
183
|
}
|
|
194
|
-
async function finished(upload, room) {
|
|
195
|
-
const text = await called('upload', { upload, room_ref: room });
|
|
184
|
+
async function finished(upload, room, partSha256) {
|
|
185
|
+
const text = await called('upload', { upload, room_ref: room, part_sha256: partSha256 });
|
|
196
186
|
const answer = parse(text);
|
|
197
|
-
return answer !== null &&
|
|
187
|
+
return answer !== null && 'upload' in answer ? answer : { text };
|
|
198
188
|
}
|
|
199
189
|
async function upload({ file, ...destination }) {
|
|
200
190
|
if (typeof file !== 'string' || file === '')
|
|
@@ -205,33 +195,15 @@ async function upload({ file, ...destination }) {
|
|
|
205
195
|
if (declared?.upload === undefined)
|
|
206
196
|
return answer;
|
|
207
197
|
const accepted = await uploaded(declared.upload, {
|
|
198
|
+
name: file,
|
|
208
199
|
bytes: description.bytes,
|
|
209
|
-
|
|
210
|
-
|
|
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),
|
|
211
204
|
});
|
|
212
205
|
return accepted.text;
|
|
213
206
|
}
|
|
214
|
-
async function appendRecord(args) {
|
|
215
|
-
const held = new Map();
|
|
216
|
-
const attachments = Array.isArray(args.attachments)
|
|
217
|
-
? await Promise.all(args.attachments.map(async (attachment) => {
|
|
218
|
-
if (typeof attachment !== 'string')
|
|
219
|
-
return attachment;
|
|
220
|
-
const description = await described(attachment);
|
|
221
|
-
held.set(description.sha256, attachment);
|
|
222
|
-
return { name: basename(attachment), ...description };
|
|
223
|
-
}))
|
|
224
|
-
: args.attachments;
|
|
225
|
-
const record = { ...args, attachments };
|
|
226
|
-
for (;;) {
|
|
227
|
-
const answer = await called('append_record', record);
|
|
228
|
-
const { upload } = parse(answer);
|
|
229
|
-
if (upload === undefined)
|
|
230
|
-
return answer;
|
|
231
|
-
for (const grant of upload)
|
|
232
|
-
await sent(grant.url, await localFile(held.get(grant.sha256)), grant.operation);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
207
|
function unexpected(argument, usage) {
|
|
236
208
|
return new Error(`unexpected argument ${argument}; ${usage}`);
|
|
237
209
|
}
|
|
@@ -299,7 +271,7 @@ async function house(args) {
|
|
|
299
271
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
300
272
|
}
|
|
301
273
|
const parsed = argumentsOf(argument);
|
|
302
|
-
return verb === 'upload' ? upload(parsed) :
|
|
274
|
+
return verb === 'upload' ? upload(parsed) : called(verb, parsed);
|
|
303
275
|
}
|
|
304
276
|
async function called(name, args) {
|
|
305
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,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 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
|
+
}
|
|
9
29
|
export async function instructions(bridge, base, copies) {
|
|
10
|
-
const lines =
|
|
11
|
-
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');
|
|
12
32
|
}
|
package/dist/kit-cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { parseArgs } from 'node:util';
|
|
3
2
|
import { answerGitCredential } from './app-git.js';
|
|
4
3
|
import { chatgpt } from './chatgpt.js';
|
|
5
4
|
import { login } from './login.js';
|
|
6
5
|
import { logout } from './logout.js';
|
|
6
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
7
7
|
import { resident } from './resident.js';
|
|
8
8
|
const USAGE = 'usage: kit <login|logout|resident|chatgpt>\n';
|
|
9
9
|
const COMMANDS = new Map([
|
|
@@ -36,7 +36,7 @@ const COMMANDS = new Map([
|
|
|
36
36
|
],
|
|
37
37
|
]);
|
|
38
38
|
const [command, ...argv] = process.argv.slice(2);
|
|
39
|
-
|
|
39
|
+
proxyUnlessRelayed();
|
|
40
40
|
async function run() {
|
|
41
41
|
if (command === 'login')
|
|
42
42
|
return login(argv);
|
package/dist/kit-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`kit: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`kit: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/login.js
CHANGED
|
@@ -10,6 +10,7 @@ import { refusalOf } from './refusals.js';
|
|
|
10
10
|
import { selectRelay } from './relay.js';
|
|
11
11
|
import { startService } from './service.js';
|
|
12
12
|
import { placeSkillSet } from './skills.js';
|
|
13
|
+
import { endUnserved } from './wire.js';
|
|
13
14
|
export const DEFAULT_HOUSE = 'https://agents.house';
|
|
14
15
|
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
15
16
|
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
@@ -25,6 +26,7 @@ export async function accepted(house, credential) {
|
|
|
25
26
|
return true;
|
|
26
27
|
if (checked.status === 401)
|
|
27
28
|
return false;
|
|
29
|
+
endUnserved(checked.text);
|
|
28
30
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
29
31
|
}
|
|
30
32
|
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
@@ -119,7 +121,7 @@ export async function login(argv) {
|
|
|
119
121
|
open(started.start_url);
|
|
120
122
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
121
123
|
}
|
|
122
|
-
await placeSkillSet();
|
|
124
|
+
await placeSkillSet(house);
|
|
123
125
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
124
126
|
const relayRecord = relay === undefined ? {} : { relay };
|
|
125
127
|
await writeEnrolment({
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
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';
|
|
6
7
|
export class Unsettled extends Error {
|
|
@@ -33,6 +34,7 @@ export async function callHouse(origin, credential, message, operation = null, s
|
|
|
33
34
|
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
34
35
|
'mcp-method': message.method,
|
|
35
36
|
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
37
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
36
38
|
},
|
|
37
39
|
body: mcpBody(message, operation),
|
|
38
40
|
signal,
|
package/dist/relay.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
const PROXY = /^https?_proxy$/i;
|
|
3
|
+
const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
1
4
|
let selected;
|
|
5
|
+
let restoreProxy;
|
|
2
6
|
export function selectRelay(relay) {
|
|
3
7
|
selected = relay === undefined || relay === '' ? null : relay;
|
|
8
|
+
proxyUnlessRelayed();
|
|
4
9
|
}
|
|
5
10
|
export function relayOrigin() {
|
|
6
11
|
if (selected !== undefined)
|
|
@@ -8,6 +13,22 @@ export function relayOrigin() {
|
|
|
8
13
|
const ambient = process.env.HOUSE_KIT_RELAY;
|
|
9
14
|
return ambient === undefined || ambient === '' ? undefined : ambient;
|
|
10
15
|
}
|
|
16
|
+
function unlessRelayed() {
|
|
17
|
+
if (relayOrigin() === undefined)
|
|
18
|
+
return { ...process.env };
|
|
19
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
20
|
+
}
|
|
21
|
+
export function proxyUnlessRelayed() {
|
|
22
|
+
restoreProxy?.();
|
|
23
|
+
restoreProxy = setGlobalProxyFromEnv(unlessRelayed());
|
|
24
|
+
}
|
|
25
|
+
export function childEnvironment() {
|
|
26
|
+
const environment = unlessRelayed();
|
|
27
|
+
if (!Object.keys(environment).some((name) => PROXY.test(name)))
|
|
28
|
+
return environment;
|
|
29
|
+
const bypassed = [environment.NO_PROXY ?? environment.no_proxy, ...LOOPBACK].filter((entry) => entry !== undefined && entry !== '').join(',');
|
|
30
|
+
return { ...environment, NO_PROXY: bypassed, no_proxy: bypassed };
|
|
31
|
+
}
|
|
11
32
|
export function relayed(target) {
|
|
12
33
|
const url = new URL(target);
|
|
13
34
|
const relay = relayOrigin();
|
package/dist/resident.js
CHANGED
|
@@ -10,10 +10,11 @@ import { house } from './api.js';
|
|
|
10
10
|
import { received } from './bridge.js';
|
|
11
11
|
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
|
-
import { restart } from './end.js';
|
|
14
|
-
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
13
|
+
import { restart, unknownMethod } from './end.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, readEnrolment, restartAsked } from './home.js';
|
|
15
15
|
import { Calls } from './calls.js';
|
|
16
16
|
import { killDescendants } from './scope.js';
|
|
17
|
+
import { removeInputs, Scripts } from './scripts.js';
|
|
17
18
|
import { quoted } from './shell.js';
|
|
18
19
|
import { SignIns } from './sign-in.js';
|
|
19
20
|
import { placeSkillSet } from './skills.js';
|
|
@@ -116,7 +117,7 @@ async function started() {
|
|
|
116
117
|
killDescendants();
|
|
117
118
|
process.exit(1);
|
|
118
119
|
});
|
|
119
|
-
await placeSkillSet();
|
|
120
|
+
await placeSkillSet((await readEnrolment()).house);
|
|
120
121
|
await mkdir(agentBase(), { recursive: true });
|
|
121
122
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
122
123
|
await installShim(codexLauncher(), 'codex-main');
|
|
@@ -142,6 +143,9 @@ export async function resident() {
|
|
|
142
143
|
else if (restartAsked() && conversations.quiet()) {
|
|
143
144
|
restart('restarting for an updated CLI');
|
|
144
145
|
}
|
|
146
|
+
else if (update.held() && conversations.quiet()) {
|
|
147
|
+
update.apply(connection.pause());
|
|
148
|
+
}
|
|
145
149
|
else if (!reported) {
|
|
146
150
|
reported = true;
|
|
147
151
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
@@ -157,10 +161,13 @@ export async function resident() {
|
|
|
157
161
|
changed,
|
|
158
162
|
});
|
|
159
163
|
const signIns = new SignIns(house, agents, calls);
|
|
160
|
-
const update = new KitUpdate(calls);
|
|
164
|
+
const update = new KitUpdate(calls, changed);
|
|
165
|
+
removeInputs();
|
|
166
|
+
const scripts = new Scripts(calls, (notification) => connection?.send(notification) ?? false);
|
|
161
167
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
162
168
|
process.once(signal, () => {
|
|
163
169
|
killDescendants();
|
|
170
|
+
removeInputs();
|
|
164
171
|
process.kill(process.pid, signal);
|
|
165
172
|
});
|
|
166
173
|
}
|
|
@@ -173,15 +180,18 @@ export async function resident() {
|
|
|
173
180
|
changed();
|
|
174
181
|
logged(copies.connected());
|
|
175
182
|
},
|
|
183
|
+
closed: () => scripts.disconnected(),
|
|
176
184
|
call: (call) => {
|
|
177
185
|
if (call.kind === 'sign_in')
|
|
178
186
|
signIns.start(call);
|
|
179
187
|
else if (call.kind === 'update')
|
|
180
|
-
update.
|
|
188
|
+
update.hold(call);
|
|
189
|
+
else if (call.kind === 'run_wizard' || call.kind === 'run_function')
|
|
190
|
+
scripts.start(call);
|
|
181
191
|
else if (CONVERSATION_METHODS.has(call.kind))
|
|
182
192
|
conversations.call(call);
|
|
183
193
|
else {
|
|
184
|
-
const refused = { refused:
|
|
194
|
+
const refused = { refused: unknownMethod(call.kind) };
|
|
185
195
|
void calls.carry(call.input_id, async () => ({ result: refused }));
|
|
186
196
|
}
|
|
187
197
|
},
|
|
@@ -192,6 +202,7 @@ export async function resident() {
|
|
|
192
202
|
logged(copies.changed(notification.rooms));
|
|
193
203
|
if (notification.type === 'work_available' && notification.subject === 'agents')
|
|
194
204
|
logged(agents.refresh());
|
|
205
|
+
scripts.notified(notification);
|
|
195
206
|
},
|
|
196
207
|
});
|
|
197
208
|
}
|
package/dist/scripts.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { spawn as spawnPiped } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { once } from 'node:events';
|
|
4
|
+
import { rmSync } from 'node:fs';
|
|
5
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { constants } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { loginPath, onLoginPath } from './clis.js';
|
|
9
|
+
import { kitHome } from './home.js';
|
|
10
|
+
import { childEnvironment } from './relay.js';
|
|
11
|
+
import { killTree } from './scope.js';
|
|
12
|
+
const TERMINAL = 'xterm-256color';
|
|
13
|
+
const FIRST_SIZE = { cols: 80, rows: 24 };
|
|
14
|
+
const SIGNALLED = 128;
|
|
15
|
+
const NEWLINE = 0x0a;
|
|
16
|
+
const NO_BASH = "bash is not on the login shell's PATH";
|
|
17
|
+
const LOST = { refused: 'the Kit lost its connection to House' };
|
|
18
|
+
function causeOf(error) {
|
|
19
|
+
return error instanceof Error ? error.message : String(error);
|
|
20
|
+
}
|
|
21
|
+
function failed(plan, cause) {
|
|
22
|
+
process.stderr.write(`kit: ${plan.named} could not start: ${cause}\n`);
|
|
23
|
+
return { refused: `the script could not start: ${cause}` };
|
|
24
|
+
}
|
|
25
|
+
function planned(call) {
|
|
26
|
+
if (call.kind === 'run_wizard')
|
|
27
|
+
return { named: 'a Web Wizard', script: call.script, terminal: 'page', function: null };
|
|
28
|
+
return {
|
|
29
|
+
named: 'an Environment function',
|
|
30
|
+
script: call.script,
|
|
31
|
+
terminal: call.terminal ? 'at once' : null,
|
|
32
|
+
function: { input: call.input, timeCap: call.time_cap, outputCap: call.output_cap },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function inputs() {
|
|
36
|
+
return join(kitHome(), 'inputs');
|
|
37
|
+
}
|
|
38
|
+
async function inputFile(value) {
|
|
39
|
+
await mkdir(inputs(), { recursive: true, mode: 0o700 });
|
|
40
|
+
const path = join(inputs(), `${randomUUID()}.json`);
|
|
41
|
+
await writeFile(path, JSON.stringify(value), { mode: 0o600, flag: 'wx' });
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
export function removeInputs() {
|
|
45
|
+
rmSync(inputs(), { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
function struck(line, typed) {
|
|
48
|
+
return typed.some((typedLine) => line.includes(typedLine));
|
|
49
|
+
}
|
|
50
|
+
class KeptOutput {
|
|
51
|
+
cap;
|
|
52
|
+
typedText = '';
|
|
53
|
+
text = '';
|
|
54
|
+
bytes = 0;
|
|
55
|
+
line = '';
|
|
56
|
+
lineBytes = 0;
|
|
57
|
+
dropping = false;
|
|
58
|
+
cut = false;
|
|
59
|
+
constructor(cap) {
|
|
60
|
+
this.cap = cap;
|
|
61
|
+
}
|
|
62
|
+
typed(data) {
|
|
63
|
+
this.typedText += data;
|
|
64
|
+
}
|
|
65
|
+
add(data) {
|
|
66
|
+
const pieces = data.split('\n');
|
|
67
|
+
const rest = pieces.pop();
|
|
68
|
+
for (const piece of pieces)
|
|
69
|
+
this.ended(piece);
|
|
70
|
+
this.extend(rest);
|
|
71
|
+
}
|
|
72
|
+
kept() {
|
|
73
|
+
const typed = this.typedLines(true);
|
|
74
|
+
this.text = [...this.text.split(/(?<=\n)/), this.line].filter((line) => line !== '' && !struck(line, typed)).join('');
|
|
75
|
+
this.bytes = Buffer.byteLength(this.text);
|
|
76
|
+
this.trim();
|
|
77
|
+
return { output: this.text, cut: this.cut };
|
|
78
|
+
}
|
|
79
|
+
typedLines(all) {
|
|
80
|
+
const lines = this.typedText.split(/\r\n|\r|\n/);
|
|
81
|
+
if (!all)
|
|
82
|
+
lines.pop();
|
|
83
|
+
return lines.filter((line) => line !== '');
|
|
84
|
+
}
|
|
85
|
+
ended(piece) {
|
|
86
|
+
const line = `${this.line}${piece}\n`;
|
|
87
|
+
const dropped = this.dropping;
|
|
88
|
+
this.line = '';
|
|
89
|
+
this.lineBytes = 0;
|
|
90
|
+
this.dropping = false;
|
|
91
|
+
if (dropped || struck(line, this.typedLines(false)))
|
|
92
|
+
return;
|
|
93
|
+
this.text += line;
|
|
94
|
+
this.bytes += Buffer.byteLength(line);
|
|
95
|
+
if (this.bytes > 2 * this.cap)
|
|
96
|
+
this.trim();
|
|
97
|
+
}
|
|
98
|
+
extend(piece) {
|
|
99
|
+
if (this.dropping)
|
|
100
|
+
return;
|
|
101
|
+
this.line += piece;
|
|
102
|
+
this.lineBytes += Buffer.byteLength(piece);
|
|
103
|
+
if (this.lineBytes <= this.cap)
|
|
104
|
+
return;
|
|
105
|
+
this.text = '';
|
|
106
|
+
this.bytes = 0;
|
|
107
|
+
this.line = '';
|
|
108
|
+
this.lineBytes = 0;
|
|
109
|
+
this.dropping = true;
|
|
110
|
+
this.cut = true;
|
|
111
|
+
}
|
|
112
|
+
trim() {
|
|
113
|
+
if (this.bytes <= this.cap)
|
|
114
|
+
return;
|
|
115
|
+
const bytes = Buffer.from(this.text);
|
|
116
|
+
const start = bytes.indexOf(NEWLINE, bytes.length - this.cap - 1) + 1;
|
|
117
|
+
this.text = bytes.subarray(start).toString('utf8');
|
|
118
|
+
this.bytes = bytes.length - start;
|
|
119
|
+
this.cut = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export class Scripts {
|
|
123
|
+
calls;
|
|
124
|
+
send;
|
|
125
|
+
runs = new Map();
|
|
126
|
+
constructor(calls, send) {
|
|
127
|
+
this.calls = calls;
|
|
128
|
+
this.send = send;
|
|
129
|
+
}
|
|
130
|
+
start(call) {
|
|
131
|
+
void this.calls.carry(call.input_id, () => this.run(call.input_id, planned(call)));
|
|
132
|
+
}
|
|
133
|
+
notified(notification) {
|
|
134
|
+
const run = this.runs.get(String(notification.wizard));
|
|
135
|
+
if (run === undefined)
|
|
136
|
+
return;
|
|
137
|
+
if (notification.type === 'wizard_input')
|
|
138
|
+
this.typed(run, String(notification.data));
|
|
139
|
+
if (notification.type === 'wizard_size')
|
|
140
|
+
this.resized(run, { cols: Number(notification.cols), rows: Number(notification.rows) });
|
|
141
|
+
if (notification.type === 'wizard_end' && run.plan.terminal === 'page')
|
|
142
|
+
this.end(run, {});
|
|
143
|
+
}
|
|
144
|
+
disconnected() {
|
|
145
|
+
for (const run of this.runs.values())
|
|
146
|
+
this.end(run, LOST);
|
|
147
|
+
}
|
|
148
|
+
async run(id, plan) {
|
|
149
|
+
const size = plan.terminal === 'page' ? null : FIRST_SIZE;
|
|
150
|
+
const output = plan.function === null ? null : new KeptOutput(plan.function.outputCap);
|
|
151
|
+
const run = { plan, input: null, output, process: null, pipes: [], terminal: null, size, typed: [], ended: null, sized: Promise.withResolvers() };
|
|
152
|
+
if (size !== null)
|
|
153
|
+
run.sized.resolve();
|
|
154
|
+
this.runs.set(id, run);
|
|
155
|
+
try {
|
|
156
|
+
return { result: await this.ran(id, run), release: plan.function !== null };
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.runs.delete(id);
|
|
160
|
+
if (run.input !== null)
|
|
161
|
+
await rm(run.input, { force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async ran(id, run) {
|
|
165
|
+
const { plan, output } = run;
|
|
166
|
+
let exited;
|
|
167
|
+
try {
|
|
168
|
+
const { spawn } = await import('@lydell/node-pty');
|
|
169
|
+
const shell = await onLoginPath('bash');
|
|
170
|
+
if (shell === null)
|
|
171
|
+
return failed(plan, NO_BASH);
|
|
172
|
+
const path = await loginPath();
|
|
173
|
+
await run.sized.promise;
|
|
174
|
+
if (plan.function !== null)
|
|
175
|
+
run.input = await inputFile(plan.function.input);
|
|
176
|
+
if (run.ended !== null)
|
|
177
|
+
return run.ended;
|
|
178
|
+
const environment = { ...childEnvironment(), PATH: path, ...(run.input === null ? {} : { HOUSE_INPUT: run.input }) };
|
|
179
|
+
if (plan.terminal === null) {
|
|
180
|
+
({ exited } = await this.piped(plan.script, shell, environment, run));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const terminal = spawn(shell, ['-c', plan.script], { name: TERMINAL, ...run.size, cwd: process.cwd(), env: environment });
|
|
184
|
+
run.process = terminal;
|
|
185
|
+
run.terminal = terminal;
|
|
186
|
+
terminal.onData((data) => {
|
|
187
|
+
this.send({ type: 'wizard_output', wizard: id, data });
|
|
188
|
+
output?.add(data);
|
|
189
|
+
});
|
|
190
|
+
for (const data of run.typed.splice(0))
|
|
191
|
+
terminal.write(data);
|
|
192
|
+
exited = new Promise((resolve) => terminal.onExit(({ exitCode, signal }) => resolve(signal ? SIGNALLED + signal : exitCode)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return failed(plan, causeOf(error));
|
|
197
|
+
}
|
|
198
|
+
let timedOut = false;
|
|
199
|
+
// House sends a function run's time cap in seconds, and a run still going at its cap ends with its process group and the output pipes it holds.
|
|
200
|
+
const capped = plan.function === null ? undefined : setTimeout(() => {
|
|
201
|
+
timedOut = true;
|
|
202
|
+
this.stop(run);
|
|
203
|
+
}, plan.function.timeCap * 1000);
|
|
204
|
+
const code = await exited;
|
|
205
|
+
clearTimeout(capped);
|
|
206
|
+
if (run.ended !== null)
|
|
207
|
+
return run.ended;
|
|
208
|
+
if (output === null)
|
|
209
|
+
return { exit_code: code };
|
|
210
|
+
const kept = output.kept();
|
|
211
|
+
return timedOut ? { timed_out: true, ...kept } : { exit_code: code, ...kept };
|
|
212
|
+
}
|
|
213
|
+
async piped(script, shell, environment, run) {
|
|
214
|
+
const child = spawnPiped(shell, ['-c', script], { cwd: process.cwd(), env: environment, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
215
|
+
const exited = new Promise((resolve) => child.once('close', (code, signal) => resolve(signal === null ? code : SIGNALLED + constants.signals[signal])));
|
|
216
|
+
await once(child, 'spawn');
|
|
217
|
+
run.process = { pid: child.pid };
|
|
218
|
+
run.pipes = [child.stdout, child.stderr];
|
|
219
|
+
for (const pipe of run.pipes) {
|
|
220
|
+
pipe.setEncoding('utf8');
|
|
221
|
+
pipe.on('data', (data) => run.output.add(data));
|
|
222
|
+
}
|
|
223
|
+
if (run.ended !== null)
|
|
224
|
+
this.stop(run);
|
|
225
|
+
return { exited };
|
|
226
|
+
}
|
|
227
|
+
typed(run, data) {
|
|
228
|
+
run.output?.typed(data);
|
|
229
|
+
if (run.terminal === null)
|
|
230
|
+
run.typed.push(data);
|
|
231
|
+
else
|
|
232
|
+
run.terminal.write(data);
|
|
233
|
+
}
|
|
234
|
+
resized(run, size) {
|
|
235
|
+
run.size = size;
|
|
236
|
+
run.sized.resolve();
|
|
237
|
+
try {
|
|
238
|
+
run.terminal?.resize(size.cols, size.rows);
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
}
|
|
242
|
+
end(run, result) {
|
|
243
|
+
if (run.ended !== null)
|
|
244
|
+
return;
|
|
245
|
+
run.ended = result;
|
|
246
|
+
run.sized.resolve();
|
|
247
|
+
this.stop(run);
|
|
248
|
+
}
|
|
249
|
+
stop(run) {
|
|
250
|
+
if (run.process !== null)
|
|
251
|
+
killTree(run.process);
|
|
252
|
+
for (const pipe of run.pipes)
|
|
253
|
+
pipe.destroy();
|
|
254
|
+
}
|
|
255
|
+
}
|
package/dist/sign-in.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
-
import { CLIS
|
|
3
|
+
import { CLIS } from './clis.js';
|
|
4
|
+
import { childEnvironment } from './relay.js';
|
|
4
5
|
import { killTree } from './scope.js';
|
|
5
6
|
import { holdSecretInput } from './secret-input.js';
|
|
6
7
|
const ESCAPE = /\u001b\[[\d;]*m/g;
|
|
@@ -56,7 +57,7 @@ export class SignIns {
|
|
|
56
57
|
const child = spawn(cli, login.args, {
|
|
57
58
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
58
59
|
detached: true,
|
|
59
|
-
env:
|
|
60
|
+
env: childEnvironment(),
|
|
60
61
|
});
|
|
61
62
|
const running = () => child.exitCode === null && child.signalCode === null;
|
|
62
63
|
const stop = () => killTree(child);
|