@agentshouse/kit 0.2.1-c3cf4b145dcda → 0.2.1-c4beedfd8b549
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/dist/acp.js +7 -97
- package/dist/agents.js +2 -1
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +7 -22
- package/dist/clis.js +51 -12
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +47 -50
- package/dist/end.js +1 -1
- package/dist/events.js +10 -76
- package/dist/house-main.js +25 -10
- package/dist/instructions.js +1 -1
- package/dist/resident.js +9 -9
- package/dist/scope.js +203 -0
- package/dist/sign-in.js +1 -1
- package/dist/work.js +348 -0
- package/package.json +1 -1
package/dist/acp.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
|
|
2
|
-
import {
|
|
3
|
-
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
4
3
|
import { Readable, Writable } from 'node:stream';
|
|
5
4
|
import { CLIS, KIT_VERSION, adapterCommand, withoutProxy } from './clis.js';
|
|
6
|
-
|
|
5
|
+
import { killTree } from './scope.js';
|
|
6
|
+
export async function startAdapter(kind, cli, cwd, app, events = () => undefined, added = {}, signal, scope = null) {
|
|
7
7
|
const adapter = CLIS[kind].adapter;
|
|
8
|
-
const
|
|
8
|
+
const started = [adapter === null ? cli : adapterCommand(kind), CLIS[kind].args];
|
|
9
|
+
const [command, args] = scope?.command(...started) ?? started;
|
|
10
|
+
const child = spawn(command, args, {
|
|
9
11
|
cwd,
|
|
10
12
|
env: { ...withoutProxy(), ...(adapter === null ? {} : { ...adapter.env, [adapter.executable]: cli }), ...added },
|
|
11
13
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -51,6 +53,7 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
51
53
|
},
|
|
52
54
|
clientInfo: { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
53
55
|
});
|
|
56
|
+
scope?.entered(child.pid);
|
|
54
57
|
return { child, connection, initialized, exited };
|
|
55
58
|
}
|
|
56
59
|
catch (error) {
|
|
@@ -58,96 +61,3 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
58
61
|
throw error;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
|
-
const LISTING_BYTES = 1 << 28;
|
|
62
|
-
function listed(options) {
|
|
63
|
-
return execFileSync('ps', [options, '-o', 'pid=,ppid=,command='], { encoding: 'utf8', maxBuffer: LISTING_BYTES })
|
|
64
|
-
.split('\n')
|
|
65
|
-
.flatMap((line) => {
|
|
66
|
-
const fields = /^\s*(\d+)\s+(\d+)\s?(.*)$/.exec(line);
|
|
67
|
-
return fields === null ? [] : [[Number(fields[1]), Number(fields[2]), fields[3]]];
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
function parents() {
|
|
71
|
-
const children = new Map();
|
|
72
|
-
const adopt = (pid, parent) => children.set(parent, [...(children.get(parent) ?? []), pid]);
|
|
73
|
-
if (process.platform === 'darwin') {
|
|
74
|
-
for (const [pid, parent] of listed('-Aww'))
|
|
75
|
-
adopt(pid, parent);
|
|
76
|
-
return children;
|
|
77
|
-
}
|
|
78
|
-
for (const entry of readdirSync('/proc')) {
|
|
79
|
-
if (!/^\d+$/.test(entry))
|
|
80
|
-
continue;
|
|
81
|
-
try {
|
|
82
|
-
const stat = readFileSync(`/proc/${entry}/stat`, 'utf8');
|
|
83
|
-
adopt(Number(entry), Number(stat.slice(stat.lastIndexOf(')') + 2).split(' ')[1]));
|
|
84
|
-
}
|
|
85
|
-
catch { }
|
|
86
|
-
}
|
|
87
|
-
return children;
|
|
88
|
-
}
|
|
89
|
-
function descendants(roots) {
|
|
90
|
-
const children = parents();
|
|
91
|
-
const tree = [];
|
|
92
|
-
const pending = roots.flatMap((root) => children.get(root) ?? []);
|
|
93
|
-
while (pending.length > 0) {
|
|
94
|
-
const pid = pending.pop();
|
|
95
|
-
tree.push(pid);
|
|
96
|
-
pending.push(...(children.get(pid) ?? []));
|
|
97
|
-
}
|
|
98
|
-
return tree;
|
|
99
|
-
}
|
|
100
|
-
function killAll(pids) {
|
|
101
|
-
for (const pid of pids) {
|
|
102
|
-
for (const target of [-pid, pid]) {
|
|
103
|
-
try {
|
|
104
|
-
process.kill(target, 'SIGKILL');
|
|
105
|
-
}
|
|
106
|
-
catch { }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function marked(marker) {
|
|
111
|
-
if (process.platform === 'darwin') {
|
|
112
|
-
return listed('-AEww')
|
|
113
|
-
.filter(([, , command]) => ` ${command} `.includes(` ${marker} `))
|
|
114
|
-
.map(([pid]) => pid);
|
|
115
|
-
}
|
|
116
|
-
return readdirSync('/proc')
|
|
117
|
-
.filter((entry) => {
|
|
118
|
-
try {
|
|
119
|
-
return /^\d+$/.test(entry) && readFileSync(`/proc/${entry}/environ`, 'utf8').split('\0').includes(marker);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
.map(Number);
|
|
126
|
-
}
|
|
127
|
-
function commandLine(pid) {
|
|
128
|
-
try {
|
|
129
|
-
return readFileSync(`/proc/${pid}/cmdline`, 'utf8').split('\0').join(' ').trim();
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
return '';
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
export function processes(marker) {
|
|
136
|
-
const pids = marked(marker);
|
|
137
|
-
const tree = [...pids, ...descendants(pids)];
|
|
138
|
-
const commands = process.platform === 'darwin'
|
|
139
|
-
? new Map(listed('-Aww').map(([pid, , command]) => [pid, command]))
|
|
140
|
-
: new Map(tree.map((pid) => [pid, commandLine(pid)]));
|
|
141
|
-
return new Set(tree.flatMap((pid) => (commands.get(pid) ? [`${pid} ${commands.get(pid)}`] : [])));
|
|
142
|
-
}
|
|
143
|
-
export function killTree(child) {
|
|
144
|
-
killAll([child.pid, ...descendants([child.pid])]);
|
|
145
|
-
}
|
|
146
|
-
export function killMarked(marker) {
|
|
147
|
-
const pids = marked(marker);
|
|
148
|
-
killAll([...pids, ...descendants(pids)]);
|
|
149
|
-
}
|
|
150
|
-
export function killDescendants() {
|
|
151
|
-
const pids = marked(`HOUSE_KIT_RESIDENT=${process.pid}`);
|
|
152
|
-
killAll([...pids, ...descendants([process.pid, ...pids])]);
|
|
153
|
-
}
|
package/dist/agents.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { client } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { readFile, realpath } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { startAdapter } from './acp.js';
|
|
4
4
|
import { CLIS, KIT_VERSION, below, installAdapter, installCli, locate, run, versionOf } from './clis.js';
|
|
5
5
|
import { agentBase, kitHome } from './home.js';
|
|
6
|
+
import { killTree } from './scope.js';
|
|
6
7
|
import { holdTranscription, transcriptionReport } from './transcription.js';
|
|
7
8
|
const SIGNED_OUT = { signed_in: false, models: [], modes: [] };
|
|
8
9
|
function select(options, category) {
|
package/dist/bootstrap.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-c4beedfd8b549",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"macos": {
|
|
6
6
|
"asset": {
|
|
7
7
|
"name": "connect-linux.sh",
|
|
8
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
8
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c4beedfd8b549/connect-linux.sh"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"linux": {
|
|
12
12
|
"asset": {
|
|
13
13
|
"name": "connect-linux.sh",
|
|
14
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
14
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c4beedfd8b549/connect-linux.sh"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"windows": {
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c4beedfd8b549/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/bridge.js
CHANGED
|
@@ -11,11 +11,10 @@ import { loginPath } from './clis.js';
|
|
|
11
11
|
import { changedPaths, } from './copies.js';
|
|
12
12
|
import { kitHome, readEnrolment } from './home.js';
|
|
13
13
|
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
14
|
-
import { operationId
|
|
14
|
+
import { operationId } from './operation.js';
|
|
15
15
|
import { refusalOf, retryable } from './refusals.js';
|
|
16
16
|
import { relayed } from './relay.js';
|
|
17
17
|
const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
18
|
-
const ATTACHMENTS = '/kit/attachments/upload';
|
|
19
18
|
export async function received(request) {
|
|
20
19
|
let text = '';
|
|
21
20
|
request.setEncoding('utf8');
|
|
@@ -68,11 +67,6 @@ function resendAfter(forwarded, attempt) {
|
|
|
68
67
|
const seconds = pendingSeconds(forwarded);
|
|
69
68
|
return seconds === null ? null : seconds * 1000;
|
|
70
69
|
}
|
|
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
70
|
function editAnswer(forwarded) {
|
|
77
71
|
const answer = answered(forwarded);
|
|
78
72
|
if ('refused' in answer)
|
|
@@ -154,19 +148,10 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
154
148
|
throw error;
|
|
155
149
|
}
|
|
156
150
|
};
|
|
157
|
-
const forward = async (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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() };
|
|
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);
|
|
170
155
|
};
|
|
171
156
|
const caller = {
|
|
172
157
|
key: `conversation:${conversationId}`,
|
|
@@ -192,7 +177,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
192
177
|
await copies.observed(JSON.parse(text));
|
|
193
178
|
return { status: 200, text: '{}' };
|
|
194
179
|
}
|
|
195
|
-
return path === '/'
|
|
180
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
196
181
|
};
|
|
197
182
|
const sockets = join(kitHome(), 'bridges');
|
|
198
183
|
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
@@ -239,7 +224,7 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
239
224
|
GIT_CONFIG_VALUE_0: app,
|
|
240
225
|
},
|
|
241
226
|
tool: async (name, args) => {
|
|
242
|
-
const forwarded = await forward(
|
|
227
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
243
228
|
if (forwarded.status !== 200)
|
|
244
229
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
245
230
|
const answer = JSON.parse(forwarded.text);
|
package/dist/clis.js
CHANGED
|
@@ -1,24 +1,56 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { constants, readFileSync } from 'node:fs';
|
|
3
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { delimiter, isAbsolute, join } from 'node:path';
|
|
6
6
|
import { kitHome } from './home.js';
|
|
7
|
-
|
|
7
|
+
const RAW_SUBTYPES = [
|
|
8
|
+
'background_tasks_changed',
|
|
9
|
+
'task_started',
|
|
10
|
+
'task_updated',
|
|
11
|
+
'task_notification',
|
|
12
|
+
'session_state_changed',
|
|
13
|
+
'hook_started',
|
|
14
|
+
'hook_response',
|
|
15
|
+
];
|
|
16
|
+
async function executable(path) {
|
|
17
|
+
try {
|
|
18
|
+
await access(path, constants.X_OK);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function allowHouseInCodex(path) {
|
|
26
|
+
const candidates = [...new Set(path.split(delimiter).filter((directory) => isAbsolute(directory)))].map((directory) => join(directory, 'house'));
|
|
27
|
+
const houses = [];
|
|
28
|
+
for (const candidate of candidates)
|
|
29
|
+
if (await executable(candidate))
|
|
30
|
+
houses.push(candidate);
|
|
8
31
|
const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
|
|
9
32
|
await mkdir(rules, { recursive: true });
|
|
10
|
-
await writeFile(join(rules, 'house.rules'),
|
|
33
|
+
await writeFile(join(rules, 'house.rules'), `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`);
|
|
11
34
|
}
|
|
12
35
|
async function nothing() { }
|
|
36
|
+
function bridgeInClaudeSandbox(bridge) {
|
|
37
|
+
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
38
|
+
}
|
|
13
39
|
export const CLIS = {
|
|
14
40
|
'codex-acp': {
|
|
15
41
|
bin: 'codex',
|
|
16
42
|
minimum: '0.159.1',
|
|
17
43
|
install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
|
|
18
|
-
adapter: {
|
|
44
|
+
adapter: {
|
|
45
|
+
package: '@agentclientprotocol/codex-acp',
|
|
46
|
+
bin: 'codex-acp',
|
|
47
|
+
executable: 'CODEX_PATH',
|
|
48
|
+
env: { CODEX_CONFIG: JSON.stringify({ features: { shell_zsh_fork: true } }) },
|
|
49
|
+
},
|
|
19
50
|
args: [],
|
|
20
51
|
fullAccess: 'agent-full-access',
|
|
21
|
-
sessionMeta: {},
|
|
52
|
+
sessionMeta: () => ({}),
|
|
53
|
+
renames: true,
|
|
22
54
|
allowHouse: allowHouseInCodex,
|
|
23
55
|
signedIn: { command: ['login', 'status'] },
|
|
24
56
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
@@ -36,12 +68,18 @@ export const CLIS = {
|
|
|
36
68
|
},
|
|
37
69
|
args: [],
|
|
38
70
|
fullAccess: 'bypassPermissions',
|
|
39
|
-
sessionMeta: {
|
|
71
|
+
sessionMeta: (bridge, title) => ({
|
|
40
72
|
claudeCode: {
|
|
41
|
-
options: {
|
|
42
|
-
|
|
73
|
+
options: {
|
|
74
|
+
allowedTools: ['Bash(house:*)'],
|
|
75
|
+
includeHookEvents: true,
|
|
76
|
+
title,
|
|
77
|
+
settings: { sandbox: { network: bridgeInClaudeSandbox(bridge) } },
|
|
78
|
+
},
|
|
79
|
+
emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
|
|
43
80
|
},
|
|
44
|
-
},
|
|
81
|
+
}),
|
|
82
|
+
renames: false,
|
|
45
83
|
allowHouse: nothing,
|
|
46
84
|
signedIn: { command: ['auth', 'status'] },
|
|
47
85
|
login: { args: ['auth', 'login', '--claudeai'], code: 'collect', rejected: 'Invalid code' },
|
|
@@ -54,7 +92,8 @@ export const CLIS = {
|
|
|
54
92
|
adapter: null,
|
|
55
93
|
args: ['agent', '--always-approve', '--no-leader', 'stdio'],
|
|
56
94
|
fullAccess: null,
|
|
57
|
-
sessionMeta: {},
|
|
95
|
+
sessionMeta: () => ({}),
|
|
96
|
+
renames: false,
|
|
58
97
|
allowHouse: nothing,
|
|
59
98
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
60
99
|
login: { args: ['login', '--device-auth'], code: 'show' },
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { connect } from 'node:net';
|
|
4
|
+
const KIT_ANSWER = '"house-kit-';
|
|
5
|
+
const SYNC = 'house-kit/sync';
|
|
6
|
+
function lines(each) {
|
|
7
|
+
let buffer = '';
|
|
8
|
+
return (chunk) => {
|
|
9
|
+
buffer += chunk;
|
|
10
|
+
for (let index = buffer.indexOf('\n'); index >= 0; index = buffer.indexOf('\n')) {
|
|
11
|
+
each(buffer.slice(0, index + 1));
|
|
12
|
+
buffer = buffer.slice(index + 1);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function answersKit(line) {
|
|
17
|
+
if (!line.includes(KIT_ANSWER))
|
|
18
|
+
return false;
|
|
19
|
+
try {
|
|
20
|
+
const message = JSON.parse(line);
|
|
21
|
+
return message.method === undefined && typeof message.id === 'string' && message.id.startsWith(KIT_ANSWER.slice(1));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const serving = args[0] === 'app-server';
|
|
29
|
+
if (process.env.HOUSE_KIT_SANDBOX === '1' && serving)
|
|
30
|
+
args.push('--disable', 'memories');
|
|
31
|
+
const codex = spawn(process.env.HOUSE_KIT_CODEX, args, { stdio: ['pipe', 'pipe', 'inherit'] });
|
|
32
|
+
const stream = serving ? connect(process.env.HOUSE_KIT_CODEX_STREAM) : null;
|
|
33
|
+
stream?.on('error', () => undefined);
|
|
34
|
+
process.stdin.setEncoding('utf8').on('data', lines((line) => codex.stdin.write(line)));
|
|
35
|
+
process.stdin.on('end', () => codex.stdin.end());
|
|
36
|
+
stream?.setEncoding('utf8').on('data', lines((line) => {
|
|
37
|
+
const message = JSON.parse(line);
|
|
38
|
+
if (message.method === SYNC)
|
|
39
|
+
stream.write(`${JSON.stringify({ jsonrpc: '2.0', id: message.id, result: {} })}\n`);
|
|
40
|
+
else
|
|
41
|
+
codex.stdin.write(line);
|
|
42
|
+
}));
|
|
43
|
+
codex.stdout.setEncoding('utf8').on('data', lines((line) => {
|
|
44
|
+
stream?.write(line);
|
|
45
|
+
if (answersKit(line) || process.stdout.write(line))
|
|
46
|
+
return;
|
|
47
|
+
codex.stdout.pause();
|
|
48
|
+
process.stdout.once('drain', () => codex.stdout.resume());
|
|
49
|
+
}));
|
|
50
|
+
codex.stdin.on('error', () => undefined);
|
|
51
|
+
for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP'])
|
|
52
|
+
process.on(signal, () => codex.kill(signal));
|
|
53
|
+
codex.on('exit', (code, signal) => {
|
|
54
|
+
if (signal === null)
|
|
55
|
+
process.exit(code ?? 1);
|
|
56
|
+
process.removeAllListeners(signal);
|
|
57
|
+
process.kill(process.pid, signal);
|
|
58
|
+
});
|
package/dist/conversations.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
|
-
import {
|
|
6
|
+
import { startAdapter } from './acp.js';
|
|
7
7
|
import { openBridge } from './bridge.js';
|
|
8
8
|
import { CLIS } from './clis.js';
|
|
9
9
|
import { readerFor } from './events.js';
|
|
@@ -11,12 +11,12 @@ import { placeFiles, uploadBytes } from './files.js';
|
|
|
11
11
|
import { agentBase } from './home.js';
|
|
12
12
|
import { instructions } from './instructions.js';
|
|
13
13
|
import { Parts } from './parts.js';
|
|
14
|
+
import { killMarked, Scope } from './scope.js';
|
|
14
15
|
import { holdSecretInput } from './secret-input.js';
|
|
15
16
|
import { createHowWeWork } from './skills.js';
|
|
16
17
|
import { transcribe } from './transcription.js';
|
|
18
|
+
import { workFor } from './work.js';
|
|
17
19
|
const KILLED = 'the conversation was killed';
|
|
18
|
-
// A process a conversation left running reports nothing when it exits, so the Kit looks again every five seconds while one runs.
|
|
19
|
-
const PROCESS_RECHECK_MS = 5000;
|
|
20
20
|
function causeOf(error) {
|
|
21
21
|
return error instanceof Error ? error.message : String(error);
|
|
22
22
|
}
|
|
@@ -29,9 +29,6 @@ function logged(error) {
|
|
|
29
29
|
function marker(bridge) {
|
|
30
30
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
31
31
|
}
|
|
32
|
-
function stop(bridge) {
|
|
33
|
-
killMarked(marker(bridge));
|
|
34
|
-
}
|
|
35
32
|
class Turn {
|
|
36
33
|
id = randomUUID();
|
|
37
34
|
parts;
|
|
@@ -53,7 +50,7 @@ class Conversation {
|
|
|
53
50
|
turn = null;
|
|
54
51
|
commands = null;
|
|
55
52
|
context = null;
|
|
56
|
-
|
|
53
|
+
leftovers = new Set();
|
|
57
54
|
waiting = [];
|
|
58
55
|
sending = null;
|
|
59
56
|
subagents = new Map();
|
|
@@ -86,9 +83,10 @@ export class Conversations {
|
|
|
86
83
|
carrying = 0;
|
|
87
84
|
reporting = 0;
|
|
88
85
|
watched = false;
|
|
89
|
-
recheck = null;
|
|
90
86
|
constructor(kit) {
|
|
91
87
|
this.kit = kit;
|
|
88
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
89
|
+
setInterval(() => this.look(), 2_000);
|
|
92
90
|
}
|
|
93
91
|
input(input) {
|
|
94
92
|
this.carrying++;
|
|
@@ -126,22 +124,24 @@ export class Conversations {
|
|
|
126
124
|
for (const conversation of this.conversations.values()) {
|
|
127
125
|
const turn = conversation.turn;
|
|
128
126
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
129
|
-
if (conversation.
|
|
127
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
130
128
|
return false;
|
|
131
129
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (running === null || running.baseline === null)
|
|
135
|
-
continue;
|
|
136
|
-
if ([...processes(marker(running.bridge))].every((found) => running.baseline.has(found)))
|
|
137
|
-
continue;
|
|
138
|
-
this.recheck ??= setTimeout(() => {
|
|
139
|
-
this.recheck = null;
|
|
140
|
-
this.kit.changed();
|
|
141
|
-
}, PROCESS_RECHECK_MS);
|
|
130
|
+
const conversations = [...this.conversations.values()];
|
|
131
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
142
132
|
return false;
|
|
133
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
134
|
+
}
|
|
135
|
+
look() {
|
|
136
|
+
for (const conversation of this.conversations.values()) {
|
|
137
|
+
for (const scope of conversation.leftovers) {
|
|
138
|
+
if (!scope.empty())
|
|
139
|
+
continue;
|
|
140
|
+
scope.close();
|
|
141
|
+
conversation.leftovers.delete(scope);
|
|
142
|
+
}
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
this.kit.changed();
|
|
145
145
|
}
|
|
146
146
|
opened() {
|
|
147
147
|
this.watched = false;
|
|
@@ -295,11 +295,13 @@ export class Conversations {
|
|
|
295
295
|
}
|
|
296
296
|
halt(conversation) {
|
|
297
297
|
if (conversation.opening !== null)
|
|
298
|
-
|
|
298
|
+
killMarked(marker(conversation.opening));
|
|
299
|
+
for (const scope of conversation.leftovers)
|
|
300
|
+
scope.kill();
|
|
299
301
|
if (conversation.running === null)
|
|
300
302
|
return;
|
|
301
303
|
conversation.running.killed = true;
|
|
302
|
-
|
|
304
|
+
conversation.running.scope.kill();
|
|
303
305
|
}
|
|
304
306
|
async kill(conversation) {
|
|
305
307
|
const running = conversation.running;
|
|
@@ -350,22 +352,30 @@ export class Conversations {
|
|
|
350
352
|
throw new Error(KILLED);
|
|
351
353
|
}
|
|
352
354
|
conversation.opening = bridge;
|
|
355
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
356
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
353
357
|
try {
|
|
354
358
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
355
|
-
await CLIS[route.kind].allowHouse();
|
|
356
|
-
|
|
357
|
-
|
|
359
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
360
|
+
const adapter = await startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
361
|
+
work.read(message);
|
|
362
|
+
this.read(conversation, reader, message);
|
|
363
|
+
}, { ...bridge.env, ...(await work.open(cli)) }, undefined, scope);
|
|
358
364
|
void adapter.exited.then(() => bridge.close());
|
|
359
365
|
const agent = adapter.connection.agent;
|
|
360
366
|
const cwd = route.working_directory;
|
|
361
|
-
const _meta = CLIS[route.kind].sessionMeta;
|
|
367
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
362
368
|
const opened = sessionId === null
|
|
363
369
|
? await agent.request('session/new', { cwd, mcpServers: [], _meta })
|
|
364
370
|
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
365
371
|
reader.session = opened.sessionId;
|
|
366
|
-
|
|
372
|
+
if (sessionId === null && CLIS[route.kind].renames) {
|
|
373
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
374
|
+
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
375
|
+
}
|
|
376
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
367
377
|
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
368
|
-
|
|
378
|
+
void work.settled().then(() => scope.record());
|
|
369
379
|
conversation.running = running;
|
|
370
380
|
conversation.session = running.sessionId;
|
|
371
381
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
@@ -376,7 +386,9 @@ export class Conversations {
|
|
|
376
386
|
return running.sessionId;
|
|
377
387
|
}
|
|
378
388
|
catch (error) {
|
|
379
|
-
|
|
389
|
+
scope.kill();
|
|
390
|
+
work.close();
|
|
391
|
+
conversation.leftovers.add(scope);
|
|
380
392
|
bridge.close();
|
|
381
393
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
382
394
|
}
|
|
@@ -409,17 +421,18 @@ export class Conversations {
|
|
|
409
421
|
const running = conversation.running;
|
|
410
422
|
if (running?.adapter !== adapter)
|
|
411
423
|
return;
|
|
412
|
-
this.detach(conversation);
|
|
424
|
+
this.detach(conversation, running);
|
|
413
425
|
if (conversation.turn !== null) {
|
|
414
426
|
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
415
427
|
}
|
|
416
428
|
this.next(conversation);
|
|
417
429
|
this.kit.changed();
|
|
418
430
|
}
|
|
419
|
-
detach(conversation) {
|
|
431
|
+
detach(conversation, running) {
|
|
432
|
+
running.work.close();
|
|
433
|
+
conversation.leftovers.add(running.scope);
|
|
420
434
|
conversation.running = null;
|
|
421
435
|
conversation.commands = null;
|
|
422
|
-
conversation.busy.clear();
|
|
423
436
|
for (const [id, question] of this.questions) {
|
|
424
437
|
if (question.conversation === conversation)
|
|
425
438
|
this.questions.delete(id);
|
|
@@ -471,8 +484,6 @@ export class Conversations {
|
|
|
471
484
|
turn.parts.measure(conversation.context);
|
|
472
485
|
for (const part of conversation.held.splice(0))
|
|
473
486
|
turn.parts.start(part);
|
|
474
|
-
for (const event of conversation.running?.reader.turnStarted() ?? [])
|
|
475
|
-
this.event(conversation, event);
|
|
476
487
|
this.kit.changed();
|
|
477
488
|
return turn;
|
|
478
489
|
}
|
|
@@ -506,8 +517,8 @@ export class Conversations {
|
|
|
506
517
|
}
|
|
507
518
|
else {
|
|
508
519
|
if (conversation.open === 1 && conversation.running === running) {
|
|
509
|
-
|
|
510
|
-
this.detach(conversation);
|
|
520
|
+
running.scope.kill();
|
|
521
|
+
this.detach(conversation, running);
|
|
511
522
|
}
|
|
512
523
|
this.end(conversation, turn, { failed: causeOf(error) });
|
|
513
524
|
}
|
|
@@ -592,20 +603,6 @@ export class Conversations {
|
|
|
592
603
|
case 'options':
|
|
593
604
|
this.options(conversation, event.options);
|
|
594
605
|
return;
|
|
595
|
-
case 'busy':
|
|
596
|
-
if (event.running)
|
|
597
|
-
conversation.busy.add(event.key);
|
|
598
|
-
else
|
|
599
|
-
conversation.busy.delete(event.key);
|
|
600
|
-
this.kit.changed();
|
|
601
|
-
return;
|
|
602
|
-
case 'servers': {
|
|
603
|
-
const running = conversation.running;
|
|
604
|
-
if (running?.baseline)
|
|
605
|
-
for (const found of processes(marker(running.bridge)))
|
|
606
|
-
running.baseline.add(found);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
606
|
case 'context':
|
|
610
607
|
conversation.context = { used: event.used, window: event.window };
|
|
611
608
|
conversation.turn?.parts.measure(conversation.context);
|
package/dist/end.js
CHANGED