@agentshouse/kit 0.2.1-c4beedfd8b549 → 0.2.1-c4d151f197958

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 CHANGED
@@ -33,15 +33,16 @@ them out; running it again without the flag brings them back.
33
33
 
34
34
  ## Commands
35
35
 
36
- `kit login` opens a link you confirm in your browser and keeps the
37
- Environment's credential in `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is
38
- set. Pass `--manual` to paste the code by hand on a machine without a browser,
39
- and `--environment <id>` to reconnect an Environment you already have. It also
36
+ `kit login` opens or prints a link you confirm in any browser, on this
37
+ computer or another, continues on its own and keeps the Environment's
38
+ credential in `~/.house-kit`, or in `$HOUSE_KIT_HOME` when it is set. Pass
39
+ `--environment <id>` to reconnect an Environment you already have. It also
40
40
  connects your own agent: outside an Agent conversation, `house <tool>` calls
41
41
  House as you and `git` clones and pushes your Apps at `<house>/app/` as you,
42
42
  until you revoke that connection on Connections. `house help` lists the Tools.
43
- `kit logout` disconnects this computer, stops House Kit and ends that Git
44
- access; a later `kit login` reconnects the same Environment.
43
+ `kit logout` disconnects this computer, stops House Kit and ends your own
44
+ agent's connection, so `house` and `git` no longer act as you here; a later
45
+ `kit login` reconnects the same Environment.
45
46
 
46
47
  `kit resident` keeps the Environment connected, and keeps the Rooms you chose
47
48
  for this Environment as Git repositories under `local-copies` in the
package/dist/api.js CHANGED
@@ -19,7 +19,7 @@ export function retryDelay(attempt) {
19
19
  // Full jitter, doubling from one second to a 30-second cap, spreads the Kits one outage dropped and retries within half a minute of House's return.
20
20
  return Math.random() * Math.min(30_000, 1000 * 2 ** attempt);
21
21
  }
22
- export async function retrying(sent, signal) {
22
+ export async function retrying(sent, signal, pause = retryDelay) {
23
23
  for (let attempt = 0;; attempt++) {
24
24
  try {
25
25
  return await sent();
@@ -28,8 +28,8 @@ export async function retrying(sent, signal) {
28
28
  if (!(error instanceof HouseRefusal) || retryable(error.text) !== true)
29
29
  throw error;
30
30
  }
31
- // A request House refused as retryable is sent again on the jittered curve.
32
- await delay(retryDelay(attempt), undefined, { signal });
31
+ // A request House refused as retryable is sent again after the caller's pause, the jittered curve unless it names its own.
32
+ await delay(pause(attempt), undefined, { signal });
33
33
  }
34
34
  }
35
35
  export async function post(house, path, body, credential, signal) {
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "version": "0.2.1-c4beedfd8b549",
3
+ "version": "0.2.1-c4d151f197958",
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-c4beedfd8b549/connect-linux.sh"
8
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c4d151f197958/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-c4beedfd8b549/connect-linux.sh"
14
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c4d151f197958/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-c4beedfd8b549/connect-windows.ps1"
20
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c4d151f197958/connect-windows.ps1"
21
21
  }
22
22
  }
23
23
  }
