@agentshouse/kit 0.2.0 → 0.2.1-alpha.1

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/resident.js CHANGED
@@ -1,10 +1,66 @@
1
+ import { killDescendants } from './acp.js';
2
+ import { Agents } from './agents.js';
3
+ import { house } from './api.js';
4
+ import { Conversations } from './conversations.js';
1
5
  import { readEnrolment } from './home.js';
6
+ import { SignIns } from './sign-in.js';
2
7
  import { holdStream } from './stream.js';
8
+ function report(error) {
9
+ process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
10
+ }
11
+ function logged(work) {
12
+ work.catch(report);
13
+ }
3
14
  export async function resident() {
4
15
  if ((await readEnrolment()) === null) {
5
16
  process.stderr.write('kit: this Environment is not enrolled; run kit login\n');
6
17
  process.exitCode = 1;
7
18
  return;
8
19
  }
9
- holdStream();
20
+ process.env.HOUSE_KIT_RESIDENT = String(process.pid);
21
+ const agents = new Agents(house);
22
+ let stream = null;
23
+ let reported = false;
24
+ const idle = () => conversations.idle() && signIns.idle();
25
+ const changed = () => {
26
+ if (!idle()) {
27
+ reported = false;
28
+ }
29
+ else if (!reported) {
30
+ reported = true;
31
+ logged(house.deliver('/kit/idle', {}, idle));
32
+ }
33
+ };
34
+ const conversations = new Conversations({
35
+ house,
36
+ agents,
37
+ send: (frame) => stream?.send(frame) ?? false,
38
+ changed,
39
+ });
40
+ const signIns = new SignIns(house, agents, changed);
41
+ for (const signal of ['SIGTERM', 'SIGINT']) {
42
+ process.once(signal, () => {
43
+ killDescendants();
44
+ process.kill(process.pid, signal);
45
+ });
46
+ }
47
+ await house.deliver('/kit/restarted', {}).catch(report);
48
+ logged(agents.refresh());
49
+ stream = holdStream({
50
+ opened: () => {
51
+ conversations.opened();
52
+ changed();
53
+ },
54
+ frame: (received) => {
55
+ if (received.type === 'work_available' && received.subject === 'agents')
56
+ logged(agents.refresh());
57
+ if (received.type === 'input') {
58
+ reported = false;
59
+ if (received.kind === 'sign_in')
60
+ signIns.start(received);
61
+ else
62
+ conversations.input(received);
63
+ }
64
+ },
65
+ });
10
66
  }
