@agentshouse/kit 0.2.1-c3cf4b145dcda → 0.2.1-c3d0b9ac4a82a
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 +42 -40
- package/dist/acp.js +92 -115
- package/dist/agents.js +112 -24
- package/dist/api.js +10 -6
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +15 -44
- package/dist/building.js +46 -0
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +131 -27
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +116 -0
- package/dist/conversations.js +456 -200
- package/dist/copies.js +185 -123
- package/dist/end.js +24 -5
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +16 -78
- package/dist/files.js +139 -31
- package/dist/git.js +13 -11
- package/dist/home.js +96 -7
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -162
- package/dist/instructions.js +8 -10
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +118 -107
- package/dist/logout.js +14 -16
- package/dist/mcp.js +6 -2
- package/dist/parts.js +34 -16
- package/dist/placement.js +39 -0
- package/dist/relay.js +33 -2
- package/dist/resident.js +116 -51
- package/dist/scope.js +206 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/sign-in.js +23 -40
- package/dist/silence.js +28 -0
- package/dist/skills.js +49 -35
- package/dist/transcription.js +85 -45
- package/dist/translate.js +2 -8
- package/dist/update.js +115 -53
- package/dist/web-wizard.js +101 -0
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/package.json +20 -20
- package/dist/bootstrap.json +0 -24
- package/dist/stream.js +0 -72
- package/how-we-work.md +0 -23
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- 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 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- 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/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- 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 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- 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 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- 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 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- 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 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- 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 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/api.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { signInAgain } from './end.js';
|
|
3
3
|
import { readEnrolment } from './home.js';
|
|
4
4
|
import { retryable } from './refusals.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
|
+
import { endUnserved, KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
|
|
6
7
|
export class HouseRefusal extends Error {
|
|
7
8
|
status;
|
|
8
9
|
text;
|
|
@@ -16,10 +17,10 @@ export function finalRefusal(error) {
|
|
|
16
17
|
return error instanceof HouseRefusal && retryable(error.text) === false;
|
|
17
18
|
}
|
|
18
19
|
export function retryDelay(attempt) {
|
|
19
|
-
// Full jitter, doubling from one second to a 30-second cap, spreads the
|
|
20
|
+
// Full jitter, doubling from one second to a 30-second cap, spreads the Kit processes one outage dropped and retries within half a minute of House's return.
|
|
20
21
|
return Math.random() * Math.min(30_000, 1000 * 2 ** attempt);
|
|
21
22
|
}
|
|
22
|
-
export async function retrying(sent, signal) {
|
|
23
|
+
export async function retrying(sent, signal, pause = retryDelay) {
|
|
23
24
|
for (let attempt = 0;; attempt++) {
|
|
24
25
|
try {
|
|
25
26
|
return await sent();
|
|
@@ -28,8 +29,8 @@ export async function retrying(sent, signal) {
|
|
|
28
29
|
if (!(error instanceof HouseRefusal) || retryable(error.text) !== true)
|
|
29
30
|
throw error;
|
|
30
31
|
}
|
|
31
|
-
// A request House refused as retryable is sent again
|
|
32
|
-
await delay(
|
|
32
|
+
// A request House refused as retryable is sent again after the caller's pause, the jittered curve unless it names its own.
|
|
33
|
+
await delay(pause(attempt), undefined, { signal });
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
export async function post(house, path, body, credential, signal) {
|
|
@@ -37,12 +38,15 @@ export async function post(house, path, body, credential, signal) {
|
|
|
37
38
|
method: 'POST',
|
|
38
39
|
headers: {
|
|
39
40
|
'content-type': 'application/json',
|
|
41
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
40
42
|
...(credential === undefined ? {} : { authorization: `Bearer ${credential}` }),
|
|
41
43
|
},
|
|
42
44
|
body: JSON.stringify(body),
|
|
43
45
|
signal,
|
|
44
46
|
});
|
|
45
47
|
const text = await answer.text();
|
|
48
|
+
if (!answer.ok)
|
|
49
|
+
endUnserved(text);
|
|
46
50
|
if (!answer.ok)
|
|
47
51
|
throw new HouseRefusal(path, answer.status, text);
|
|
48
52
|
return JSON.parse(text);
|
|
@@ -54,7 +58,7 @@ async function enrolledPost(path, body, signal) {
|
|
|
54
58
|
}
|
|
55
59
|
catch (error) {
|
|
56
60
|
if (error instanceof HouseRefusal && error.status === 401)
|
|
57
|
-
|
|
61
|
+
await signInAgain(credential);
|
|
58
62
|
throw error;
|
|
59
63
|
}
|
|
60
64
|
}
|
package/dist/app-git.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { extname, resolve } from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
1
|
import { run } from './git.js';
|
|
4
2
|
import { kitHome, readOwnAgent } from './home.js';
|
|
5
|
-
|
|
3
|
+
const HELPER = '!kit git-credential';
|
|
6
4
|
function section(house) {
|
|
7
5
|
return `credential.${new URL('/app/', house).href}`;
|
|
8
6
|
}
|
|
9
|
-
function helper() {
|
|
10
|
-
const main = fileURLToPath(new URL(`./kit-main${extname(fileURLToPath(import.meta.url))}`, import.meta.url));
|
|
11
|
-
return `!HOUSE_KIT_HOME=${quoted(resolve(kitHome()))} ${quoted(process.execPath)} ${quoted(main)} git-credential`;
|
|
12
|
-
}
|
|
13
7
|
async function configured(args) {
|
|
14
8
|
try {
|
|
15
9
|
return await run(kitHome(), ['config', '--global', ...args]);
|
|
@@ -20,30 +14,18 @@ async function configured(args) {
|
|
|
20
14
|
throw error;
|
|
21
15
|
}
|
|
22
16
|
}
|
|
23
|
-
async function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const ran = await configured(['--add', `${section(house)}.helper`, value]);
|
|
17
|
+
export async function placeAppGit(ownAgent) {
|
|
18
|
+
if (ownAgent === null)
|
|
19
|
+
return;
|
|
20
|
+
await configured(['--remove-section', section(ownAgent.house)]);
|
|
21
|
+
for (const value of ['', HELPER]) {
|
|
22
|
+
const ran = await configured(['--add', `${section(ownAgent.house)}.helper`, value]);
|
|
30
23
|
if (ran === null)
|
|
31
24
|
return;
|
|
32
25
|
if (ran.status !== 0)
|
|
33
26
|
throw new Error(`git config failed: ${ran.stderr.trim()}`);
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
|
-
export async function placeAppGit(before, after) {
|
|
37
|
-
if (before !== null && before.house !== after?.house)
|
|
38
|
-
await forget(before.house);
|
|
39
|
-
if (after !== null)
|
|
40
|
-
await register(after.house);
|
|
41
|
-
}
|
|
42
|
-
export async function forgetAppGit() {
|
|
43
|
-
const ownAgent = await readOwnAgent();
|
|
44
|
-
if (ownAgent !== null)
|
|
45
|
-
await forget(ownAgent.house);
|
|
46
|
-
}
|
|
47
29
|
export async function answerGitCredential(operation) {
|
|
48
30
|
let asked = '';
|
|
49
31
|
for await (const chunk of process.stdin)
|
|
@@ -55,8 +37,10 @@ export async function answerGitCredential(operation) {
|
|
|
55
37
|
.filter((line) => line.includes('='))
|
|
56
38
|
.map((line) => [line.slice(0, line.indexOf('=')), line.slice(line.indexOf('=') + 1)]));
|
|
57
39
|
const ownAgent = await readOwnAgent();
|
|
58
|
-
if (ownAgent === null)
|
|
40
|
+
if (ownAgent === null) {
|
|
41
|
+
process.stderr.write(`kit: Kit home ${kitHome()} is not signed in; run kit login\n`);
|
|
59
42
|
return;
|
|
43
|
+
}
|
|
60
44
|
const house = new URL(ownAgent.house);
|
|
61
45
|
if (attributes.get('protocol') !== house.protocol.slice(0, -1) || attributes.get('host') !== house.host)
|
|
62
46
|
return;
|
package/dist/authority-main.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { HouseRefusal, post } from './api.js';
|
|
3
2
|
import { readEnrolment } from './home.js';
|
|
3
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
4
4
|
function unanswered(error) {
|
|
5
5
|
return error instanceof HouseRefusal ? error.status >= 500 : error instanceof TypeError && error.message === 'fetch failed';
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
proxyUnlessRelayed();
|
|
8
8
|
try {
|
|
9
9
|
const { house, credential } = (await readEnrolment());
|
|
10
10
|
try {
|
package/dist/bridge.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
-
import { mkdir } from 'node:fs/promises';
|
|
5
4
|
import { createServer, request as httpRequest } from 'node:http';
|
|
6
5
|
import { request as httpsRequest } from 'node:https';
|
|
7
6
|
import { delimiter, dirname, join } from 'node:path';
|
|
@@ -9,13 +8,12 @@ import { parse } from 'yaml';
|
|
|
9
8
|
import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
|
|
10
9
|
import { loginPath } from './clis.js';
|
|
11
10
|
import { changedPaths, } from './copies.js';
|
|
12
|
-
import { kitHome, readEnrolment } from './home.js';
|
|
11
|
+
import { kitHome, readEnrolment, socketDirectory } from './home.js';
|
|
13
12
|
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
14
|
-
import { operationId
|
|
13
|
+
import { operationId } from './operation.js';
|
|
15
14
|
import { refusalOf, retryable } from './refusals.js';
|
|
16
15
|
import { relayed } from './relay.js';
|
|
17
|
-
const MCP_REQUEST_BYTES =
|
|
18
|
-
const ATTACHMENTS = '/kit/attachments/upload';
|
|
16
|
+
const MCP_REQUEST_BYTES = 4_000_000;
|
|
19
17
|
export async function received(request) {
|
|
20
18
|
let text = '';
|
|
21
19
|
request.setEncoding('utf8');
|
|
@@ -68,11 +66,6 @@ function resendAfter(forwarded, attempt) {
|
|
|
68
66
|
const seconds = pendingSeconds(forwarded);
|
|
69
67
|
return seconds === null ? null : seconds * 1000;
|
|
70
68
|
}
|
|
71
|
-
function versioned(conversationId, text) {
|
|
72
|
-
const file = JSON.parse(text);
|
|
73
|
-
const identity = JSON.stringify([conversationId, file.room_ref ?? null, file.name, file.sha256]);
|
|
74
|
-
return JSON.stringify({ ...file, version: uuidOf(createHash('sha256').update(identity).digest(), 8) });
|
|
75
|
-
}
|
|
76
69
|
function editAnswer(forwarded) {
|
|
77
70
|
const answer = answered(forwarded);
|
|
78
71
|
if ('refused' in answer)
|
|
@@ -88,7 +81,7 @@ function editAnswer(forwarded) {
|
|
|
88
81
|
export async function openBridge(house, copies, conversationId, signal) {
|
|
89
82
|
const path = [join(kitHome(), 'shim'), dirname(process.execPath), await loginPath()].join(delimiter);
|
|
90
83
|
const origin = (await readEnrolment()).house;
|
|
91
|
-
const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
|
|
84
|
+
const { credential } = await retrying(() => house.post(`/kit/conversations/${conversationId}/credential`, {}, signal), signal);
|
|
92
85
|
const closed = new AbortController();
|
|
93
86
|
const sent = (message, operation) => callHouse(origin, credential, message, operation, closed.signal);
|
|
94
87
|
const send = async (message, operation) => {
|
|
@@ -103,7 +96,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
103
96
|
const after = resendAfter(forwarded, attempt);
|
|
104
97
|
if (after === null)
|
|
105
98
|
return forwarded;
|
|
106
|
-
// A
|
|
99
|
+
// A Tool call House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
|
|
107
100
|
await delay(after, undefined, { signal: closed.signal });
|
|
108
101
|
}
|
|
109
102
|
};
|
|
@@ -141,32 +134,9 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
141
134
|
const held = JSON.parse(text).transfer;
|
|
142
135
|
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
143
136
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.post('/kit/tools/mutations', {}, closed.signal)
|
|
148
|
-
.then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
|
|
149
|
-
try {
|
|
150
|
-
return (await mutations).has(tool);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
mutations = null;
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
const forward = async (path, text) => {
|
|
158
|
-
if (path === '/') {
|
|
159
|
-
const message = JSON.parse(text);
|
|
160
|
-
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
161
|
-
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
162
|
-
}
|
|
163
|
-
const answer = await fetch(relayed(new URL(path, origin)), {
|
|
164
|
-
method: 'POST',
|
|
165
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
166
|
-
body: versioned(conversationId, text),
|
|
167
|
-
signal: closed.signal,
|
|
168
|
-
});
|
|
169
|
-
return { status: answer.status, text: await answer.text() };
|
|
137
|
+
const forward = (text) => {
|
|
138
|
+
const message = JSON.parse(text);
|
|
139
|
+
return send(message, message.method === 'tools/call' ? operationId() : null);
|
|
170
140
|
};
|
|
171
141
|
const caller = {
|
|
172
142
|
key: `conversation:${conversationId}`,
|
|
@@ -188,15 +158,15 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
188
158
|
const { cwd, commit } = JSON.parse(text);
|
|
189
159
|
return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
|
|
190
160
|
}
|
|
161
|
+
if (path === '/git/fetch')
|
|
162
|
+
return { status: 200, text: JSON.stringify(await copies.fetch()) };
|
|
191
163
|
if (path === '/git/observed') {
|
|
192
164
|
await copies.observed(JSON.parse(text));
|
|
193
165
|
return { status: 200, text: '{}' };
|
|
194
166
|
}
|
|
195
|
-
return path === '/'
|
|
167
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
196
168
|
};
|
|
197
|
-
const
|
|
198
|
-
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
199
|
-
const socketPath = join(sockets, `${randomUUID()}.sock`);
|
|
169
|
+
const socketPath = join(socketDirectory(), `${randomUUID()}.sock`);
|
|
200
170
|
const bridge = createServer(async (request, response) => {
|
|
201
171
|
try {
|
|
202
172
|
const path = request.url ?? '';
|
|
@@ -212,7 +182,8 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
212
182
|
response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
|
|
213
183
|
return;
|
|
214
184
|
}
|
|
215
|
-
|
|
185
|
+
const verb = request.url === '/git/push' || request.url === '/git/fetch';
|
|
186
|
+
response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
|
|
216
187
|
}
|
|
217
188
|
});
|
|
218
189
|
bridge.listen(socketPath);
|
|
@@ -239,7 +210,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
239
210
|
GIT_CONFIG_VALUE_0: app,
|
|
240
211
|
},
|
|
241
212
|
tool: async (name, args) => {
|
|
242
|
-
const forwarded = await forward(
|
|
213
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
243
214
|
if (forwarded.status !== 200)
|
|
244
215
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
245
216
|
const answer = JSON.parse(forwarded.text);
|
package/dist/building.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
import { Transform } from 'node:stream';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { readEnrolment } from './home.js';
|
|
7
|
+
import { relayed } from './relay.js';
|
|
8
|
+
const MANIFEST = 'bootstrap.json';
|
|
9
|
+
async function served(name, signal) {
|
|
10
|
+
const enrolment = await readEnrolment();
|
|
11
|
+
if (enrolment === null)
|
|
12
|
+
throw new Error('this Kit is connected to no House');
|
|
13
|
+
const answer = await fetch(relayed(new URL(`/kit-installer/${encodeURIComponent(name)}`, enrolment.house)), { signal });
|
|
14
|
+
if (answer.status === 200 && answer.body !== null)
|
|
15
|
+
return answer;
|
|
16
|
+
await answer.body?.cancel();
|
|
17
|
+
throw new Error(`House answered ${answer.status} for ${name}`);
|
|
18
|
+
}
|
|
19
|
+
export async function servedDigest(name, signal) {
|
|
20
|
+
const manifest = (await (await served(MANIFEST, signal)).json());
|
|
21
|
+
const sha256 = manifest.files?.[name]?.sha256;
|
|
22
|
+
return typeof sha256 === 'string' ? sha256 : null;
|
|
23
|
+
}
|
|
24
|
+
export async function downloadServed(name, sha256, destination, signal, most = Infinity) {
|
|
25
|
+
const answer = await served(name, signal);
|
|
26
|
+
const digest = createHash('sha256');
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
const hashed = new Transform({
|
|
29
|
+
transform(chunk, _encoding, done) {
|
|
30
|
+
bytes += chunk.length;
|
|
31
|
+
if (bytes > most)
|
|
32
|
+
return done(new Error(`${name} is larger than its pinned ${most} bytes`));
|
|
33
|
+
digest.update(chunk);
|
|
34
|
+
done(null, chunk);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await pipeline(answer.body, hashed, createWriteStream(destination), { signal });
|
|
39
|
+
if (digest.digest('hex') !== sha256)
|
|
40
|
+
throw new Error(`${name} does not match its sha256`);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await rm(destination, { force: true });
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/calls.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { kitHome } from './home.js';
|
|
4
|
+
function logged(error) {
|
|
5
|
+
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
6
|
+
}
|
|
7
|
+
export class Calls {
|
|
8
|
+
house;
|
|
9
|
+
changed;
|
|
10
|
+
carried = new Map();
|
|
11
|
+
path = join(kitHome(), 'call-results.json');
|
|
12
|
+
results;
|
|
13
|
+
returned = new Set();
|
|
14
|
+
open = 0;
|
|
15
|
+
constructor(house, changed) {
|
|
16
|
+
this.house = house;
|
|
17
|
+
this.changed = changed;
|
|
18
|
+
this.results = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
|
|
19
|
+
}
|
|
20
|
+
keepResult(callId, result) {
|
|
21
|
+
if (this.returned.has(callId))
|
|
22
|
+
return;
|
|
23
|
+
this.results.set(callId, result);
|
|
24
|
+
this.save();
|
|
25
|
+
}
|
|
26
|
+
idle() {
|
|
27
|
+
return this.open === 0;
|
|
28
|
+
}
|
|
29
|
+
carry(callId, work) {
|
|
30
|
+
const known = this.carried.get(callId);
|
|
31
|
+
const result = this.results.get(callId);
|
|
32
|
+
const carried = known ?? (result === undefined ? work() : Promise.resolve({ result }));
|
|
33
|
+
if (known === undefined) {
|
|
34
|
+
this.carried.set(callId, carried);
|
|
35
|
+
carried.catch(() => this.carried.delete(callId));
|
|
36
|
+
}
|
|
37
|
+
this.open++;
|
|
38
|
+
this.changed();
|
|
39
|
+
return carried
|
|
40
|
+
.then(async ({ result, returned }) => {
|
|
41
|
+
try {
|
|
42
|
+
await this.house.deliver(`/kit/inputs/${callId}/ack`, result);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.returned.add(callId);
|
|
46
|
+
if (this.results.delete(callId))
|
|
47
|
+
this.save();
|
|
48
|
+
}
|
|
49
|
+
if (known === undefined)
|
|
50
|
+
await returned?.();
|
|
51
|
+
})
|
|
52
|
+
.catch(logged)
|
|
53
|
+
.finally(() => {
|
|
54
|
+
this.open--;
|
|
55
|
+
this.changed();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
save() {
|
|
59
|
+
const temporary = `${this.path}.${process.pid}`;
|
|
60
|
+
writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.results)), { mode: 0o600 });
|
|
61
|
+
renameSync(temporary, this.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/chatgpt.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { placeAppGit } from './app-git.js';
|
|
3
|
+
import { kitHome, readOwnAgent, writeOwnAgent } from './home.js';
|
|
4
|
+
import { accepted, DEFAULT_HOUSE } from './login.js';
|
|
5
|
+
const KEY_SOURCE = 'New key for a script on Connections';
|
|
6
|
+
const USAGE = `usage: kit chatgpt --key <key> [--house <origin>]
|
|
7
|
+
--key <key> a key from ${KEY_SOURCE}
|
|
8
|
+
--house <origin> the House of the key, ${DEFAULT_HOUSE} unless one is stored
|
|
9
|
+
`;
|
|
10
|
+
export async function chatgpt(argv) {
|
|
11
|
+
const { values } = parseArgs({
|
|
12
|
+
args: argv,
|
|
13
|
+
options: { key: { type: 'string' }, house: { type: 'string' }, help: { type: 'boolean', default: false } },
|
|
14
|
+
});
|
|
15
|
+
if (values.help) {
|
|
16
|
+
process.stdout.write(USAGE);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (values.key === undefined)
|
|
20
|
+
throw new Error(`pass --key with a key from ${KEY_SOURCE}`);
|
|
21
|
+
const before = await readOwnAgent();
|
|
22
|
+
const house = values.house ?? before?.house ?? DEFAULT_HOUSE;
|
|
23
|
+
if (!(await accepted(house, values.key)))
|
|
24
|
+
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
25
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
26
|
+
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
27
|
+
await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
28
|
+
if (before !== null && before.credential !== values.key) {
|
|
29
|
+
process.stdout.write('This replaced the key this Kit home held; give each session its own HOUSE_KIT_HOME to keep its own key.\n');
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
32
|
+
}
|
package/dist/clis-main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { post } from './api.js';
|
|
3
2
|
import { CLIS, below, locate, versionOf } from './clis.js';
|
|
4
3
|
import { readEnrolment } from './home.js';
|
|
5
|
-
|
|
4
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
5
|
+
proxyUnlessRelayed();
|
|
6
6
|
try {
|
|
7
7
|
const { house, credential } = (await readEnrolment());
|
|
8
8
|
const { agents } = await post(house, '/kit/agents/desired', {}, credential);
|
package/dist/clis.js
CHANGED
|
@@ -1,24 +1,92 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { constants, readFileSync } from 'node:fs';
|
|
4
|
+
import { access, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
7
|
+
import { downloadServed, servedDigest } from './building.js';
|
|
6
8
|
import { kitHome } from './home.js';
|
|
7
|
-
|
|
9
|
+
import { childEnvironment } from './relay.js';
|
|
10
|
+
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
11
|
+
function houseRule(houses) {
|
|
12
|
+
return `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`;
|
|
13
|
+
}
|
|
14
|
+
const RAW_SUBTYPES = [
|
|
15
|
+
'background_tasks_changed',
|
|
16
|
+
'task_started',
|
|
17
|
+
'task_updated',
|
|
18
|
+
'task_notification',
|
|
19
|
+
'session_state_changed',
|
|
20
|
+
'hook_started',
|
|
21
|
+
'hook_response',
|
|
22
|
+
];
|
|
23
|
+
async function executable(path) {
|
|
24
|
+
try {
|
|
25
|
+
await access(path, constants.X_OK);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function listedHouses(file) {
|
|
33
|
+
let text;
|
|
34
|
+
try {
|
|
35
|
+
text = await readFile(file, 'utf8');
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT')
|
|
39
|
+
return [];
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const listed = JSON.parse(HOUSE_EXECUTABLES.exec(text)?.[1] ?? '[]');
|
|
44
|
+
return Array.isArray(listed) ? listed.filter((house) => typeof house === 'string') : [];
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function allowHouseInCodex(path) {
|
|
51
|
+
const candidates = [...new Set(path.split(delimiter).filter((directory) => isAbsolute(directory)))].map((directory) => join(directory, 'house'));
|
|
8
52
|
const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
|
|
53
|
+
const file = join(rules, 'house.rules');
|
|
54
|
+
const houses = [];
|
|
55
|
+
for (const house of [...(await listedHouses(file)), ...candidates]) {
|
|
56
|
+
if (!houses.includes(house) && (await executable(house)))
|
|
57
|
+
houses.push(house);
|
|
58
|
+
}
|
|
9
59
|
await mkdir(rules, { recursive: true });
|
|
10
|
-
|
|
60
|
+
const temporary = `${file}.${randomUUID()}`;
|
|
61
|
+
await writeFile(temporary, houseRule(houses));
|
|
62
|
+
await rename(temporary, file);
|
|
11
63
|
}
|
|
12
64
|
async function nothing() { }
|
|
65
|
+
function answered(error) {
|
|
66
|
+
return typeof error === 'object' && error !== null ? error : {};
|
|
67
|
+
}
|
|
68
|
+
function bridgeInClaudeSandbox(bridge) {
|
|
69
|
+
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
70
|
+
}
|
|
13
71
|
export const CLIS = {
|
|
14
72
|
'codex-acp': {
|
|
15
73
|
bin: 'codex',
|
|
16
|
-
minimum: '0.
|
|
74
|
+
minimum: '0.160.1',
|
|
17
75
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
18
|
-
adapter: {
|
|
76
|
+
adapter: {
|
|
77
|
+
package: '@agentclientprotocol/codex-acp',
|
|
78
|
+
bin: 'codex-acp',
|
|
79
|
+
executable: 'CODEX_PATH',
|
|
80
|
+
env: { CODEX_CONFIG: JSON.stringify({ features: { shell_zsh_fork: true } }) },
|
|
81
|
+
},
|
|
19
82
|
args: [],
|
|
20
83
|
fullAccess: 'agent-full-access',
|
|
21
|
-
sessionMeta: {},
|
|
84
|
+
sessionMeta: () => ({}),
|
|
85
|
+
unknownSession: (error) => {
|
|
86
|
+
const { code, data } = answered(error);
|
|
87
|
+
return code === -32603 && typeof data?.details === 'string' && data.details.startsWith('no rollout found');
|
|
88
|
+
},
|
|
89
|
+
renames: true,
|
|
22
90
|
allowHouse: allowHouseInCodex,
|
|
23
91
|
signedIn: { command: ['login', 'status'] },
|
|
24
92
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
@@ -26,7 +94,7 @@ export const CLIS = {
|
|
|
26
94
|
},
|
|
27
95
|
'claude-agent-acp': {
|
|
28
96
|
bin: 'claude',
|
|
29
|
-
minimum: '2.1.
|
|
97
|
+
minimum: '2.1.303',
|
|
30
98
|
install: 'curl -fsSL https://claude.ai/install.sh | bash',
|
|
31
99
|
adapter: {
|
|
32
100
|
package: '@agentclientprotocol/claude-agent-acp',
|
|
@@ -36,12 +104,19 @@ export const CLIS = {
|
|
|
36
104
|
},
|
|
37
105
|
args: [],
|
|
38
106
|
fullAccess: 'bypassPermissions',
|
|
39
|
-
sessionMeta: {
|
|
107
|
+
sessionMeta: (bridge, title) => ({
|
|
40
108
|
claudeCode: {
|
|
41
|
-
options: {
|
|
42
|
-
|
|
109
|
+
options: {
|
|
110
|
+
allowedTools: ['Bash(house:*)'],
|
|
111
|
+
includeHookEvents: true,
|
|
112
|
+
title,
|
|
113
|
+
settings: { sandbox: { network: bridgeInClaudeSandbox(bridge) } },
|
|
114
|
+
},
|
|
115
|
+
emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
|
|
43
116
|
},
|
|
44
|
-
},
|
|
117
|
+
}),
|
|
118
|
+
unknownSession: (error) => answered(error).code === -32002,
|
|
119
|
+
renames: false,
|
|
45
120
|
allowHouse: nothing,
|
|
46
121
|
signedIn: { command: ['auth', 'status'] },
|
|
47
122
|
login: { args: ['auth', 'login', '--claudeai'], code: 'collect', rejected: 'Invalid code' },
|
|
@@ -52,9 +127,11 @@ export const CLIS = {
|
|
|
52
127
|
minimum: '1.0.46',
|
|
53
128
|
install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
54
129
|
adapter: null,
|
|
55
|
-
args: ['agent', '--always-approve', '--no-leader', 'stdio'],
|
|
130
|
+
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
56
131
|
fullAccess: null,
|
|
57
|
-
sessionMeta: {},
|
|
132
|
+
sessionMeta: () => ({}),
|
|
133
|
+
unknownSession: (error) => answered(error).data?.code === 'FS_NOT_FOUND',
|
|
134
|
+
renames: false,
|
|
58
135
|
allowHouse: nothing,
|
|
59
136
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
60
137
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
@@ -62,8 +139,7 @@ export const CLIS = {
|
|
|
62
139
|
},
|
|
63
140
|
};
|
|
64
141
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
65
|
-
export const
|
|
66
|
-
const PROXY = /^https?_proxy$/i;
|
|
142
|
+
export const KIT_RELEASE = KIT_PACKAGE.version;
|
|
67
143
|
const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
|
|
68
144
|
// An install still running after fifteen minutes is ended and reported as failed, so a hung download cannot hold its CLI's report.
|
|
69
145
|
const INSTALL_MS = 15 * 60_000;
|
|
@@ -85,15 +161,12 @@ async function installedAdapter(kind) {
|
|
|
85
161
|
return null;
|
|
86
162
|
}
|
|
87
163
|
}
|
|
88
|
-
export function withoutProxy() {
|
|
89
|
-
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
90
|
-
}
|
|
91
164
|
export function run(command, args, timeoutMs, detached = false, signal) {
|
|
92
165
|
return new Promise((resolve) => {
|
|
93
166
|
const child = spawn(command, args, {
|
|
94
167
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
95
168
|
timeout: timeoutMs,
|
|
96
|
-
env:
|
|
169
|
+
env: childEnvironment(),
|
|
97
170
|
detached,
|
|
98
171
|
signal: detached ? undefined : signal,
|
|
99
172
|
});
|
|
@@ -127,15 +200,37 @@ export function run(command, args, timeoutMs, detached = false, signal) {
|
|
|
127
200
|
export function failureOf(ran, program) {
|
|
128
201
|
return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
|
|
129
202
|
}
|
|
203
|
+
export function adapterArchive(kind) {
|
|
204
|
+
return `${kind}-${KIT_PACKAGE.peerDependencies[CLIS[kind].adapter.package]}.tar.gz`;
|
|
205
|
+
}
|
|
130
206
|
export async function installAdapter(kind, signal) {
|
|
131
207
|
const adapter = CLIS[kind].adapter;
|
|
132
208
|
if (adapter === null)
|
|
133
209
|
return null;
|
|
134
|
-
|
|
135
|
-
if ((await installedAdapter(kind)) === release)
|
|
210
|
+
if ((await installedAdapter(kind)) === KIT_PACKAGE.peerDependencies[adapter.package])
|
|
136
211
|
return null;
|
|
137
|
-
const
|
|
138
|
-
|
|
212
|
+
const name = adapterArchive(kind);
|
|
213
|
+
const archive = `${prefix(kind)}.tar.gz`;
|
|
214
|
+
const deadline = AbortSignal.timeout(INSTALL_MS);
|
|
215
|
+
const bounded = signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
|
|
216
|
+
try {
|
|
217
|
+
const sha256 = await servedDigest(name, bounded);
|
|
218
|
+
if (sha256 === null)
|
|
219
|
+
return `House serves no ${name}`;
|
|
220
|
+
await rm(prefix(kind), { recursive: true, force: true });
|
|
221
|
+
await mkdir(prefix(kind), { recursive: true });
|
|
222
|
+
await downloadServed(name, sha256, archive, bounded);
|
|
223
|
+
const ran = await run('tar', ['-xzf', archive, '-C', prefix(kind)], INSTALL_MS, true, signal);
|
|
224
|
+
return ran.status === 0 ? null : failureOf(ran, 'tar');
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (deadline.aborted && signal?.aborted !== true)
|
|
228
|
+
return `${name} did not arrive within ${INSTALL_MS / 1000} seconds`;
|
|
229
|
+
return error instanceof Error ? error.message : String(error);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
await rm(archive, { force: true });
|
|
233
|
+
}
|
|
139
234
|
}
|
|
140
235
|
export async function installCli(kind, signal) {
|
|
141
236
|
const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
|
|
@@ -150,15 +245,24 @@ const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$H
|
|
|
150
245
|
function inLoginShell(script, argument = '', signal) {
|
|
151
246
|
return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true, signal);
|
|
152
247
|
}
|
|
153
|
-
export async function
|
|
154
|
-
const ran = await inLoginShell(LOOKUP,
|
|
248
|
+
export async function onLoginPath(program, signal) {
|
|
249
|
+
const ran = await inLoginShell(LOOKUP, program, signal);
|
|
155
250
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
|
|
156
251
|
}
|
|
252
|
+
export function locate(kind, signal) {
|
|
253
|
+
return onLoginPath(CLIS[kind].bin, signal);
|
|
254
|
+
}
|
|
157
255
|
export async function loginPath() {
|
|
158
256
|
const marker = 'house-kit-path ';
|
|
159
257
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
160
258
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
161
259
|
}
|
|
260
|
+
export async function writeCodexRule() {
|
|
261
|
+
const shell = await loginPath().catch(() => '');
|
|
262
|
+
await allowHouseInCodex([dirname(process.execPath), process.env.PATH ?? '', shell].join(delimiter)).catch((error) => {
|
|
263
|
+
throw new Error(`Codex's rule for house could not be written: ${error.message}`);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
162
266
|
export async function versionOf(path, signal) {
|
|
163
267
|
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
164
268
|
}
|