@agentshouse/kit 0.2.1-c2fb1c24bcdd5 → 0.2.1-c3304109525e3
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 +2 -2
- package/dist/acp.js +54 -4
- package/dist/agents.js +12 -21
- package/dist/calls.js +11 -3
- package/dist/clis.js +7 -3
- package/dist/connection.js +19 -29
- package/dist/conversations.js +140 -40
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/instructions.js +4 -4
- package/dist/login.js +3 -1
- package/dist/mcp.js +2 -0
- package/dist/resident.js +17 -12
- package/dist/scripts.js +255 -0
- package/dist/silence.js +28 -0
- package/dist/skills.js +38 -12
- package/dist/update.js +21 -4
- package/package.json +8 -8
- package/dist/web-wizard.js +0 -101
package/dist/scripts.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { spawn as spawnPiped } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { once } from 'node:events';
|
|
4
|
+
import { rmSync } from 'node:fs';
|
|
5
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { constants } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { loginPath, onLoginPath } from './clis.js';
|
|
9
|
+
import { kitHome } from './home.js';
|
|
10
|
+
import { childEnvironment } from './relay.js';
|
|
11
|
+
import { killTree } from './scope.js';
|
|
12
|
+
const TERMINAL = 'xterm-256color';
|
|
13
|
+
const FIRST_SIZE = { cols: 80, rows: 24 };
|
|
14
|
+
const SIGNALLED = 128;
|
|
15
|
+
const NEWLINE = 0x0a;
|
|
16
|
+
const NO_BASH = "bash is not on the login shell's PATH";
|
|
17
|
+
const LOST = { refused: 'the Kit lost its connection to House' };
|
|
18
|
+
function causeOf(error) {
|
|
19
|
+
return error instanceof Error ? error.message : String(error);
|
|
20
|
+
}
|
|
21
|
+
function failed(plan, cause) {
|
|
22
|
+
process.stderr.write(`kit: ${plan.named} could not start: ${cause}\n`);
|
|
23
|
+
return { refused: `the script could not start: ${cause}` };
|
|
24
|
+
}
|
|
25
|
+
function planned(call) {
|
|
26
|
+
if (call.kind === 'run_wizard')
|
|
27
|
+
return { named: 'a Web Wizard', script: call.script, terminal: 'page', function: null };
|
|
28
|
+
return {
|
|
29
|
+
named: 'an Environment function',
|
|
30
|
+
script: call.script,
|
|
31
|
+
terminal: call.terminal ? 'at once' : null,
|
|
32
|
+
function: { input: call.input, timeCap: call.time_cap, outputCap: call.output_cap },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function inputs() {
|
|
36
|
+
return join(kitHome(), 'inputs');
|
|
37
|
+
}
|
|
38
|
+
async function inputFile(value) {
|
|
39
|
+
await mkdir(inputs(), { recursive: true, mode: 0o700 });
|
|
40
|
+
const path = join(inputs(), `${randomUUID()}.json`);
|
|
41
|
+
await writeFile(path, JSON.stringify(value), { mode: 0o600, flag: 'wx' });
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
export function removeInputs() {
|
|
45
|
+
rmSync(inputs(), { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
function struck(line, typed) {
|
|
48
|
+
return typed.some((typedLine) => line.includes(typedLine));
|
|
49
|
+
}
|
|
50
|
+
class KeptOutput {
|
|
51
|
+
cap;
|
|
52
|
+
typedText = '';
|
|
53
|
+
text = '';
|
|
54
|
+
bytes = 0;
|
|
55
|
+
line = '';
|
|
56
|
+
lineBytes = 0;
|
|
57
|
+
dropping = false;
|
|
58
|
+
cut = false;
|
|
59
|
+
constructor(cap) {
|
|
60
|
+
this.cap = cap;
|
|
61
|
+
}
|
|
62
|
+
typed(data) {
|
|
63
|
+
this.typedText += data;
|
|
64
|
+
}
|
|
65
|
+
add(data) {
|
|
66
|
+
const pieces = data.split('\n');
|
|
67
|
+
const rest = pieces.pop();
|
|
68
|
+
for (const piece of pieces)
|
|
69
|
+
this.ended(piece);
|
|
70
|
+
this.extend(rest);
|
|
71
|
+
}
|
|
72
|
+
kept() {
|
|
73
|
+
const typed = this.typedLines(true);
|
|
74
|
+
this.text = [...this.text.split(/(?<=\n)/), this.line].filter((line) => line !== '' && !struck(line, typed)).join('');
|
|
75
|
+
this.bytes = Buffer.byteLength(this.text);
|
|
76
|
+
this.trim();
|
|
77
|
+
return { output: this.text, cut: this.cut };
|
|
78
|
+
}
|
|
79
|
+
typedLines(all) {
|
|
80
|
+
const lines = this.typedText.split(/\r\n|\r|\n/);
|
|
81
|
+
if (!all)
|
|
82
|
+
lines.pop();
|
|
83
|
+
return lines.filter((line) => line !== '');
|
|
84
|
+
}
|
|
85
|
+
ended(piece) {
|
|
86
|
+
const line = `${this.line}${piece}\n`;
|
|
87
|
+
const dropped = this.dropping;
|
|
88
|
+
this.line = '';
|
|
89
|
+
this.lineBytes = 0;
|
|
90
|
+
this.dropping = false;
|
|
91
|
+
if (dropped || struck(line, this.typedLines(false)))
|
|
92
|
+
return;
|
|
93
|
+
this.text += line;
|
|
94
|
+
this.bytes += Buffer.byteLength(line);
|
|
95
|
+
if (this.bytes > 2 * this.cap)
|
|
96
|
+
this.trim();
|
|
97
|
+
}
|
|
98
|
+
extend(piece) {
|
|
99
|
+
if (this.dropping)
|
|
100
|
+
return;
|
|
101
|
+
this.line += piece;
|
|
102
|
+
this.lineBytes += Buffer.byteLength(piece);
|
|
103
|
+
if (this.lineBytes <= this.cap)
|
|
104
|
+
return;
|
|
105
|
+
this.text = '';
|
|
106
|
+
this.bytes = 0;
|
|
107
|
+
this.line = '';
|
|
108
|
+
this.lineBytes = 0;
|
|
109
|
+
this.dropping = true;
|
|
110
|
+
this.cut = true;
|
|
111
|
+
}
|
|
112
|
+
trim() {
|
|
113
|
+
if (this.bytes <= this.cap)
|
|
114
|
+
return;
|
|
115
|
+
const bytes = Buffer.from(this.text);
|
|
116
|
+
const start = bytes.indexOf(NEWLINE, bytes.length - this.cap - 1) + 1;
|
|
117
|
+
this.text = bytes.subarray(start).toString('utf8');
|
|
118
|
+
this.bytes = bytes.length - start;
|
|
119
|
+
this.cut = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export class Scripts {
|
|
123
|
+
calls;
|
|
124
|
+
send;
|
|
125
|
+
runs = new Map();
|
|
126
|
+
constructor(calls, send) {
|
|
127
|
+
this.calls = calls;
|
|
128
|
+
this.send = send;
|
|
129
|
+
}
|
|
130
|
+
start(call) {
|
|
131
|
+
void this.calls.carry(call.input_id, () => this.run(call.input_id, planned(call)));
|
|
132
|
+
}
|
|
133
|
+
notified(notification) {
|
|
134
|
+
const run = this.runs.get(String(notification.wizard));
|
|
135
|
+
if (run === undefined)
|
|
136
|
+
return;
|
|
137
|
+
if (notification.type === 'wizard_input')
|
|
138
|
+
this.typed(run, String(notification.data));
|
|
139
|
+
if (notification.type === 'wizard_size')
|
|
140
|
+
this.resized(run, { cols: Number(notification.cols), rows: Number(notification.rows) });
|
|
141
|
+
if (notification.type === 'wizard_end' && run.plan.terminal === 'page')
|
|
142
|
+
this.end(run, {});
|
|
143
|
+
}
|
|
144
|
+
disconnected() {
|
|
145
|
+
for (const run of this.runs.values())
|
|
146
|
+
this.end(run, LOST);
|
|
147
|
+
}
|
|
148
|
+
async run(id, plan) {
|
|
149
|
+
const size = plan.terminal === 'page' ? null : FIRST_SIZE;
|
|
150
|
+
const output = plan.function === null ? null : new KeptOutput(plan.function.outputCap);
|
|
151
|
+
const run = { plan, input: null, output, process: null, pipes: [], terminal: null, size, typed: [], ended: null, sized: Promise.withResolvers() };
|
|
152
|
+
if (size !== null)
|
|
153
|
+
run.sized.resolve();
|
|
154
|
+
this.runs.set(id, run);
|
|
155
|
+
try {
|
|
156
|
+
return { result: await this.ran(id, run), release: plan.function !== null };
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.runs.delete(id);
|
|
160
|
+
if (run.input !== null)
|
|
161
|
+
await rm(run.input, { force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async ran(id, run) {
|
|
165
|
+
const { plan, output } = run;
|
|
166
|
+
let exited;
|
|
167
|
+
try {
|
|
168
|
+
const { spawn } = await import('@lydell/node-pty');
|
|
169
|
+
const shell = await onLoginPath('bash');
|
|
170
|
+
if (shell === null)
|
|
171
|
+
return failed(plan, NO_BASH);
|
|
172
|
+
const path = await loginPath();
|
|
173
|
+
await run.sized.promise;
|
|
174
|
+
if (plan.function !== null)
|
|
175
|
+
run.input = await inputFile(plan.function.input);
|
|
176
|
+
if (run.ended !== null)
|
|
177
|
+
return run.ended;
|
|
178
|
+
const environment = { ...childEnvironment(), PATH: path, ...(run.input === null ? {} : { HOUSE_INPUT: run.input }) };
|
|
179
|
+
if (plan.terminal === null) {
|
|
180
|
+
({ exited } = await this.piped(plan.script, shell, environment, run));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const terminal = spawn(shell, ['-c', plan.script], { name: TERMINAL, ...run.size, cwd: process.cwd(), env: environment });
|
|
184
|
+
run.process = terminal;
|
|
185
|
+
run.terminal = terminal;
|
|
186
|
+
terminal.onData((data) => {
|
|
187
|
+
this.send({ type: 'wizard_output', wizard: id, data });
|
|
188
|
+
output?.add(data);
|
|
189
|
+
});
|
|
190
|
+
for (const data of run.typed.splice(0))
|
|
191
|
+
terminal.write(data);
|
|
192
|
+
exited = new Promise((resolve) => terminal.onExit(({ exitCode, signal }) => resolve(signal ? SIGNALLED + signal : exitCode)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return failed(plan, causeOf(error));
|
|
197
|
+
}
|
|
198
|
+
let timedOut = false;
|
|
199
|
+
// House sends a function run's time cap in seconds, and a run still going at its cap ends with its process group and the output pipes it holds.
|
|
200
|
+
const capped = plan.function === null ? undefined : setTimeout(() => {
|
|
201
|
+
timedOut = true;
|
|
202
|
+
this.stop(run);
|
|
203
|
+
}, plan.function.timeCap * 1000);
|
|
204
|
+
const code = await exited;
|
|
205
|
+
clearTimeout(capped);
|
|
206
|
+
if (run.ended !== null)
|
|
207
|
+
return run.ended;
|
|
208
|
+
if (output === null)
|
|
209
|
+
return { exit_code: code };
|
|
210
|
+
const kept = output.kept();
|
|
211
|
+
return timedOut ? { timed_out: true, ...kept } : { exit_code: code, ...kept };
|
|
212
|
+
}
|
|
213
|
+
async piped(script, shell, environment, run) {
|
|
214
|
+
const child = spawnPiped(shell, ['-c', script], { cwd: process.cwd(), env: environment, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
215
|
+
const exited = new Promise((resolve) => child.once('close', (code, signal) => resolve(signal === null ? code : SIGNALLED + constants.signals[signal])));
|
|
216
|
+
await once(child, 'spawn');
|
|
217
|
+
run.process = { pid: child.pid };
|
|
218
|
+
run.pipes = [child.stdout, child.stderr];
|
|
219
|
+
for (const pipe of run.pipes) {
|
|
220
|
+
pipe.setEncoding('utf8');
|
|
221
|
+
pipe.on('data', (data) => run.output.add(data));
|
|
222
|
+
}
|
|
223
|
+
if (run.ended !== null)
|
|
224
|
+
this.stop(run);
|
|
225
|
+
return { exited };
|
|
226
|
+
}
|
|
227
|
+
typed(run, data) {
|
|
228
|
+
run.output?.typed(data);
|
|
229
|
+
if (run.terminal === null)
|
|
230
|
+
run.typed.push(data);
|
|
231
|
+
else
|
|
232
|
+
run.terminal.write(data);
|
|
233
|
+
}
|
|
234
|
+
resized(run, size) {
|
|
235
|
+
run.size = size;
|
|
236
|
+
run.sized.resolve();
|
|
237
|
+
try {
|
|
238
|
+
run.terminal?.resize(size.cols, size.rows);
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
}
|
|
242
|
+
end(run, result) {
|
|
243
|
+
if (run.ended !== null)
|
|
244
|
+
return;
|
|
245
|
+
run.ended = result;
|
|
246
|
+
run.sized.resolve();
|
|
247
|
+
this.stop(run);
|
|
248
|
+
}
|
|
249
|
+
stop(run) {
|
|
250
|
+
if (run.process !== null)
|
|
251
|
+
killTree(run.process);
|
|
252
|
+
for (const pipe of run.pipes)
|
|
253
|
+
pipe.destroy();
|
|
254
|
+
}
|
|
255
|
+
}
|
package/dist/silence.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const SYSTEM_CLOCK = {
|
|
2
|
+
later: (delay, callback) => setTimeout(callback, delay),
|
|
3
|
+
clear: (timer) => clearTimeout(timer),
|
|
4
|
+
};
|
|
5
|
+
export class Silence {
|
|
6
|
+
timer = null;
|
|
7
|
+
interval;
|
|
8
|
+
silent;
|
|
9
|
+
clock;
|
|
10
|
+
constructor(interval, silent, clock = SYSTEM_CLOCK) {
|
|
11
|
+
this.interval = interval;
|
|
12
|
+
this.silent = silent;
|
|
13
|
+
this.clock = clock;
|
|
14
|
+
}
|
|
15
|
+
heard() {
|
|
16
|
+
this.stop();
|
|
17
|
+
this.timer = this.clock.later(this.interval, () => {
|
|
18
|
+
this.timer = null;
|
|
19
|
+
this.silent();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
stop() {
|
|
23
|
+
if (this.timer === null)
|
|
24
|
+
return;
|
|
25
|
+
this.clock.clear(this.timer);
|
|
26
|
+
this.timer = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/skills.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { cp, lstat, mkdir, realpath, rm, symlink } from 'node:fs/promises';
|
|
1
|
+
import { cp, lstat, mkdir, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
|
-
import { listSkills } from '@agentshouse/skills';
|
|
4
|
+
import { GUIDE, listSkills } from '@agentshouse/skills';
|
|
5
|
+
import { parse } from 'yaml';
|
|
5
6
|
import { KIT_RELEASE } from './clis.js';
|
|
6
7
|
import { kitHome, readConfiguration, readHome, writeHome } from './home.js';
|
|
7
8
|
const RECORD = 'skill-set.json';
|
|
9
|
+
const ENTRY = 'SKILL.md';
|
|
10
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---\n/u;
|
|
11
|
+
const ORIGIN = '<origin>';
|
|
8
12
|
function skillsDirectory() {
|
|
9
13
|
return join(homedir(), '.agents', 'skills');
|
|
10
14
|
}
|
|
@@ -22,9 +26,31 @@ async function present(path) {
|
|
|
22
26
|
throw error;
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
|
-
|
|
29
|
+
function skillName(text) {
|
|
30
|
+
const front = FRONTMATTER.exec(text);
|
|
31
|
+
const fields = (front === null ? null : parse(front[1]));
|
|
32
|
+
return typeof fields?.name === 'string' && typeof fields.description === 'string' ? fields.name : undefined;
|
|
33
|
+
}
|
|
34
|
+
function skillSet(origin) {
|
|
35
|
+
const upstream = listSkills().map(({ name, directory }) => ({
|
|
36
|
+
name,
|
|
37
|
+
place: (destination) => cp(directory, destination, { recursive: true }),
|
|
38
|
+
}));
|
|
39
|
+
const pages = GUIDE.flatMap(({ text }) => {
|
|
40
|
+
const name = skillName(text);
|
|
41
|
+
if (name === undefined)
|
|
42
|
+
return [];
|
|
43
|
+
const place = async (destination) => {
|
|
44
|
+
await mkdir(destination);
|
|
45
|
+
await writeFile(join(destination, ENTRY), text.replaceAll(ORIGIN, origin));
|
|
46
|
+
};
|
|
47
|
+
return [{ name, place }];
|
|
48
|
+
});
|
|
49
|
+
return [...upstream, ...pages].sort((left, right) => (left.name < right.name ? -1 : 1));
|
|
50
|
+
}
|
|
51
|
+
async function install(origin) {
|
|
26
52
|
const placed = [];
|
|
27
|
-
for (const skill of
|
|
53
|
+
for (const skill of skillSet(origin)) {
|
|
28
54
|
if (!(await present(join(skillsDirectory(), skill.name))) && !(await present(join(claudeDirectory(), skill.name)))) {
|
|
29
55
|
placed.push(skill);
|
|
30
56
|
}
|
|
@@ -35,32 +61,32 @@ async function install() {
|
|
|
35
61
|
await mkdir(claudeDirectory(), { recursive: true });
|
|
36
62
|
const skills = await realpath(skillsDirectory());
|
|
37
63
|
const claude = await realpath(claudeDirectory());
|
|
38
|
-
for (const { name,
|
|
39
|
-
await
|
|
64
|
+
for (const { name, place } of placed) {
|
|
65
|
+
await place(join(skillsDirectory(), name));
|
|
40
66
|
if (claude !== skills)
|
|
41
67
|
await symlink(relative(claude, join(skills, name)), join(claudeDirectory(), name));
|
|
42
68
|
}
|
|
43
|
-
await writeHome(RECORD, { version: KIT_RELEASE, names });
|
|
69
|
+
await writeHome(RECORD, { version: KIT_RELEASE, origin, names });
|
|
44
70
|
}
|
|
45
|
-
export async function placeSkillSet() {
|
|
71
|
+
export async function placeSkillSet(origin) {
|
|
46
72
|
try {
|
|
47
|
-
await replaceSkillSet();
|
|
73
|
+
await replaceSkillSet(origin);
|
|
48
74
|
}
|
|
49
75
|
catch (error) {
|
|
50
76
|
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
|
-
async function replaceSkillSet() {
|
|
79
|
+
async function replaceSkillSet(origin) {
|
|
54
80
|
const accepted = (await readConfiguration()).skills;
|
|
55
81
|
const installed = await readHome(RECORD);
|
|
56
|
-
if (accepted && installed?.version === KIT_RELEASE)
|
|
82
|
+
if (accepted && installed?.version === KIT_RELEASE && installed.origin === origin)
|
|
57
83
|
return;
|
|
58
84
|
for (const name of installed?.names ?? []) {
|
|
59
85
|
await rm(join(claudeDirectory(), name), { recursive: true, force: true });
|
|
60
86
|
await rm(join(skillsDirectory(), name), { recursive: true, force: true });
|
|
61
87
|
}
|
|
62
88
|
if (accepted)
|
|
63
|
-
await install();
|
|
89
|
+
await install(origin);
|
|
64
90
|
else
|
|
65
91
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
66
92
|
}
|
package/dist/update.js
CHANGED
|
@@ -10,6 +10,9 @@ import { relayed } from './relay.js';
|
|
|
10
10
|
const BOOTSTRAP_BYTES = 1_000_000;
|
|
11
11
|
// A bootstrap is at most a million bytes, so two minutes carry it over ten kilobytes a second and no stalled host holds the update queue.
|
|
12
12
|
const BOOTSTRAP_MS = 120_000;
|
|
13
|
+
// A bootstrap's downloads and CLI updates take minutes, so one still running after an hour has hung and is ended before it keeps the Kit offline.
|
|
14
|
+
const RERUN_MS = 60 * 60_000;
|
|
15
|
+
const TIMED_OUT = '124';
|
|
13
16
|
function directory() {
|
|
14
17
|
return join(kitHome(), 'update');
|
|
15
18
|
}
|
|
@@ -35,7 +38,7 @@ function placement() {
|
|
|
35
38
|
return service() === 'user' ? ['--workspace', workspaceRoot()] : ['--linux'];
|
|
36
39
|
}
|
|
37
40
|
function record() {
|
|
38
|
-
return `outcome=$1; shift; bash "$0" "$@"; printf %s "$?" > "$outcome"; ${[...elevated(), 'systemctl', ...manager(), 'start', 'house-kit.service'].join(' ')}`;
|
|
41
|
+
return `outcome=$1; shift; timeout ${RERUN_MS / 1000} bash "$0" "$@"; printf %s "$?" > "$outcome"; ${[...elevated(), 'systemctl', ...manager(), 'start', 'house-kit.service'].join(' ')}`;
|
|
39
42
|
}
|
|
40
43
|
async function pendingCall() {
|
|
41
44
|
try {
|
|
@@ -49,6 +52,8 @@ async function recorded() {
|
|
|
49
52
|
const status = await readFile(outcome(), 'utf8').catch(() => null);
|
|
50
53
|
if (status === null)
|
|
51
54
|
return null;
|
|
55
|
+
if (status.trim() === TIMED_OUT)
|
|
56
|
+
return { refused: `the Kit bootstrap did not finish within ${RERUN_MS / 1000} seconds` };
|
|
52
57
|
return status.trim() === '0' ? {} : { refused: `the Kit bootstrap exited with ${status.trim()}` };
|
|
53
58
|
}
|
|
54
59
|
async function forget(callId) {
|
|
@@ -109,9 +114,12 @@ function rerun(script, callId, flags) {
|
|
|
109
114
|
}
|
|
110
115
|
export class KitUpdate {
|
|
111
116
|
calls;
|
|
117
|
+
changed;
|
|
118
|
+
waiting = new Map();
|
|
112
119
|
queue = Promise.resolve();
|
|
113
|
-
constructor(calls) {
|
|
120
|
+
constructor(calls, changed) {
|
|
114
121
|
this.calls = calls;
|
|
122
|
+
this.changed = changed;
|
|
115
123
|
}
|
|
116
124
|
async resumed() {
|
|
117
125
|
const callId = await pendingCall();
|
|
@@ -119,8 +127,17 @@ export class KitUpdate {
|
|
|
119
127
|
return;
|
|
120
128
|
await this.enqueue(callId, () => finished(callId));
|
|
121
129
|
}
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
hold(call) {
|
|
131
|
+
this.waiting.set(call.input_id, call);
|
|
132
|
+
this.changed();
|
|
133
|
+
}
|
|
134
|
+
held() {
|
|
135
|
+
return this.waiting.size > 0;
|
|
136
|
+
}
|
|
137
|
+
apply(resume) {
|
|
138
|
+
const held = [...this.waiting.values()];
|
|
139
|
+
this.waiting.clear();
|
|
140
|
+
void Promise.all(held.map((call) => this.enqueue(call.input_id, () => this.carry(call)))).then(resume);
|
|
124
141
|
}
|
|
125
142
|
enqueue(callId, work) {
|
|
126
143
|
const before = this.queue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-c3304109525e3",
|
|
4
4
|
"description": "Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"test": "tsc -p tsconfig.json && vitest run"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@agentclientprotocol/sdk": "
|
|
28
|
-
"@agentshouse/mdmodel": "0.1.0-
|
|
29
|
-
"@agentshouse/skills": "0.1.0-
|
|
27
|
+
"@agentclientprotocol/sdk": "2.0.1",
|
|
28
|
+
"@agentshouse/mdmodel": "0.1.0-c4f4d37bc9d6a",
|
|
29
|
+
"@agentshouse/skills": "0.1.0-c1884c2b56810",
|
|
30
30
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
31
31
|
"yaml": "2.9.1"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
35
|
-
"@agentclientprotocol/codex-acp": "2.
|
|
34
|
+
"@agentclientprotocol/claude-agent-acp": "0.94.0",
|
|
35
|
+
"@agentclientprotocol/codex-acp": "2.2.2"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@agentclientprotocol/claude-agent-acp": {
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@types/node": "24.
|
|
46
|
+
"@types/node": "24.19.2",
|
|
47
47
|
"@types/ws": "8.18.2",
|
|
48
48
|
"typescript": "7.0.2",
|
|
49
|
-
"vitest": "5.0.
|
|
49
|
+
"vitest": "5.0.5",
|
|
50
50
|
"ws": "8.22.0"
|
|
51
51
|
},
|
|
52
52
|
"packageManager": "npm@11.19.1",
|
package/dist/web-wizard.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { loginPath, onLoginPath } from './clis.js';
|
|
2
|
-
import { childEnvironment } from './relay.js';
|
|
3
|
-
import { killTree } from './scope.js';
|
|
4
|
-
const TERMINAL = 'xterm-256color';
|
|
5
|
-
const SIGNALLED = 128;
|
|
6
|
-
const NO_BASH = "bash is not on the login shell's PATH";
|
|
7
|
-
const LOST = { refused: 'the Kit lost its connection to House' };
|
|
8
|
-
function causeOf(error) {
|
|
9
|
-
return error instanceof Error ? error.message : String(error);
|
|
10
|
-
}
|
|
11
|
-
function failed(cause) {
|
|
12
|
-
process.stderr.write(`kit: a Web Wizard could not start: ${cause}\n`);
|
|
13
|
-
return { refused: `the script could not start: ${cause}` };
|
|
14
|
-
}
|
|
15
|
-
export class WebWizards {
|
|
16
|
-
calls;
|
|
17
|
-
send;
|
|
18
|
-
runs = new Map();
|
|
19
|
-
constructor(calls, send) {
|
|
20
|
-
this.calls = calls;
|
|
21
|
-
this.send = send;
|
|
22
|
-
}
|
|
23
|
-
start(call) {
|
|
24
|
-
void this.calls.carry(call.input_id, () => this.run(call));
|
|
25
|
-
}
|
|
26
|
-
notified(notification) {
|
|
27
|
-
const run = this.runs.get(String(notification.wizard));
|
|
28
|
-
if (run === undefined)
|
|
29
|
-
return;
|
|
30
|
-
if (notification.type === 'wizard_input')
|
|
31
|
-
this.typed(run, String(notification.data));
|
|
32
|
-
if (notification.type === 'wizard_size')
|
|
33
|
-
this.resized(run, { cols: Number(notification.cols), rows: Number(notification.rows) });
|
|
34
|
-
if (notification.type === 'wizard_end')
|
|
35
|
-
this.end(run, {});
|
|
36
|
-
}
|
|
37
|
-
disconnected() {
|
|
38
|
-
for (const run of this.runs.values())
|
|
39
|
-
this.end(run, LOST);
|
|
40
|
-
}
|
|
41
|
-
async run(call) {
|
|
42
|
-
const run = { terminal: null, size: null, typed: [], ended: null, sized: Promise.withResolvers() };
|
|
43
|
-
this.runs.set(call.input_id, run);
|
|
44
|
-
try {
|
|
45
|
-
return { result: await this.ran(call.input_id, call.script, run) };
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
this.runs.delete(call.input_id);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async ran(wizard, script, run) {
|
|
52
|
-
let terminal;
|
|
53
|
-
try {
|
|
54
|
-
const { spawn } = await import('@lydell/node-pty');
|
|
55
|
-
const shell = await onLoginPath('bash');
|
|
56
|
-
if (shell === null)
|
|
57
|
-
return failed(NO_BASH);
|
|
58
|
-
const path = await loginPath();
|
|
59
|
-
await run.sized.promise;
|
|
60
|
-
if (run.ended !== null)
|
|
61
|
-
return run.ended;
|
|
62
|
-
terminal = spawn(shell, ['-c', script], {
|
|
63
|
-
name: TERMINAL,
|
|
64
|
-
...run.size,
|
|
65
|
-
cwd: process.cwd(),
|
|
66
|
-
env: { ...childEnvironment(), PATH: path },
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
return failed(causeOf(error));
|
|
71
|
-
}
|
|
72
|
-
run.terminal = terminal;
|
|
73
|
-
terminal.onData((data) => this.send({ type: 'wizard_output', wizard, data }));
|
|
74
|
-
for (const data of run.typed.splice(0))
|
|
75
|
-
terminal.write(data);
|
|
76
|
-
const exited = await new Promise((resolve) => terminal.onExit(resolve));
|
|
77
|
-
return run.ended ?? { exit_code: exited.signal ? SIGNALLED + exited.signal : exited.exitCode };
|
|
78
|
-
}
|
|
79
|
-
typed(run, data) {
|
|
80
|
-
if (run.terminal === null)
|
|
81
|
-
run.typed.push(data);
|
|
82
|
-
else
|
|
83
|
-
run.terminal.write(data);
|
|
84
|
-
}
|
|
85
|
-
resized(run, size) {
|
|
86
|
-
run.size = size;
|
|
87
|
-
run.sized.resolve();
|
|
88
|
-
try {
|
|
89
|
-
run.terminal?.resize(size.cols, size.rows);
|
|
90
|
-
}
|
|
91
|
-
catch { }
|
|
92
|
-
}
|
|
93
|
-
end(run, result) {
|
|
94
|
-
if (run.ended !== null)
|
|
95
|
-
return;
|
|
96
|
-
run.ended = result;
|
|
97
|
-
run.sized.resolve();
|
|
98
|
-
if (run.terminal !== null)
|
|
99
|
-
killTree(run.terminal);
|
|
100
|
-
}
|
|
101
|
-
}
|