@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/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, setGlobalProxyFromEnv } from 'node:http';
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
- setGlobalProxyFromEnv();
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
- async function sentPart(url, file, start, end) {
135
- const answer = await opened(url, 'PUT', { 'content-length': String(end - start) }, end === start ? undefined : createReadStream(file, { start, end: end - 1 }));
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 && ('upload' in answer || 'checking' in answer) ? answer : { text };
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
- send: (url, start, end) => sentPart(url, file, start, end),
190
- finish: (id) => finished(id, destination.room_ref),
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
  }
@@ -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 < 14)) {
4
- process.stderr.write(`house: the Kit needs Node 24.14.0 or newer; this is ${process.versions.node}\n`);
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 {
@@ -1,6 +1,6 @@
1
1
  import { agentInstructions } from '@agentshouse/skills';
2
2
  import { HOUSE_REF } from './git.js';
3
- const HOW_WE_WORK = '/rooms/private/how-we-work.md';
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 readHowWeWork(bridge) {
11
+ async function readOwnerRules(bridge) {
12
12
  let document = '';
13
- for (let command = `cat ${HOW_WE_WORK}`; command !== undefined;) {
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 readHowWeWork(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
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
- setGlobalProxyFromEnv();
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 < 14)) {
4
- process.stderr.write(`kit: the Kit needs Node 24.14.0 or newer; this is ${process.versions.node}\n`);
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.start(call);
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: `This Kit does not know ${call.kind}; update the Kit` };
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
  }
@@ -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, withoutProxy } from './clis.js';
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: withoutProxy(),
60
+ env: childEnvironment(),
60
61
  });
61
62
  const running = () => child.exitCode === null && child.signalCode === null;
62
63
  const stop = () => killTree(child);
@@ -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
+ }