@agentshouse/kit 0.2.1-c1873657edab7 → 0.2.1-c215cacfd4cf4
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 +3 -3
- package/dist/acp.js +57 -6
- package/dist/agents.js +6 -9
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +46 -0
- package/dist/calls.js +11 -3
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +39 -15
- package/dist/connection.js +20 -29
- package/dist/conversations.js +149 -45
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/files.js +33 -17
- package/dist/git.js +2 -2
- package/dist/house-cli.js +24 -11
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +4 -4
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +3 -1
- package/dist/mcp.js +2 -0
- package/dist/relay.js +21 -0
- package/dist/resident.js +17 -6
- package/dist/scripts.js +255 -0
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +38 -12
- package/dist/transcription.js +32 -23
- package/dist/update.js +51 -8
- package/package.json +17 -17
- package/dist/bootstrap.json +0 -24
package/dist/house-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import { request
|
|
3
|
+
import { request } from 'node:http';
|
|
4
4
|
import { request as httpsRequest } from 'node:https';
|
|
5
5
|
import { extname, join, resolve } from 'node:path';
|
|
6
6
|
import { Readable } from 'node:stream';
|
|
@@ -10,7 +10,7 @@ import { HOUSE_REF } from './git.js';
|
|
|
10
10
|
import { ownerSocket, readOwnAgent } from './home.js';
|
|
11
11
|
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
12
12
|
import { refusalOf, retryable } from './refusals.js';
|
|
13
|
-
import { relayed, selectRelay } from './relay.js';
|
|
13
|
+
import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
|
|
14
14
|
import { parse } from 'yaml';
|
|
15
15
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
16
16
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
@@ -89,7 +89,6 @@ async function direct(message) {
|
|
|
89
89
|
if (ownAgent === null)
|
|
90
90
|
throw new Error(UNCONNECTED);
|
|
91
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
92
|
-
setGlobalProxyFromEnv();
|
|
93
92
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
94
93
|
throw new Unsettled(UNREACHABLE);
|
|
95
94
|
});
|
|
@@ -120,7 +119,7 @@ function opened(url, method, headers, body) {
|
|
|
120
119
|
const local = socketPath !== undefined;
|
|
121
120
|
const target = local ? null : relayed(url);
|
|
122
121
|
if (!local)
|
|
123
|
-
|
|
122
|
+
proxyUnlessRelayed();
|
|
124
123
|
const sent = target === null
|
|
125
124
|
? request({ socketPath, path: url, method, headers }, answered)
|
|
126
125
|
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
|
@@ -131,8 +130,19 @@ function opened(url, method, headers, body) {
|
|
|
131
130
|
pipeline(body, sent).catch(() => undefined);
|
|
132
131
|
});
|
|
133
132
|
}
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
function ranged(file, start, end) {
|
|
134
|
+
return end === start ? undefined : createReadStream(file, { start, end: end - 1 });
|
|
135
|
+
}
|
|
136
|
+
async function* read(file, start, end) {
|
|
137
|
+
try {
|
|
138
|
+
yield* ranged(file, start, end) ?? [];
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw unreadable(file, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function sentPart(url, file, start, end, headers) {
|
|
145
|
+
const answer = await opened(url, 'PUT', { ...headers, 'content-length': String(end - start) }, ranged(file, start, end));
|
|
136
146
|
await answer.toArray();
|
|
137
147
|
if (answer.statusCode !== 200)
|
|
138
148
|
throw storeRefusal('upload', answer.statusCode);
|
|
@@ -171,10 +181,10 @@ async function described(file) {
|
|
|
171
181
|
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
172
182
|
return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
|
|
173
183
|
}
|
|
174
|
-
async function finished(upload, room) {
|
|
175
|
-
const text = await called('upload', { upload, room_ref: room });
|
|
184
|
+
async function finished(upload, room, partSha256) {
|
|
185
|
+
const text = await called('upload', { upload, room_ref: room, part_sha256: partSha256 });
|
|
176
186
|
const answer = parse(text);
|
|
177
|
-
return answer !== null &&
|
|
187
|
+
return answer !== null && 'upload' in answer ? answer : { text };
|
|
178
188
|
}
|
|
179
189
|
async function upload({ file, ...destination }) {
|
|
180
190
|
if (typeof file !== 'string' || file === '')
|
|
@@ -185,9 +195,12 @@ async function upload({ file, ...destination }) {
|
|
|
185
195
|
if (declared?.upload === undefined)
|
|
186
196
|
return answer;
|
|
187
197
|
const accepted = await uploaded(declared.upload, {
|
|
198
|
+
name: file,
|
|
188
199
|
bytes: description.bytes,
|
|
189
|
-
|
|
190
|
-
|
|
200
|
+
sha256: description.sha256,
|
|
201
|
+
read: (start, end) => read(file, start, end),
|
|
202
|
+
send: (url, start, end, headers) => sentPart(url, file, start, end, headers),
|
|
203
|
+
finish: (id, partSha256) => finished(id, destination.room_ref, partSha256),
|
|
191
204
|
});
|
|
192
205
|
return accepted.text;
|
|
193
206
|
}
|
package/dist/house-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`house: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`house: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/instructions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentInstructions } from '@agentshouse/skills';
|
|
2
2
|
import { HOUSE_REF } from './git.js';
|
|
3
|
-
const
|
|
3
|
+
const OWNER_RULES = '/rooms/private/owner-rules.md';
|
|
4
4
|
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
5
5
|
const FAILED = /^exit: [1-9]\d*$/;
|
|
6
6
|
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
@@ -8,9 +8,9 @@ function cutOf(lines) {
|
|
|
8
8
|
const cut = CUT.exec(lines[0] ?? '');
|
|
9
9
|
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
10
10
|
}
|
|
11
|
-
async function
|
|
11
|
+
async function readOwnerRules(bridge) {
|
|
12
12
|
let document = '';
|
|
13
|
-
for (let command = `cat ${
|
|
13
|
+
for (let command = `cat ${OWNER_RULES}`; command !== undefined;) {
|
|
14
14
|
const read = await bridge.tool('shell', { command });
|
|
15
15
|
const text = read.content.map((content) => content.text).join('\n');
|
|
16
16
|
const lines = text.split('\n');
|
|
@@ -28,5 +28,5 @@ async function readHowWeWork(bridge) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function instructions(bridge, base, copies) {
|
|
30
30
|
const lines = agentInstructions(HOUSE_REF, copies);
|
|
31
|
-
return [lines.join('\n'), base, (await
|
|
31
|
+
return [lines.join('\n'), base, (await readOwnerRules(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
32
32
|
}
|
package/dist/kit-cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { parseArgs } from 'node:util';
|
|
3
2
|
import { answerGitCredential } from './app-git.js';
|
|
4
3
|
import { chatgpt } from './chatgpt.js';
|
|
5
4
|
import { login } from './login.js';
|
|
6
5
|
import { logout } from './logout.js';
|
|
6
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
7
7
|
import { resident } from './resident.js';
|
|
8
8
|
const USAGE = 'usage: kit <login|logout|resident|chatgpt>\n';
|
|
9
9
|
const COMMANDS = new Map([
|
|
@@ -36,7 +36,7 @@ const COMMANDS = new Map([
|
|
|
36
36
|
],
|
|
37
37
|
]);
|
|
38
38
|
const [command, ...argv] = process.argv.slice(2);
|
|
39
|
-
|
|
39
|
+
proxyUnlessRelayed();
|
|
40
40
|
async function run() {
|
|
41
41
|
if (command === 'login')
|
|
42
42
|
return login(argv);
|
package/dist/kit-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`kit: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`kit: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/login.js
CHANGED
|
@@ -10,6 +10,7 @@ import { refusalOf } from './refusals.js';
|
|
|
10
10
|
import { selectRelay } from './relay.js';
|
|
11
11
|
import { startService } from './service.js';
|
|
12
12
|
import { placeSkillSet } from './skills.js';
|
|
13
|
+
import { endUnserved } from './wire.js';
|
|
13
14
|
export const DEFAULT_HOUSE = 'https://agents.house';
|
|
14
15
|
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
15
16
|
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
@@ -25,6 +26,7 @@ export async function accepted(house, credential) {
|
|
|
25
26
|
return true;
|
|
26
27
|
if (checked.status === 401)
|
|
27
28
|
return false;
|
|
29
|
+
endUnserved(checked.text);
|
|
28
30
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
29
31
|
}
|
|
30
32
|
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
@@ -119,7 +121,7 @@ export async function login(argv) {
|
|
|
119
121
|
open(started.start_url);
|
|
120
122
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
121
123
|
}
|
|
122
|
-
await placeSkillSet();
|
|
124
|
+
await placeSkillSet(house);
|
|
123
125
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
124
126
|
const relayRecord = relay === undefined ? {} : { relay };
|
|
125
127
|
await writeEnrolment({
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { KIT_RELEASE } from './clis.js';
|
|
3
3
|
import { relayed } from './relay.js';
|
|
4
|
+
import { KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
|
|
4
5
|
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
5
6
|
export const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
6
7
|
export class Unsettled extends Error {
|
|
@@ -33,6 +34,7 @@ export async function callHouse(origin, credential, message, operation = null, s
|
|
|
33
34
|
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
34
35
|
'mcp-method': message.method,
|
|
35
36
|
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
37
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
36
38
|
},
|
|
37
39
|
body: mcpBody(message, operation),
|
|
38
40
|
signal,
|
package/dist/relay.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
const PROXY = /^https?_proxy$/i;
|
|
3
|
+
const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
1
4
|
let selected;
|
|
5
|
+
let restoreProxy;
|
|
2
6
|
export function selectRelay(relay) {
|
|
3
7
|
selected = relay === undefined || relay === '' ? null : relay;
|
|
8
|
+
proxyUnlessRelayed();
|
|
4
9
|
}
|
|
5
10
|
export function relayOrigin() {
|
|
6
11
|
if (selected !== undefined)
|
|
@@ -8,6 +13,22 @@ export function relayOrigin() {
|
|
|
8
13
|
const ambient = process.env.HOUSE_KIT_RELAY;
|
|
9
14
|
return ambient === undefined || ambient === '' ? undefined : ambient;
|
|
10
15
|
}
|
|
16
|
+
function unlessRelayed() {
|
|
17
|
+
if (relayOrigin() === undefined)
|
|
18
|
+
return { ...process.env };
|
|
19
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
20
|
+
}
|
|
21
|
+
export function proxyUnlessRelayed() {
|
|
22
|
+
restoreProxy?.();
|
|
23
|
+
restoreProxy = setGlobalProxyFromEnv(unlessRelayed());
|
|
24
|
+
}
|
|
25
|
+
export function childEnvironment() {
|
|
26
|
+
const environment = unlessRelayed();
|
|
27
|
+
if (!Object.keys(environment).some((name) => PROXY.test(name)))
|
|
28
|
+
return environment;
|
|
29
|
+
const bypassed = [environment.NO_PROXY ?? environment.no_proxy, ...LOOPBACK].filter((entry) => entry !== undefined && entry !== '').join(',');
|
|
30
|
+
return { ...environment, NO_PROXY: bypassed, no_proxy: bypassed };
|
|
31
|
+
}
|
|
11
32
|
export function relayed(target) {
|
|
12
33
|
const url = new URL(target);
|
|
13
34
|
const relay = relayOrigin();
|
package/dist/resident.js
CHANGED
|
@@ -10,10 +10,11 @@ import { house } from './api.js';
|
|
|
10
10
|
import { received } from './bridge.js';
|
|
11
11
|
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
|
-
import { restart } from './end.js';
|
|
14
|
-
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
13
|
+
import { restart, unknownMethod } from './end.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, readEnrolment, restartAsked } from './home.js';
|
|
15
15
|
import { Calls } from './calls.js';
|
|
16
16
|
import { killDescendants } from './scope.js';
|
|
17
|
+
import { removeInputs, Scripts } from './scripts.js';
|
|
17
18
|
import { quoted } from './shell.js';
|
|
18
19
|
import { SignIns } from './sign-in.js';
|
|
19
20
|
import { placeSkillSet } from './skills.js';
|
|
@@ -116,7 +117,7 @@ async function started() {
|
|
|
116
117
|
killDescendants();
|
|
117
118
|
process.exit(1);
|
|
118
119
|
});
|
|
119
|
-
await placeSkillSet();
|
|
120
|
+
await placeSkillSet((await readEnrolment()).house);
|
|
120
121
|
await mkdir(agentBase(), { recursive: true });
|
|
121
122
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
122
123
|
await installShim(codexLauncher(), 'codex-main');
|
|
@@ -142,6 +143,9 @@ export async function resident() {
|
|
|
142
143
|
else if (restartAsked() && conversations.quiet()) {
|
|
143
144
|
restart('restarting for an updated CLI');
|
|
144
145
|
}
|
|
146
|
+
else if (update.held() && conversations.quiet()) {
|
|
147
|
+
update.apply(connection.pause());
|
|
148
|
+
}
|
|
145
149
|
else if (!reported) {
|
|
146
150
|
reported = true;
|
|
147
151
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
@@ -157,10 +161,13 @@ export async function resident() {
|
|
|
157
161
|
changed,
|
|
158
162
|
});
|
|
159
163
|
const signIns = new SignIns(house, agents, calls);
|
|
160
|
-
const update = new KitUpdate(calls);
|
|
164
|
+
const update = new KitUpdate(calls, changed);
|
|
165
|
+
removeInputs();
|
|
166
|
+
const scripts = new Scripts(calls, (notification) => connection?.send(notification) ?? false);
|
|
161
167
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
162
168
|
process.once(signal, () => {
|
|
163
169
|
killDescendants();
|
|
170
|
+
removeInputs();
|
|
164
171
|
process.kill(process.pid, signal);
|
|
165
172
|
});
|
|
166
173
|
}
|
|
@@ -173,15 +180,18 @@ export async function resident() {
|
|
|
173
180
|
changed();
|
|
174
181
|
logged(copies.connected());
|
|
175
182
|
},
|
|
183
|
+
closed: () => scripts.disconnected(),
|
|
176
184
|
call: (call) => {
|
|
177
185
|
if (call.kind === 'sign_in')
|
|
178
186
|
signIns.start(call);
|
|
179
187
|
else if (call.kind === 'update')
|
|
180
|
-
update.
|
|
188
|
+
update.hold(call);
|
|
189
|
+
else if (call.kind === 'run_wizard' || call.kind === 'run_function')
|
|
190
|
+
scripts.start(call);
|
|
181
191
|
else if (CONVERSATION_METHODS.has(call.kind))
|
|
182
192
|
conversations.call(call);
|
|
183
193
|
else {
|
|
184
|
-
const refused = { refused:
|
|
194
|
+
const refused = { refused: unknownMethod(call.kind) };
|
|
185
195
|
void calls.carry(call.input_id, async () => ({ result: refused }));
|
|
186
196
|
}
|
|
187
197
|
},
|
|
@@ -192,6 +202,7 @@ export async function resident() {
|
|
|
192
202
|
logged(copies.changed(notification.rooms));
|
|
193
203
|
if (notification.type === 'work_available' && notification.subject === 'agents')
|
|
194
204
|
logged(agents.refresh());
|
|
205
|
+
scripts.notified(notification);
|
|
195
206
|
},
|
|
196
207
|
});
|
|
197
208
|
}
|
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/sign-in.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
-
import { CLIS
|
|
3
|
+
import { CLIS } from './clis.js';
|
|
4
|
+
import { childEnvironment } from './relay.js';
|
|
4
5
|
import { killTree } from './scope.js';
|
|
5
6
|
import { holdSecretInput } from './secret-input.js';
|
|
6
7
|
const ESCAPE = /\u001b\[[\d;]*m/g;
|
|
@@ -56,7 +57,7 @@ export class SignIns {
|
|
|
56
57
|
const child = spawn(cli, login.args, {
|
|
57
58
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
58
59
|
detached: true,
|
|
59
|
-
env:
|
|
60
|
+
env: childEnvironment(),
|
|
60
61
|
});
|
|
61
62
|
const running = () => child.exitCode === null && child.signalCode === null;
|
|
62
63
|
const stop = () => killTree(child);
|
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
|
+
}
|