@agentshouse/kit 0.2.1-alpha.1 → 0.2.1-alpha.10
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 +20 -2
- package/dist/acp.js +3 -3
- package/dist/agents.js +6 -0
- package/dist/api.js +2 -0
- package/dist/authority-main.js +22 -0
- package/dist/bootstrap.json +28 -0
- package/dist/bridge.js +133 -43
- package/dist/clis.js +5 -1
- package/dist/configure-main.js +6 -0
- package/dist/conversations.js +37 -14
- package/dist/copies.js +796 -0
- package/dist/enrol-main.js +4 -0
- package/dist/git-main.js +153 -0
- package/dist/git.js +81 -0
- package/dist/home.js +19 -6
- package/dist/house-main.js +30 -15
- package/dist/instructions.js +8 -3
- package/dist/kit-main.js +2 -0
- package/dist/login.js +6 -2
- package/dist/operation.js +9 -0
- package/dist/resident.js +62 -7
- package/dist/sign-in.js +6 -2
- package/dist/skills.js +83 -0
- package/dist/translate.js +51 -0
- package/dist/update.js +116 -0
- package/how-we-work.md +25 -0
- package/package.json +11 -4
- package/skills/ask-matt/LICENSE +21 -0
- package/skills/ask-matt/PHASE-BOUNDARIES.md +55 -0
- package/skills/ask-matt/SKILL.md +91 -0
- package/skills/ask-matt/agents/openai.yaml +4 -0
- package/skills/code-review/LICENSE +21 -0
- package/skills/code-review/SKILL.md +89 -0
- package/skills/code-review/agents/openai.yaml +3 -0
- package/skills/codebase-design/DEEPENING.md +37 -0
- package/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/skills/codebase-design/LICENSE +21 -0
- package/skills/codebase-design/SKILL.md +116 -0
- package/skills/codebase-design/agents/openai.yaml +3 -0
- package/skills/diagnosing-bugs/LICENSE +21 -0
- package/skills/diagnosing-bugs/SKILL.md +140 -0
- package/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/skills/domain-modeling/LICENSE +21 -0
- package/skills/domain-modeling/SKILL.md +76 -0
- package/skills/domain-modeling/agents/openai.yaml +3 -0
- package/skills/grill-me/LICENSE +21 -0
- package/skills/grill-me/SKILL.md +8 -0
- package/skills/grill-me/agents/openai.yaml +4 -0
- package/skills/grill-with-docs/LICENSE +21 -0
- package/skills/grill-with-docs/SKILL.md +8 -0
- package/skills/grill-with-docs/agents/openai.yaml +4 -0
- package/skills/grilling/LICENSE +21 -0
- package/skills/grilling/SKILL.md +30 -0
- package/skills/grilling/agents/openai.yaml +3 -0
- package/skills/handoff/LICENSE +21 -0
- package/skills/handoff/SKILL.md +17 -0
- package/skills/handoff/agents/openai.yaml +4 -0
- package/skills/implement/LICENSE +21 -0
- package/skills/implement/SKILL.md +16 -0
- package/skills/implement/agents/openai.yaml +4 -0
- package/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/skills/improve-codebase-architecture/LICENSE +21 -0
- package/skills/improve-codebase-architecture/SKILL.md +72 -0
- package/skills/improve-codebase-architecture/agents/openai.yaml +4 -0
- package/skills/prototype/LICENSE +21 -0
- package/skills/prototype/LOGIC.md +67 -0
- package/skills/prototype/SKILL.md +28 -0
- package/skills/prototype/UI.md +112 -0
- package/skills/prototype/agents/openai.yaml +3 -0
- package/skills/research/LICENSE +21 -0
- package/skills/research/SKILL.md +14 -0
- package/skills/research/agents/openai.yaml +3 -0
- package/skills/resolving-merge-conflicts/LICENSE +21 -0
- package/skills/resolving-merge-conflicts/SKILL.md +16 -0
- package/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/skills/tdd/LICENSE +21 -0
- package/skills/tdd/SKILL.md +40 -0
- package/skills/tdd/agents/openai.yaml +3 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/tests.md +77 -0
- package/skills/teach/GLOSSARY-FORMAT.md +35 -0
- package/skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/skills/teach/LICENSE +21 -0
- package/skills/teach/MISSION-FORMAT.md +31 -0
- package/skills/teach/RESOURCES-FORMAT.md +32 -0
- package/skills/teach/SKILL.md +141 -0
- package/skills/teach/agents/openai.yaml +4 -0
- package/skills/to-questionnaire/LICENSE +21 -0
- package/skills/to-questionnaire/SKILL.md +55 -0
- package/skills/to-questionnaire/agents/openai.yaml +4 -0
- package/skills/to-spec/LICENSE +21 -0
- package/skills/to-spec/SKILL.md +76 -0
- package/skills/to-spec/agents/openai.yaml +4 -0
- package/skills/to-tickets/LICENSE +21 -0
- package/skills/to-tickets/SKILL.md +106 -0
- package/skills/to-tickets/agents/openai.yaml +4 -0
- package/skills/wait-what/LICENSE +21 -0
- package/skills/wait-what/SKILL.md +8 -0
- package/skills/wait-what/agents/openai.yaml +4 -0
- package/skills/wayfinder/LICENSE +21 -0
- package/skills/wayfinder/SKILL.md +129 -0
- package/skills/wayfinder/agents/openai.yaml +4 -0
- package/skills/writing-for-agents/LICENSE +21 -0
- package/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/skills/writing-for-agents/SKILL.md +83 -0
- package/skills/writing-for-agents/agents/openai.yaml +3 -0
package/README.md
CHANGED
|
@@ -7,7 +7,19 @@ through the agent CLIs you chose and gives each Agent the `house` command.
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
Connect a computer from agents.house. It gives you the one command that
|
|
10
|
-
installs House Kit on that computer and updates it when you run it again.
|
|
10
|
+
installs House Kit on that computer and updates it when you run it again. On
|
|
11
|
+
Linux, macOS and Windows 11 it runs House Kit in one Docker container with
|
|
12
|
+
your workspace, `~/AgentsHouse` unless you pass `--workspace <path>`, mounted
|
|
13
|
+
at `/agents/house`. Append a `kit` or `house` command to the same command to
|
|
14
|
+
run it in that container.
|
|
15
|
+
|
|
16
|
+
On your own Ubuntu 26.04 server, run the Linux command with `--linux` to
|
|
17
|
+
install House Kit natively, with its workspace at `/agents/house`; `kit` and
|
|
18
|
+
`house` are then on the server's `PATH`.
|
|
19
|
+
|
|
20
|
+
House Kit also gives every Agent on the Environment a set of agent skills. Add
|
|
21
|
+
`--no-skills` to the command to leave them out; running it again without the
|
|
22
|
+
flag brings them back.
|
|
11
23
|
|
|
12
24
|
## Commands
|
|
13
25
|
|
|
@@ -16,8 +28,14 @@ Environment's credential in `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is
|
|
|
16
28
|
set. Pass `--manual` to paste the code by hand on a machine without a browser,
|
|
17
29
|
and `--environment <id>` to reconnect an Environment you already have.
|
|
18
30
|
|
|
19
|
-
`kit resident` keeps the Environment connected
|
|
31
|
+
`kit resident` keeps the Environment connected, and keeps the Rooms you chose
|
|
32
|
+
for this Environment as Git repositories under `/agents/house/working-copies`.
|
|
33
|
+
Commit in one and run `house git push --owner` there to send the commit to
|
|
34
|
+
House; on a computer, run it through the install command from the copy's
|
|
35
|
+
directory. Inside an Agent conversation the Agent runs `house git push`.
|
|
20
36
|
|
|
21
37
|
## License
|
|
22
38
|
|
|
23
39
|
MIT
|
|
40
|
+
|
|
41
|
+
The skills come from [mattpocock/skills](https://github.com/mattpocock/skills); thank you, Matt Pocock.
|
package/dist/acp.js
CHANGED
|
@@ -2,13 +2,13 @@ import { ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { Readable, Writable } from 'node:stream';
|
|
5
|
-
import { CLIS, KIT_VERSION, cliCommand } from './clis.js';
|
|
5
|
+
import { CLIS, KIT_VERSION, cliCommand, withoutProxy } from './clis.js';
|
|
6
6
|
export const TURN_STARTED = 'kit/turn_started';
|
|
7
7
|
export const TURN_ENDED = 'kit/turn_ended';
|
|
8
|
-
export async function startAdapter(kind, cwd, app, jobs = () => undefined,
|
|
8
|
+
export async function startAdapter(kind, cwd, app, jobs = () => undefined, added = {}) {
|
|
9
9
|
const child = spawn(cliCommand(kind), CLIS[kind].args, {
|
|
10
10
|
cwd,
|
|
11
|
-
env,
|
|
11
|
+
env: { ...withoutProxy(), ...added },
|
|
12
12
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
13
13
|
detached: true,
|
|
14
14
|
});
|
package/dist/agents.js
CHANGED
|
@@ -18,6 +18,10 @@ async function signedIn(kind) {
|
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
async function sshHostKey() {
|
|
22
|
+
const listed = await run('ssh-keygen', ['-l', '-E', 'sha256', '-f', '/etc/ssh/ssh_host_ed25519_key.pub'], 10_000);
|
|
23
|
+
return listed.status === 0 ? (/^\d+ (SHA256:\S+)/.exec(listed.output)?.[1] ?? null) : null;
|
|
24
|
+
}
|
|
21
25
|
async function operatingSystem() {
|
|
22
26
|
const release = await readFile('/etc/os-release', 'utf8');
|
|
23
27
|
return /^PRETTY_NAME="?([^"\n]*)"?$/m.exec(release)?.[1] ?? release;
|
|
@@ -63,10 +67,12 @@ export class Agents {
|
|
|
63
67
|
for (const [kind, release] of this.releases) {
|
|
64
68
|
agents.push({ kind, release, signed_in: release !== null && (await signedIn(kind)) });
|
|
65
69
|
}
|
|
70
|
+
const hostKey = await sshHostKey();
|
|
66
71
|
await this.house.deliver('/kit/agents/report', {
|
|
67
72
|
os: await operatingSystem(),
|
|
68
73
|
kit_version: KIT_VERSION,
|
|
69
74
|
agents,
|
|
75
|
+
...(hostKey === null ? {} : { ssh_host_key: hostKey }),
|
|
70
76
|
});
|
|
71
77
|
}
|
|
72
78
|
async install(kinds) {
|
package/dist/api.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readEnrolment } from './home.js';
|
|
2
2
|
export class HouseRefusal extends Error {
|
|
3
3
|
status;
|
|
4
|
+
text;
|
|
4
5
|
constructor(path, status, text) {
|
|
5
6
|
super(`House refused ${path} with ${status}: ${text}`);
|
|
6
7
|
this.status = status;
|
|
8
|
+
this.text = text;
|
|
7
9
|
}
|
|
8
10
|
}
|
|
9
11
|
export async function post(house, path, body, credential, signal) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
import { HouseRefusal, post } from './api.js';
|
|
3
|
+
import { readEnrolment } from './home.js';
|
|
4
|
+
function unanswered(error) {
|
|
5
|
+
return error instanceof HouseRefusal ? error.status >= 500 : error instanceof TypeError && error.message === 'fetch failed';
|
|
6
|
+
}
|
|
7
|
+
setGlobalProxyFromEnv();
|
|
8
|
+
try {
|
|
9
|
+
const { house, credential } = (await readEnrolment());
|
|
10
|
+
try {
|
|
11
|
+
await post(house, '/kit/agents/desired', {}, credential);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (!unanswered(error))
|
|
15
|
+
throw error;
|
|
16
|
+
process.stderr.write(`kit: House did not answer, so the stored credential stands: ${error.message}\n`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
process.stderr.write(`kit: ${error.message}\n`);
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "0.2.1-alpha.10",
|
|
4
|
+
"hosts": {
|
|
5
|
+
"linux": {
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.10/connect-linux.sh)",
|
|
7
|
+
"asset": {
|
|
8
|
+
"name": "connect-linux.sh",
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.10/connect-linux.sh"
|
|
10
|
+
},
|
|
11
|
+
"workspace": {
|
|
12
|
+
"argument": "--workspace",
|
|
13
|
+
"quoting": "posix"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"windows": {
|
|
17
|
+
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.10/connect-windows.ps1)))",
|
|
18
|
+
"asset": {
|
|
19
|
+
"name": "connect-windows.ps1",
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-alpha.10/connect-windows.ps1"
|
|
21
|
+
},
|
|
22
|
+
"workspace": {
|
|
23
|
+
"argument": "--workspace",
|
|
24
|
+
"quoting": "powershell"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/bridge.js
CHANGED
|
@@ -1,76 +1,171 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { createServer, request as httpRequest } from 'node:http';
|
|
5
5
|
import { request as httpsRequest } from 'node:https';
|
|
6
|
-
import { join } from 'node:path';
|
|
6
|
+
import { delimiter, join } from 'node:path';
|
|
7
|
+
import { parse } from 'yaml';
|
|
7
8
|
import { KIT_VERSION } from './clis.js';
|
|
9
|
+
import { changedPaths, refusalCode, } from './copies.js';
|
|
8
10
|
import { kitHome, readEnrolment } from './home.js';
|
|
11
|
+
import { operationId } from './operation.js';
|
|
9
12
|
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
13
|
+
const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
10
14
|
const FORWARDED = new Set(['/', '/kit/attachments/upload']);
|
|
11
|
-
function
|
|
12
|
-
const bytes = randomBytes(16);
|
|
13
|
-
bytes.writeUIntBE(Date.now(), 0, 6);
|
|
14
|
-
bytes[6] = 0x70 | (bytes[6] & 0x0f);
|
|
15
|
-
bytes[8] = 0x80 | (bytes[8] & 0x3f);
|
|
16
|
-
const hex = bytes.toString('hex');
|
|
17
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
18
|
-
}
|
|
19
|
-
async function received(request) {
|
|
15
|
+
export async function received(request) {
|
|
20
16
|
let text = '';
|
|
21
17
|
request.setEncoding('utf8');
|
|
22
18
|
for await (const chunk of request)
|
|
23
19
|
text += chunk;
|
|
24
20
|
return text;
|
|
25
21
|
}
|
|
26
|
-
|
|
22
|
+
function relay(request, response, target, authorization) {
|
|
23
|
+
const send = target.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
24
|
+
const forwarded = send(target, {
|
|
25
|
+
method: request.method,
|
|
26
|
+
headers: { ...request.headers, host: target.host, ...(authorization === undefined ? {} : { authorization }) },
|
|
27
|
+
}, (answer) => {
|
|
28
|
+
response.writeHead(answer.statusCode, answer.headers);
|
|
29
|
+
answer.pipe(response);
|
|
30
|
+
});
|
|
31
|
+
forwarded.on('error', () => response.destroy());
|
|
32
|
+
request.pipe(forwarded);
|
|
33
|
+
}
|
|
34
|
+
function answered(forwarded) {
|
|
35
|
+
if (forwarded.status >= 500)
|
|
36
|
+
throw new Error(`House answered ${forwarded.status}`);
|
|
37
|
+
if (forwarded.status !== 200)
|
|
38
|
+
return { refused: { code: refusalCode(forwarded.text), text: forwarded.text } };
|
|
39
|
+
const answer = JSON.parse(forwarded.text);
|
|
40
|
+
if (answer.error !== undefined)
|
|
41
|
+
return { refused: { code: refusalCode(answer.error.message), text: answer.error.message } };
|
|
42
|
+
const text = answer.result.content.map((content) => content.text).join('\n');
|
|
43
|
+
return answer.result.isError === true ? { refused: { code: refusalCode(text), text } } : { text };
|
|
44
|
+
}
|
|
45
|
+
function editAnswer(forwarded) {
|
|
46
|
+
const answer = answered(forwarded);
|
|
47
|
+
if ('refused' in answer)
|
|
48
|
+
return answer;
|
|
49
|
+
const { results } = parse(answer.text);
|
|
50
|
+
return {
|
|
51
|
+
accepted: results.flatMap((result) => [
|
|
52
|
+
...(result.from === undefined ? [] : [{ path: result.from, revision: null, content: null }]),
|
|
53
|
+
{ path: result.path, revision: result.op === 'remove' ? null : result.revision },
|
|
54
|
+
]),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export async function openBridge(house, copies, conversationId, signal) {
|
|
27
58
|
const origin = (await readEnrolment()).house;
|
|
28
59
|
const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
|
|
29
60
|
const closed = new AbortController();
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
61
|
+
const body = (message, operation) => JSON.stringify({
|
|
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
|
+
});
|
|
73
|
+
const send = async (message, operation) => {
|
|
74
|
+
const tool = message.method === 'tools/call' ? String(message.params?.name) : null;
|
|
75
|
+
const answer = await fetch(new URL('/', origin), {
|
|
76
|
+
method: 'POST',
|
|
36
77
|
headers: {
|
|
78
|
+
'content-type': 'application/json',
|
|
79
|
+
authorization: `Bearer ${credential}`,
|
|
37
80
|
accept: 'application/json, text/event-stream',
|
|
38
81
|
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
39
82
|
'mcp-method': message.method,
|
|
40
83
|
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
41
84
|
},
|
|
42
|
-
body:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
};
|
|
85
|
+
body: body(message, operation),
|
|
86
|
+
signal: closed.signal,
|
|
87
|
+
});
|
|
88
|
+
return { status: answer.status, text: await answer.text() };
|
|
89
|
+
};
|
|
90
|
+
const call = (name, args) => ({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: args } });
|
|
91
|
+
const staged = async (operation, changes) => {
|
|
92
|
+
const bytes = Buffer.from(JSON.stringify({ changes }));
|
|
93
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
94
|
+
const prepared = answered(await send(call('run_command', {
|
|
95
|
+
command: 'prepare_edit_upload',
|
|
96
|
+
arguments: { paths: changedPaths(changes), bytes: bytes.length, sha256 },
|
|
97
|
+
}), operation));
|
|
98
|
+
if ('refused' in prepared)
|
|
99
|
+
return prepared;
|
|
100
|
+
const { transfer } = parse(prepared.text);
|
|
101
|
+
const posted = await fetch(transfer.url, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
104
|
+
body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
|
|
105
|
+
signal: closed.signal,
|
|
106
|
+
});
|
|
107
|
+
const text = await posted.text();
|
|
108
|
+
if (posted.status >= 500)
|
|
109
|
+
throw new Error(`House answered ${posted.status}`);
|
|
110
|
+
if (!posted.ok)
|
|
111
|
+
return { refused: { code: refusalCode(text), text } };
|
|
112
|
+
const held = JSON.parse(text).transfer;
|
|
113
|
+
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
55
114
|
};
|
|
56
115
|
const forward = async (path, text) => {
|
|
57
|
-
|
|
116
|
+
if (path === '/') {
|
|
117
|
+
const message = JSON.parse(text);
|
|
118
|
+
const tool = message.method === 'tools/call' ? String(message.params?.name) : null;
|
|
119
|
+
const writes = tool !== null &&
|
|
120
|
+
(await house.post('/kit/tools/mutations', {}, closed.signal)).tools.some((listed) => listed.name === tool && listed.writes);
|
|
121
|
+
return send(message, writes ? operationId() : null);
|
|
122
|
+
}
|
|
58
123
|
const answer = await fetch(new URL(path, origin), {
|
|
59
124
|
method: 'POST',
|
|
60
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}
|
|
61
|
-
body:
|
|
125
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
126
|
+
body: text,
|
|
62
127
|
signal: closed.signal,
|
|
63
128
|
});
|
|
64
129
|
return { status: answer.status, text: await answer.text() };
|
|
65
130
|
};
|
|
131
|
+
const caller = {
|
|
132
|
+
key: `conversation:${conversationId}`,
|
|
133
|
+
submit: async (operation, changes, held, retain) => {
|
|
134
|
+
const inline = call('edit', { changes });
|
|
135
|
+
let upload = held;
|
|
136
|
+
if (upload === undefined && Buffer.byteLength(body(inline, operation)) > MCP_REQUEST_BYTES) {
|
|
137
|
+
const prepared = await staged(operation, changes);
|
|
138
|
+
if ('refused' in prepared)
|
|
139
|
+
return prepared;
|
|
140
|
+
upload = prepared;
|
|
141
|
+
await retain(upload);
|
|
142
|
+
}
|
|
143
|
+
return editAnswer(await send(upload === undefined ? inline : call('edit', { paths: changedPaths(changes), upload }), operation));
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const local = async (path, text) => {
|
|
147
|
+
if (path === '/git/push') {
|
|
148
|
+
const { cwd, commit } = JSON.parse(text);
|
|
149
|
+
return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
|
|
150
|
+
}
|
|
151
|
+
if (path === '/git/observed') {
|
|
152
|
+
await copies.observed(JSON.parse(text));
|
|
153
|
+
return { status: 200, text: '{}' };
|
|
154
|
+
}
|
|
155
|
+
return FORWARDED.has(path) ? forward(path, text) : { status: 404, text: '' };
|
|
156
|
+
};
|
|
66
157
|
const sockets = join(kitHome(), 'bridges');
|
|
67
158
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
68
159
|
const socketPath = join(sockets, `${randomUUID()}.sock`);
|
|
69
160
|
const bridge = createServer(async (request, response) => {
|
|
70
161
|
try {
|
|
71
162
|
const path = request.url ?? '';
|
|
72
|
-
|
|
73
|
-
|
|
163
|
+
if (URL.canParse(path)) {
|
|
164
|
+
relay(request, response, new URL(path));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const answered = await local(path, await received(request));
|
|
168
|
+
response.writeHead(answered.status, { 'content-type': 'application/json' }).end(answered.text);
|
|
74
169
|
}
|
|
75
170
|
catch (error) {
|
|
76
171
|
response.writeHead(502).end(error instanceof Error ? error.message : String(error));
|
|
@@ -86,13 +181,7 @@ export async function openBridge(house, conversationId, signal) {
|
|
|
86
181
|
response.writeHead(404).end();
|
|
87
182
|
return;
|
|
88
183
|
}
|
|
89
|
-
|
|
90
|
-
const forwarded = send(target, { method: request.method, headers: { ...request.headers, host: target.host, authorization } }, (answer) => {
|
|
91
|
-
response.writeHead(answer.statusCode, answer.headers);
|
|
92
|
-
answer.pipe(response);
|
|
93
|
-
});
|
|
94
|
-
forwarded.on('error', () => response.destroy());
|
|
95
|
-
request.pipe(forwarded);
|
|
184
|
+
relay(request, response, target, authorization);
|
|
96
185
|
});
|
|
97
186
|
git.listen(0, '127.0.0.1');
|
|
98
187
|
await once(git, 'listening');
|
|
@@ -100,6 +189,7 @@ export async function openBridge(house, conversationId, signal) {
|
|
|
100
189
|
return {
|
|
101
190
|
env: {
|
|
102
191
|
HOUSE_BRIDGE: socketPath,
|
|
192
|
+
PATH: `${join(kitHome(), 'shim')}${delimiter}${process.env.PATH ?? ''}`,
|
|
103
193
|
GIT_CONFIG_COUNT: '1',
|
|
104
194
|
GIT_CONFIG_KEY_0: `url.${proxy}/app/.insteadOf`,
|
|
105
195
|
GIT_CONFIG_VALUE_0: app,
|
package/dist/clis.js
CHANGED
|
@@ -65,6 +65,7 @@ export const CLIS = {
|
|
|
65
65
|
};
|
|
66
66
|
const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
67
67
|
export const KIT_VERSION = KIT_PACKAGE.version;
|
|
68
|
+
const PROXY = /^https?_proxy$/i;
|
|
68
69
|
export function pinned(kind) {
|
|
69
70
|
return KIT_PACKAGE.peerDependencies[CLIS[kind].package];
|
|
70
71
|
}
|
|
@@ -83,9 +84,12 @@ export async function installedRelease(kind) {
|
|
|
83
84
|
return null;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
87
|
+
export function withoutProxy() {
|
|
88
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
89
|
+
}
|
|
86
90
|
export function run(command, args, timeoutMs) {
|
|
87
91
|
return new Promise((resolve) => {
|
|
88
|
-
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'], timeout: timeoutMs });
|
|
92
|
+
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'], timeout: timeoutMs, env: withoutProxy() });
|
|
89
93
|
let output = '';
|
|
90
94
|
child.stdout.on('data', (chunk) => {
|
|
91
95
|
output += chunk.toString('utf8');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readConfiguration, writeConfiguration } from './home.js';
|
|
2
|
+
const skills = !process.argv.slice(2).includes('--no-skills');
|
|
3
|
+
const stored = await readConfiguration();
|
|
4
|
+
await writeConfiguration({ skills });
|
|
5
|
+
if (stored.skills !== skills)
|
|
6
|
+
process.stdout.write('changed\n');
|
package/dist/conversations.js
CHANGED
|
@@ -9,11 +9,15 @@ import { CLIS } from './clis.js';
|
|
|
9
9
|
import { placeFiles } from './files.js';
|
|
10
10
|
import { instructions } from './instructions.js';
|
|
11
11
|
import { holdSecretInput } from './secret-input.js';
|
|
12
|
+
import { createHowWeWork } from './skills.js';
|
|
12
13
|
const LAUNCH_BASE = '/agents/house';
|
|
13
14
|
const KILLED = 'the conversation was killed';
|
|
14
15
|
function causeOf(error) {
|
|
15
16
|
return error instanceof Error ? error.message : String(error);
|
|
16
17
|
}
|
|
18
|
+
function answerMessage(request, response) {
|
|
19
|
+
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
20
|
+
}
|
|
17
21
|
function logged(error) {
|
|
18
22
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
19
23
|
}
|
|
@@ -36,11 +40,11 @@ class Turn {
|
|
|
36
40
|
unsentFrom = null;
|
|
37
41
|
draftSequence = 0;
|
|
38
42
|
planSequence = 0;
|
|
43
|
+
entries = null;
|
|
39
44
|
questions = 0;
|
|
40
45
|
secrets = 0;
|
|
41
46
|
prompt = null;
|
|
42
47
|
ended = false;
|
|
43
|
-
ready = Promise.resolve();
|
|
44
48
|
}
|
|
45
49
|
class Conversation {
|
|
46
50
|
id;
|
|
@@ -140,8 +144,13 @@ export class Conversations {
|
|
|
140
144
|
}
|
|
141
145
|
if (input.kind === 'option')
|
|
142
146
|
await this.option(conversation, String(input.option), input.value);
|
|
143
|
-
if (input.kind === 'answer')
|
|
144
|
-
this.questions.get(String(input.interaction_id))
|
|
147
|
+
if (input.kind === 'answer') {
|
|
148
|
+
const question = this.questions.get(String(input.interaction_id));
|
|
149
|
+
if (question === undefined) {
|
|
150
|
+
return this.message(conversation, { ...input, text: answerMessage(input.request, input.response), files: [], first: false });
|
|
151
|
+
}
|
|
152
|
+
question.answer(input.response);
|
|
153
|
+
}
|
|
145
154
|
return {};
|
|
146
155
|
}
|
|
147
156
|
async open(conversation, input) {
|
|
@@ -166,7 +175,10 @@ export class Conversations {
|
|
|
166
175
|
ack = { provider_session_id: opened };
|
|
167
176
|
}
|
|
168
177
|
if (input.first === true) {
|
|
169
|
-
prompt.push({
|
|
178
|
+
prompt.push({
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
181
|
+
});
|
|
170
182
|
}
|
|
171
183
|
prompt.push({ type: 'text', text: String(input.text) });
|
|
172
184
|
if (paths.length > 0)
|
|
@@ -239,14 +251,15 @@ export class Conversations {
|
|
|
239
251
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
240
252
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal));
|
|
241
253
|
await unlessKilled(this.kit.agents.installed(), conversation.killed.signal);
|
|
242
|
-
const bridge = await openBridge(this.kit.house, conversation.id, conversation.killed.signal);
|
|
254
|
+
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
243
255
|
if (conversation.kills > 0) {
|
|
244
256
|
bridge.close();
|
|
245
257
|
throw new Error(KILLED);
|
|
246
258
|
}
|
|
247
259
|
conversation.opening = bridge;
|
|
248
260
|
try {
|
|
249
|
-
|
|
261
|
+
await unlessKilled(createHowWeWork(bridge), conversation.killed.signal);
|
|
262
|
+
const adapter = await startAdapter(route.kind, route.working_directory, app, (job) => this.job(conversation, job), bridge.env);
|
|
250
263
|
void adapter.exited.then(() => bridge.close());
|
|
251
264
|
const agent = adapter.connection.agent;
|
|
252
265
|
const cwd = route.working_directory;
|
|
@@ -340,9 +353,19 @@ export class Conversations {
|
|
|
340
353
|
report(conversation) {
|
|
341
354
|
const turn = new Turn();
|
|
342
355
|
conversation.open++;
|
|
343
|
-
|
|
356
|
+
void this.deliver(conversation, turn, 'started', {})
|
|
357
|
+
.then(() => this.admitted(conversation, turn))
|
|
358
|
+
.catch(logged);
|
|
344
359
|
return turn;
|
|
345
360
|
}
|
|
361
|
+
admitted(conversation, turn) {
|
|
362
|
+
if (turn.text !== '') {
|
|
363
|
+
turn.unsentFrom = 0;
|
|
364
|
+
this.draft(conversation, turn);
|
|
365
|
+
}
|
|
366
|
+
if (turn.entries !== null)
|
|
367
|
+
this.plan(conversation, turn);
|
|
368
|
+
}
|
|
346
369
|
begin(conversation) {
|
|
347
370
|
const turn = this.report(conversation);
|
|
348
371
|
conversation.turn = turn;
|
|
@@ -479,12 +502,12 @@ export class Conversations {
|
|
|
479
502
|
if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {
|
|
480
503
|
const turn = this.started(conversation);
|
|
481
504
|
turn.text += update.content.text;
|
|
482
|
-
|
|
483
|
-
void turn.ready.then(() => this.draft(conversation, turn, text));
|
|
505
|
+
this.draft(conversation, turn);
|
|
484
506
|
}
|
|
485
507
|
else if (update.sessionUpdate === 'plan') {
|
|
486
508
|
const turn = this.started(conversation);
|
|
487
|
-
|
|
509
|
+
turn.entries = update.entries;
|
|
510
|
+
this.plan(conversation, turn);
|
|
488
511
|
}
|
|
489
512
|
else if (update.sessionUpdate === 'agent_thought_chunk' || update.sessionUpdate === 'tool_call') {
|
|
490
513
|
this.started(conversation);
|
|
@@ -503,8 +526,8 @@ export class Conversations {
|
|
|
503
526
|
conversation.jobs.delete(job.id);
|
|
504
527
|
this.kit.changed();
|
|
505
528
|
}
|
|
506
|
-
draft(conversation, turn
|
|
507
|
-
const blocks = blocksOf(text);
|
|
529
|
+
draft(conversation, turn) {
|
|
530
|
+
const blocks = blocksOf(turn.text);
|
|
508
531
|
const from = Math.min(firstChange(turn.sent, blocks), turn.unsentFrom ?? Infinity);
|
|
509
532
|
const sent = this.kit.send({
|
|
510
533
|
type: 'draft',
|
|
@@ -523,14 +546,14 @@ export class Conversations {
|
|
|
523
546
|
turn.unsentFrom = from;
|
|
524
547
|
}
|
|
525
548
|
}
|
|
526
|
-
plan(conversation, turn
|
|
549
|
+
plan(conversation, turn) {
|
|
527
550
|
const sent = this.kit.send({
|
|
528
551
|
type: 'plan',
|
|
529
552
|
conversation_id: conversation.id,
|
|
530
553
|
turn_id: turn.id,
|
|
531
554
|
sequence: turn.planSequence + 1,
|
|
532
555
|
from: 0,
|
|
533
|
-
steps: entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
556
|
+
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
534
557
|
});
|
|
535
558
|
if (sent)
|
|
536
559
|
turn.planSequence++;
|