@@ -0,0 +1,16 @@
1
+ export async function holdSecretInput(house, subject, steps, waiting) {
2
+ while (waiting()) {
3
+ try {
4
+ const held = await house.post(`/kit/secret-input/${subject}`, { steps });
5
+ if (held.outcome === 'collected')
6
+ return held.content;
7
+ if (held.release !== 'held')
8
+ return null;
9
+ }
10
+ catch (error) {
11
+ process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
12
+ await new Promise((resolve) => setTimeout(resolve, 1000));
13
+ }
14
+ }
15
+ return null;
16
+ }
@@ -0,0 +1,83 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createInterface } from 'node:readline';
3
+ import { killTree } from './acp.js';
4
+ import { CLIS, cliCommand } from './clis.js';
5
+ import { holdSecretInput } from './secret-input.js';
6
+ const ESCAPE = /\u001b\[[\d;]*m/g;
7
+ const LINK = /https:\/\/\S+/;
8
+ const CODE = /^[A-Z0-9]+-[A-Z0-9]+$/;
9
+ function logged(error) {
10
+ process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
11
+ }
12
+ export class SignIns {
13
+ house;
14
+ agents;
15
+ changed;
16
+ running = new Set();
17
+ constructor(house, agents, changed) {
18
+ this.house = house;
19
+ this.agents = agents;
20
+ this.changed = changed;
21
+ }
22
+ idle() {
23
+ return this.running.size === 0;
24
+ }
25
+ start(input) {
26
+ if (this.running.has(input.input_id))
27
+ return;
28
+ this.running.add(input.input_id);
29
+ this.changed();
30
+ this.login(input)
31
+ .then(() => this.agents.report())
32
+ .then(() => this.house.deliver(`/kit/inputs/${input.input_id}/ack`, {}))
33
+ .catch(logged)
34
+ .finally(() => {
35
+ this.running.delete(input.input_id);
36
+ this.changed();
37
+ });
38
+ }
39
+ async login(input) {
40
+ await this.agents.installed();
41
+ const login = CLIS[input.cli].login;
42
+ const child = spawn(cliCommand(input.cli), login.args, { stdio: ['pipe', 'pipe', 'pipe'], detached: true });
43
+ const running = () => child.exitCode === null && child.signalCode === null;
44
+ let link;
45
+ let code;
46
+ let last = '';
47
+ let held = false;
48
+ const read = (line) => {
49
+ const plain = line.replace(ESCAPE, '').trim();
50
+ if (plain === '')
51
+ return;
52
+ last = plain;
53
+ link ??= LINK.exec(plain)?.[0];
54
+ if (CODE.test(plain))
55
+ code ??= plain;
56
+ const step = login.code === 'collect'
57
+ ? { kind: 'collect', label: 'Code shown after you sign in', name: 'code' }
58
+ : code === undefined
59
+ ? undefined
60
+ : { kind: 'show', label: 'One-time code', text: code };
61
+ if (held || link === undefined || step === undefined)
62
+ return;
63
+ held = true;
64
+ const steps = [{ kind: 'visit', label: 'Open this link and sign in', url: link }, step];
65
+ void holdSecretInput(this.house, input.input_id, steps, running).then((content) => {
66
+ if (!running())
67
+ return;
68
+ if (content === null)
69
+ killTree(child);
70
+ else
71
+ child.stdin.end(`${content.code}\n`);
72
+ });
73
+ };
74
+ for (const output of [child.stdout, child.stderr])
75
+ createInterface({ input: output }).on('line', read);
76
+ const failed = await new Promise((resolve) => {
77
+ child.on('error', (error) => resolve(error.message));
78
+ child.on('close', (status, signal) => resolve(status === 0 ? null : `exited with ${status ?? signal}${last ? `: ${last}` : ''}`));
79
+ });
80
+ if (failed !== null)
81
+ process.stderr.write(`kit: the ${input.cli} sign-in failed: ${failed}\n`);
82
+ }
83
+ }
package/dist/stream.js CHANGED
@@ -5,7 +5,8 @@ function streamUrl(house) {
5
5
  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
6
6
  return url;
7
7
  }
8
- export function holdStream() {
8
+ export function holdStream(handlers) {
9
+ let socket = null;
9
10
  let failures = 0;
10
11
  const connect = async () => {
11
12
  const { house, credential } = (await readEnrolment());
@@ -17,9 +18,16 @@ export function holdStream() {
17
18
  opening.onopen = () => {
18
19
  opened = true;
19
20
  failures = 0;
21
+ socket = opening;
22
+ handlers.opened();
23
+ };
24
+ opening.onmessage = (event) => {
25
+ handlers.frame(JSON.parse(String(event.data)));
20
26
  };
21
27
  opening.onerror = () => undefined;
22
28
  opening.onclose = (event) => {
29
+ if (socket === opening)
30
+ socket = null;
23
31
  process.stderr.write(`kit: control stream closed ${event.code} ${event.reason}\n`);
24
32
  const delay = opened
25
33
  ? 1000 + Math.random() * 2000
@@ -28,4 +36,12 @@ export function holdStream() {
28
36
  };
29
37
  };
30
38
  void connect();
39
+ return {
40
+ send(frame) {
41
+ if (socket === null || socket.readyState !== WebSocket.OPEN)
42
+ return false;
43
+ socket.send(JSON.stringify(frame));
44
+ return true;
45
+ },
46
+ };
31
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.2.0",
3
+ "version": "0.2.1-alpha.1",
4
4
  "description": "House Kit connects an Environment to agents.house.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,6 +19,25 @@
19
19
  "build": "tsc -p tsconfig.build.json",
20
20
  "test": "tsc -p tsconfig.json && vitest run"
21
21
  },
22
+ "dependencies": {
23
+ "@agentclientprotocol/sdk": "1.7.0"
24
+ },
25
+ "peerDependencies": {
26
+ "@agentclientprotocol/claude-agent-acp": "0.85.1",
27
+ "@agentclientprotocol/codex-acp": "2.1.1",
28
+ "@xai-official/grok": "1.0.46"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "@agentclientprotocol/claude-agent-acp": {
32
+ "optional": true
33
+ },
34
+ "@agentclientprotocol/codex-acp": {
35
+ "optional": true
36
+ },
37
+ "@xai-official/grok": {
38
+ "optional": true
39
+ }
40
+ },
22
41
  "devDependencies": {
23
42
  "@types/node": "24.13.6",
24
43
  "@types/ws": "8.18.2",