package/dist/clis.js CHANGED
@@ -90,7 +90,7 @@ export const CLIS = {
90
90
  minimum: '1.0.46',
91
91
  install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
92
92
  adapter: null,
93
- args: ['agent', '--always-approve', '--no-leader', 'stdio'],
93
+ args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
94
94
  fullAccess: null,
95
95
  sessionMeta: () => ({}),
96
96
  renames: false,
package/dist/login.js CHANGED
@@ -1,54 +1,19 @@
1
+ import { spawn } from 'node:child_process';
1
2
  import { createHash, randomBytes } from 'node:crypto';
2
- import { createServer } from 'node:http';
3
3
  import { hostname } from 'node:os';
4
- import { createInterface } from 'node:readline/promises';
5
4
  import { parseArgs } from 'node:util';
6
- import { post } from './api.js';
5
+ import { post, retrying } from './api.js';
7
6
  import { placeAppGit } from './app-git.js';
8
7
  import { forgetOwnAgent, readDisconnected, readEnrolment, readOwnAgent, writeEnrolment, writeOwnAgent } from './home.js';
9
8
  import { callHouse, rpc } from './mcp.js';
10
9
  const DEFAULT_HOUSE = 'https://agents.house';
11
- function listen() {
12
- return new Promise((resolve, reject) => {
13
- let deliver = () => undefined;
14
- let held = null;
15
- const code = new Promise((settle) => {
16
- deliver = settle;
17
- });
18
- const server = createServer((request, response) => {
19
- const received = new URL(request.url ?? '/', 'http://127.0.0.1').searchParams.get('code');
20
- if (received === null || held !== null) {
21
- response.writeHead(404).end();
22
- return;
23
- }
24
- held = response;
25
- deliver(received);
26
- });
27
- const close = () => {
28
- server.close();
29
- server.closeAllConnections();
30
- };
31
- const port = Number(process.env.HOUSE_KIT_LOGIN_PORT ?? 0);
32
- server.once('error', reject);
33
- server.listen(port, port === 0 ? '127.0.0.1' : '0.0.0.0', () => {
34
- const address = server.address();
35
- if (address === null || typeof address === 'string') {
36
- reject(new Error('the login callback has no port'));
37
- return;
38
- }
39
- resolve({
40
- uri: `http://127.0.0.1:${address.port}/callback`,
41
- code,
42
- answer: (text) => new Promise((written) => {
43
- if (held === null || held.destroyed || !held.socket || held.socket.destroyed)
44
- return written();
45
- held.once('close', () => written());
46
- held.writeHead(200, { 'content-type': 'text/plain; charset=utf-8', connection: 'close' }).end(`${text}\n`);
47
- }),
48
- close,
49
- });
50
- });
51
- });
10
+ const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
11
+ // Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
12
+ const POLL_INTERVAL_MS = 2000;
13
+ function open(link) {
14
+ spawn(OPENER, [link], { detached: true, stdio: 'ignore' })
15
+ .on('error', () => undefined)
16
+ .unref();
52
17
  }
53
18
  async function accepted(house, credential) {
54
19
  const checked = await callHouse(house, credential, rpc('tools/list', {}));
@@ -58,25 +23,12 @@ async function accepted(house, credential) {
58
23
  return false;
59
24
  throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
60
25
  }
61
- async function typed(question) {
62
- const reader = createInterface({ input: process.stdin, output: process.stdout });
63
- const closed = new Promise((_, reject) => {
64
- reader.once('close', () => reject(new Error('no code was typed')));
65
- });
66
- try {
67
- return (await Promise.race([reader.question(question), closed])).trim();
68
- }
69
- finally {
70
- reader.close();
71
- }
72
- }
73
26
  export async function login(argv) {
74
27
  const { values } = parseArgs({
75
28
  args: argv,
76
29
  options: {
77
30
  house: { type: 'string' },
78
31
  environment: { type: 'string' },
79
- manual: { type: 'boolean', default: false },
80
32
  },
81
33
  });
82
34
  const enrolled = (await readEnrolment()) ?? (await readDisconnected());
@@ -86,55 +38,24 @@ export async function login(argv) {
86
38
  const ownAgent = await readOwnAgent();
87
39
  const held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
88
40
  const verifier = randomBytes(32).toString('base64url');
89
- const started = {
41
+ const started = await retrying(() => post(house, '/kit', {
90
42
  ...(environment === undefined
91
43
  ? { act: 'new_environment', label: hostname(), ...(replaces === undefined ? {} : { replaces }) }
92
44
  : { act: 'existing_environment', environment_id: environment }),
93
45
  code_challenge: createHash('sha256').update(verifier).digest('base64url'),
94
46
  ...(held ? {} : { headless: true }),
95
- };
96
- const exchange = async (code) => {
97
- const exchanged = await post(house, '/kit/token', { code, code_verifier: verifier });
98
- await writeEnrolment({ house, environment: exchanged.environment, credential: exchanged.credential });
99
- process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
100
- if (exchanged.headless_credential !== undefined) {
101
- await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
102
- }
103
- else if (held && ownAgent.user !== exchanged.user) {
104
- await forgetOwnAgent();
105
- process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
106
- }
107
- };
108
- const placeGit = async () => placeAppGit(ownAgent, await readOwnAgent());
109
- if (values.manual) {
110
- const { start_url } = await post(house, '/kit', {
111
- ...started,
112
- presentation: 'manual',
113
- });
114
- process.stdout.write(`Open this link and confirm: ${start_url}\n`);
115
- await exchange(await typed('Code: '));
116
- await placeGit();
117
- return;
118
- }
119
- const callback = await listen();
120
- try {
121
- const { start_url } = await post(house, '/kit', {
122
- ...started,
123
- presentation: 'loopback',
124
- loopback_uri: callback.uri,
125
- });
126
- process.stdout.write(`Open this link and confirm: ${start_url}\n`);
127
- try {
128
- await exchange(await callback.code);
129
- }
130
- catch (error) {
131
- await callback.answer(`This Environment was not connected: ${error.message}`);
132
- throw error;
133
- }
134
- await callback.answer('This Environment is connected. You can close this tab.');
47
+ }));
48
+ process.stdout.write(`Open this link and confirm: ${started.start_url}\n`);
49
+ open(started.start_url);
50
+ const exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
51
+ await writeEnrolment({ house, environment: exchanged.environment, credential: exchanged.credential });
52
+ process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
53
+ if (exchanged.headless_credential !== undefined) {
54
+ await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
135
55
  }
136
- finally {
137
- callback.close();
56
+ else if (held && ownAgent.user !== exchanged.user) {
57
+ await forgetOwnAgent();
58
+ process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
138
59
  }
139
- await placeGit();
60
+ await placeAppGit(ownAgent, await readOwnAgent());
140
61
  }
package/dist/logout.js CHANGED
@@ -1,21 +1,27 @@
1
1
  import { HouseRefusal, post } from './api.js';
2
2
  import { forgetAppGit } from './app-git.js';
3
- import { forgetEnrolment, readEnrolment } from './home.js';
3
+ import { forgetEnrolment, forgetOwnAgent, readEnrolment, readOwnAgent } from './home.js';
4
+ const KEPT_CONNECTION = "House had already disconnected this computer, so its own agent's connection stays until you revoke it on Connections.\n";
4
5
  export async function logout() {
5
6
  const enrolled = await readEnrolment();
6
- if (enrolled === null) {
7
- await forgetAppGit();
8
- process.stdout.write('This computer is not connected to House.\n');
9
- return;
7
+ const ownAgent = await readOwnAgent();
8
+ let refused = false;
9
+ if (enrolled !== null) {
10
+ try {
11
+ await post(enrolled.house, '/kit/logout', {}, enrolled.credential);
12
+ }
13
+ catch (error) {
14
+ if (!(error instanceof HouseRefusal && error.status === 401))
15
+ throw error;
16
+ refused = true;
17
+ }
18
+ await forgetEnrolment(enrolled);
10
19
  }
11
- try {
12
- await post(enrolled.house, '/kit/logout', {}, enrolled.credential);
13
- }
14
- catch (error) {
15
- if (!(error instanceof HouseRefusal && error.status === 401))
16
- throw error;
17
- }
18
- await forgetEnrolment(enrolled);
19
20
  await forgetAppGit();
20
- process.stdout.write(`Environment ${enrolled.environment} is disconnected; kit login reconnects it.\n`);
21
+ await forgetOwnAgent();
22
+ process.stdout.write(enrolled === null
23
+ ? 'This computer is not connected to House.\n'
24
+ : `Environment ${enrolled.environment} is disconnected; kit login reconnects it.\n`);
25
+ if (ownAgent !== null && (enrolled === null || refused))
26
+ process.stdout.write(KEPT_CONNECTION);
21
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.2.1-c4beedfd8b549",
3
+ "version": "0.2.1-c4d151f197958",
4
4
  "description": "House Kit connects an Environment to agents.house.",
5
5
  "license": "MIT",
6
6
  "repository": {