@agentshouse/kit 0.2.1-alpha.9 → 0.2.1-c14f8446ba284
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 +37 -20
- package/dist/abort.js +9 -0
- package/dist/acp.js +18 -75
- package/dist/agents.js +246 -52
- package/dist/api.js +36 -4
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +10 -14
- package/dist/bridge.js +99 -70
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +171 -71
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +324 -199
- package/dist/copies.js +70 -68
- package/dist/end.js +8 -0
- package/dist/events.js +521 -0
- package/dist/files.js +16 -1
- package/dist/git-main.js +2 -2
- package/dist/git.js +6 -2
- package/dist/home.js +32 -3
- package/dist/house-main.js +117 -44
- package/dist/instructions.js +13 -12
- package/dist/kit-main.js +7 -1
- package/dist/login.js +39 -94
- package/dist/logout.js +21 -0
- package/dist/mcp.js +39 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +64 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +10 -0
- package/dist/resident.js +46 -21
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +7 -2
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +48 -11
- package/dist/skills.js +5 -10
- package/dist/stream.js +31 -6
- package/dist/transcription.js +170 -0
- package/dist/update.js +119 -0
- package/dist/work.js +348 -0
- package/how-we-work.md +10 -12
- package/package.json +4 -7
- package/dist/blocks.js +0 -51
package/dist/app-git.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { extname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { run } from './git.js';
|
|
4
|
+
import { kitHome, readOwnAgent } from './home.js';
|
|
5
|
+
import { quoted } from './shell.js';
|
|
6
|
+
function section(house) {
|
|
7
|
+
return `credential.${new URL('/app/', house).href}`;
|
|
8
|
+
}
|
|
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
|
+
async function configured(args) {
|
|
14
|
+
try {
|
|
15
|
+
return await run(kitHome(), ['config', '--global', ...args]);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT')
|
|
19
|
+
return null;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function forget(house) {
|
|
24
|
+
await configured(['--remove-section', section(house)]);
|
|
25
|
+
}
|
|
26
|
+
async function register(house) {
|
|
27
|
+
await forget(house);
|
|
28
|
+
for (const value of ['', helper()]) {
|
|
29
|
+
const ran = await configured(['--add', `${section(house)}.helper`, value]);
|
|
30
|
+
if (ran === null)
|
|
31
|
+
return;
|
|
32
|
+
if (ran.status !== 0)
|
|
33
|
+
throw new Error(`git config failed: ${ran.stderr.trim()}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
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
|
+
export async function answerGitCredential(operation) {
|
|
48
|
+
let asked = '';
|
|
49
|
+
for await (const chunk of process.stdin)
|
|
50
|
+
asked += String(chunk);
|
|
51
|
+
if (operation !== 'get')
|
|
52
|
+
return;
|
|
53
|
+
const attributes = new Map(asked
|
|
54
|
+
.split('\n')
|
|
55
|
+
.filter((line) => line.includes('='))
|
|
56
|
+
.map((line) => [line.slice(0, line.indexOf('=')), line.slice(line.indexOf('=') + 1)]));
|
|
57
|
+
const ownAgent = await readOwnAgent();
|
|
58
|
+
if (ownAgent === null)
|
|
59
|
+
return;
|
|
60
|
+
const house = new URL(ownAgent.house);
|
|
61
|
+
if (attributes.get('protocol') !== house.protocol.slice(0, -1) || attributes.get('host') !== house.host)
|
|
62
|
+
return;
|
|
63
|
+
process.stdout.write(`username=house\npassword=${ownAgent.credential}\n`);
|
|
64
|
+
}
|
package/dist/bootstrap.json
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
3
|
-
"version": "0.2.1-
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"version": "0.2.1-c14f8446ba284",
|
|
4
4
|
"hosts": {
|
|
5
|
+
"macos": {
|
|
6
|
+
"asset": {
|
|
7
|
+
"name": "connect-linux.sh",
|
|
8
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c14f8446ba284/connect-linux.sh"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
5
11
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.9/connect-linux.sh)",
|
|
7
12
|
"asset": {
|
|
8
13
|
"name": "connect-linux.sh",
|
|
9
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
10
|
-
},
|
|
11
|
-
"workspace": {
|
|
12
|
-
"argument": "--workspace",
|
|
13
|
-
"quoting": "posix"
|
|
14
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c14f8446ba284/connect-linux.sh"
|
|
14
15
|
}
|
|
15
16
|
},
|
|
16
17
|
"windows": {
|
|
17
|
-
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.9/connect-windows.ps1)))",
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
21
|
-
},
|
|
22
|
-
"workspace": {
|
|
23
|
-
"argument": "--workspace",
|
|
24
|
-
"quoting": "powershell"
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c14f8446ba284/connect-windows.ps1"
|
|
25
21
|
}
|
|
26
22
|
}
|
|
27
23
|
}
|
package/dist/bridge.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
4
|
import { mkdir } from 'node:fs/promises';
|
|
4
5
|
import { createServer, request as httpRequest } from 'node:http';
|
|
5
6
|
import { request as httpsRequest } from 'node:https';
|
|
6
|
-
import { delimiter, join } from 'node:path';
|
|
7
|
+
import { delimiter, dirname, join } from 'node:path';
|
|
7
8
|
import { parse } from 'yaml';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
|
|
10
|
+
import { loginPath } from './clis.js';
|
|
11
|
+
import { changedPaths, } from './copies.js';
|
|
10
12
|
import { kitHome, readEnrolment } from './home.js';
|
|
13
|
+
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
11
14
|
import { operationId } from './operation.js';
|
|
12
|
-
|
|
15
|
+
import { refusalOf, retryable } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
13
17
|
const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
14
|
-
const FORWARDED = new Set(['/', '/kit/attachments/upload']);
|
|
15
18
|
export async function received(request) {
|
|
16
19
|
let text = '';
|
|
17
20
|
request.setEncoding('utf8');
|
|
@@ -19,7 +22,8 @@ export async function received(request) {
|
|
|
19
22
|
text += chunk;
|
|
20
23
|
return text;
|
|
21
24
|
}
|
|
22
|
-
function relay(request, response,
|
|
25
|
+
function relay(request, response, house, authorization) {
|
|
26
|
+
const target = relayed(house);
|
|
23
27
|
const send = target.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
24
28
|
const forwarded = send(target, {
|
|
25
29
|
method: request.method,
|
|
@@ -31,16 +35,37 @@ function relay(request, response, target, authorization) {
|
|
|
31
35
|
forwarded.on('error', () => response.destroy());
|
|
32
36
|
request.pipe(forwarded);
|
|
33
37
|
}
|
|
38
|
+
function refusalIn(text, status) {
|
|
39
|
+
return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
|
|
40
|
+
}
|
|
34
41
|
function answered(forwarded) {
|
|
35
|
-
if (forwarded.status >= 500)
|
|
36
|
-
throw new Error(`House answered ${forwarded.status}`);
|
|
37
42
|
if (forwarded.status !== 200)
|
|
38
|
-
return { refused:
|
|
43
|
+
return { refused: refusalIn(forwarded.text, forwarded.status) };
|
|
39
44
|
const answer = JSON.parse(forwarded.text);
|
|
40
45
|
if (answer.error !== undefined)
|
|
41
|
-
return { refused:
|
|
46
|
+
return { refused: refusalIn(answer.error.message, forwarded.status) };
|
|
42
47
|
const text = answer.result.content.map((content) => content.text).join('\n');
|
|
43
|
-
return answer.result.isError === true ? { refused:
|
|
48
|
+
return answer.result.isError === true ? { refused: refusalIn(text, forwarded.status) } : { text };
|
|
49
|
+
}
|
|
50
|
+
function pendingSeconds(forwarded) {
|
|
51
|
+
if (forwarded.status !== 200)
|
|
52
|
+
return null;
|
|
53
|
+
const { result } = JSON.parse(forwarded.text);
|
|
54
|
+
if (result === undefined || result.isError === true)
|
|
55
|
+
return null;
|
|
56
|
+
const text = result.content.map((content) => content.text).join('\n');
|
|
57
|
+
if (!text.startsWith('{"pending":'))
|
|
58
|
+
return null;
|
|
59
|
+
const seconds = JSON.parse(text).pending.retry_after;
|
|
60
|
+
return typeof seconds === 'number' ? seconds : null;
|
|
61
|
+
}
|
|
62
|
+
function resendAfter(forwarded, attempt) {
|
|
63
|
+
if (forwarded === null)
|
|
64
|
+
return retryDelay(attempt);
|
|
65
|
+
if (forwarded.status !== 200)
|
|
66
|
+
return retryable(forwarded.text) === false ? null : retryDelay(attempt);
|
|
67
|
+
const seconds = pendingSeconds(forwarded);
|
|
68
|
+
return seconds === null ? null : seconds * 1000;
|
|
44
69
|
}
|
|
45
70
|
function editAnswer(forwarded) {
|
|
46
71
|
const answer = answered(forwarded);
|
|
@@ -50,44 +75,33 @@ function editAnswer(forwarded) {
|
|
|
50
75
|
return {
|
|
51
76
|
accepted: results.flatMap((result) => [
|
|
52
77
|
...(result.from === undefined ? [] : [{ path: result.from, revision: null, content: null }]),
|
|
53
|
-
{ path: result.path, revision: result.
|
|
78
|
+
{ path: result.path, revision: result.revision ?? null },
|
|
54
79
|
]),
|
|
55
80
|
};
|
|
56
81
|
}
|
|
57
82
|
export async function openBridge(house, copies, conversationId, signal) {
|
|
83
|
+
const path = [join(kitHome(), 'shim'), dirname(process.execPath), await loginPath()].join(delimiter);
|
|
58
84
|
const origin = (await readEnrolment()).house;
|
|
59
85
|
const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
|
|
60
86
|
const closed = new AbortController();
|
|
61
|
-
const
|
|
62
|
-
...message,
|
|
63
|
-
params: {
|
|
64
|
-
...message.params,
|
|
65
|
-
_meta: {
|
|
66
|
-
'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
|
|
67
|
-
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
68
|
-
'io.modelcontextprotocol/clientCapabilities': {},
|
|
69
|
-
...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
});
|
|
87
|
+
const sent = (message, operation) => callHouse(origin, credential, message, operation, closed.signal);
|
|
73
88
|
const send = async (message, operation) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
signal: closed.signal
|
|
87
|
-
}
|
|
88
|
-
return { status: answer.status, text: await answer.text() };
|
|
89
|
+
if (operation === null)
|
|
90
|
+
return sent(message, null);
|
|
91
|
+
for (let attempt = 0;; attempt++) {
|
|
92
|
+
const forwarded = await sent(message, operation).catch((error) => {
|
|
93
|
+
if (closed.signal.aborted)
|
|
94
|
+
throw error;
|
|
95
|
+
return null;
|
|
96
|
+
});
|
|
97
|
+
const after = resendAfter(forwarded, attempt);
|
|
98
|
+
if (after === null)
|
|
99
|
+
return forwarded;
|
|
100
|
+
// A write House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
|
|
101
|
+
await delay(after, undefined, { signal: closed.signal });
|
|
102
|
+
}
|
|
89
103
|
};
|
|
90
|
-
const call = (name, args) => (
|
|
104
|
+
const call = (name, args) => rpc('tools/call', { name, arguments: args });
|
|
91
105
|
const staged = async (operation, changes) => {
|
|
92
106
|
const bytes = Buffer.from(JSON.stringify({ changes }));
|
|
93
107
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
@@ -98,42 +112,53 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
98
112
|
if ('refused' in prepared)
|
|
99
113
|
return prepared;
|
|
100
114
|
const { transfer } = parse(prepared.text);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
let text;
|
|
116
|
+
try {
|
|
117
|
+
text = await retrying(async () => {
|
|
118
|
+
const posted = await fetch(relayed(transfer.url), {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
121
|
+
body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
|
|
122
|
+
signal: closed.signal,
|
|
123
|
+
});
|
|
124
|
+
const answer = await posted.text();
|
|
125
|
+
if (!posted.ok)
|
|
126
|
+
throw new HouseRefusal('/', posted.status, answer);
|
|
127
|
+
return answer;
|
|
128
|
+
}, closed.signal);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (!finalRefusal(error))
|
|
132
|
+
throw error;
|
|
133
|
+
return { refused: refusalIn(error.text, error.status) };
|
|
134
|
+
}
|
|
112
135
|
const held = JSON.parse(text).transfer;
|
|
113
136
|
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
114
137
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
138
|
+
let mutations = null;
|
|
139
|
+
const writes = async (tool) => {
|
|
140
|
+
mutations ??= house
|
|
141
|
+
.post('/kit/tools/mutations', {}, closed.signal)
|
|
142
|
+
.then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
|
|
143
|
+
try {
|
|
144
|
+
return (await mutations).has(tool);
|
|
122
145
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
catch (error) {
|
|
147
|
+
mutations = null;
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const forward = async (text) => {
|
|
152
|
+
const message = JSON.parse(text);
|
|
153
|
+
const tool = message.method === 'tools/call' ? String(message.params.name) : null;
|
|
154
|
+
return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
|
|
130
155
|
};
|
|
131
156
|
const caller = {
|
|
132
157
|
key: `conversation:${conversationId}`,
|
|
133
158
|
submit: async (operation, changes, held, retain) => {
|
|
134
159
|
const inline = call('edit', { changes });
|
|
135
160
|
let upload = held;
|
|
136
|
-
if (upload === undefined && Buffer.byteLength(
|
|
161
|
+
if (upload === undefined && Buffer.byteLength(mcpBody(inline, operation)) > MCP_REQUEST_BYTES) {
|
|
137
162
|
const prepared = await staged(operation, changes);
|
|
138
163
|
if ('refused' in prepared)
|
|
139
164
|
return prepared;
|
|
@@ -152,7 +177,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
152
177
|
await copies.observed(JSON.parse(text));
|
|
153
178
|
return { status: 200, text: '{}' };
|
|
154
179
|
}
|
|
155
|
-
return
|
|
180
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
156
181
|
};
|
|
157
182
|
const sockets = join(kitHome(), 'bridges');
|
|
158
183
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
@@ -168,7 +193,11 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
168
193
|
response.writeHead(answered.status, { 'content-type': 'application/json' }).end(answered.text);
|
|
169
194
|
}
|
|
170
195
|
catch (error) {
|
|
171
|
-
|
|
196
|
+
if (error instanceof HouseRefusal) {
|
|
197
|
+
response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
response.writeHead(502).end(request.url === '/git/push' && error instanceof Error ? error.message : UNREACHABLE);
|
|
172
201
|
}
|
|
173
202
|
});
|
|
174
203
|
bridge.listen(socketPath);
|
|
@@ -189,13 +218,13 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
189
218
|
return {
|
|
190
219
|
env: {
|
|
191
220
|
HOUSE_BRIDGE: socketPath,
|
|
192
|
-
PATH:
|
|
221
|
+
PATH: path,
|
|
193
222
|
GIT_CONFIG_COUNT: '1',
|
|
194
223
|
GIT_CONFIG_KEY_0: `url.${proxy}/app/.insteadOf`,
|
|
195
224
|
GIT_CONFIG_VALUE_0: app,
|
|
196
225
|
},
|
|
197
226
|
tool: async (name, args) => {
|
|
198
|
-
const forwarded = await forward(
|
|
227
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
199
228
|
if (forwarded.status !== 200)
|
|
200
229
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
201
230
|
const answer = JSON.parse(forwarded.text);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
import { post } from './api.js';
|
|
3
|
+
import { CLIS, below, locate, versionOf } from './clis.js';
|
|
4
|
+
import { readEnrolment } from './home.js';
|
|
5
|
+
setGlobalProxyFromEnv();
|
|
6
|
+
try {
|
|
7
|
+
const { house, credential } = (await readEnrolment());
|
|
8
|
+
const { agents } = await post(house, '/kit/agents/desired', {}, credential);
|
|
9
|
+
for (const kind of agents) {
|
|
10
|
+
const cli = await locate(kind);
|
|
11
|
+
if (cli === null)
|
|
12
|
+
continue;
|
|
13
|
+
const release = await versionOf(cli);
|
|
14
|
+
if (release === null)
|
|
15
|
+
continue;
|
|
16
|
+
const { bin, minimum } = CLIS[kind];
|
|
17
|
+
process.stdout.write(`${[bin, cli, release, minimum, below(release, minimum) ? 'old' : 'current'].join('\t')}\n`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
process.stderr.write(`kit: ${error.message}\n`);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|