@agentshouse/kit 0.1.0-alpha.9 → 0.2.0

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.
Files changed (83) hide show
  1. package/README.md +14 -1740
  2. package/dist/api.js +11 -0
  3. package/dist/home.js +24 -0
  4. package/dist/house-main.js +4 -0
  5. package/dist/kit-main.js +16 -0
  6. package/dist/login.js +112 -0
  7. package/dist/resident.js +10 -0
  8. package/dist/stream.js +31 -0
  9. package/package.json +16 -11
  10. package/bin/connect-linux.sh +0 -134
  11. package/bin/kit.js +0 -1023
  12. package/bootstrap.json +0 -17
  13. package/src/acp.js +0 -99
  14. package/src/adapter-mcp.js +0 -44
  15. package/src/adapter.js +0 -252
  16. package/src/agent-logins.js +0 -65
  17. package/src/agents.js +0 -504
  18. package/src/apply.js +0 -29
  19. package/src/attach.js +0 -52
  20. package/src/binding.js +0 -90
  21. package/src/blocks.js +0 -156
  22. package/src/bridge.js +0 -449
  23. package/src/bytes.js +0 -89
  24. package/src/channel.js +0 -37
  25. package/src/command.js +0 -109
  26. package/src/configuration.js +0 -465
  27. package/src/connector-login.js +0 -91
  28. package/src/connector.js +0 -107
  29. package/src/continuation.js +0 -61
  30. package/src/contract.js +0 -363
  31. package/src/conversation-originals.js +0 -143
  32. package/src/custom/authorization.js +0 -63
  33. package/src/declaration.js +0 -35
  34. package/src/delivery.js +0 -191
  35. package/src/directory.js +0 -102
  36. package/src/draft.js +0 -89
  37. package/src/google/calendar.js +0 -177
  38. package/src/google/declared.js +0 -146
  39. package/src/google/drive.js +0 -435
  40. package/src/google/gateway.js +0 -166
  41. package/src/google/gmail.js +0 -249
  42. package/src/google/id.js +0 -1
  43. package/src/google/oauth.js +0 -273
  44. package/src/google/source.js +0 -25
  45. package/src/hook.js +0 -376
  46. package/src/host.js +0 -121
  47. package/src/house-main.js +0 -79
  48. package/src/house.js +0 -1090
  49. package/src/inbound.js +0 -175
  50. package/src/instance/google.js +0 -1
  51. package/src/kit-work-stream.js +0 -84
  52. package/src/launcher-main.js +0 -19
  53. package/src/launcher.js +0 -276
  54. package/src/local-files.js +0 -113
  55. package/src/login.js +0 -274
  56. package/src/mcp.js +0 -71
  57. package/src/migrator.js +0 -248
  58. package/src/mount.js +0 -582
  59. package/src/operation.js +0 -20
  60. package/src/original.js +0 -631
  61. package/src/peer.js +0 -124
  62. package/src/plan.js +0 -57
  63. package/src/question.js +0 -316
  64. package/src/refusal.js +0 -79
  65. package/src/repository.js +0 -521
  66. package/src/session-bin/house +0 -2
  67. package/src/settlement.js +0 -57
  68. package/src/setup.js +0 -190
  69. package/src/source-originals.js +0 -116
  70. package/src/source.js +0 -286
  71. package/src/starter-set/grilling/LICENSE +0 -21
  72. package/src/starter-set/grilling/SKILL.md +0 -30
  73. package/src/starter-set/grilling/agents/openai.yaml +0 -3
  74. package/src/starter-set.js +0 -17
  75. package/src/state.js +0 -157
  76. package/src/sync-library.js +0 -293
  77. package/src/sync-state.js +0 -120
  78. package/src/sync.js +0 -482
  79. package/src/telegram.js +0 -1045
  80. package/src/terminal.js +0 -57
  81. package/src/tmux.js +0 -119
  82. package/src/worker.js +0 -925
  83. package/src/workspace.js +0 -500
package/dist/api.js ADDED
@@ -0,0 +1,11 @@
1
+ export async function post(house, path, body) {
2
+ const answer = await fetch(new URL(path, house), {
3
+ method: 'POST',
4
+ headers: { 'content-type': 'application/json' },
5
+ body: JSON.stringify(body),
6
+ });
7
+ const text = await answer.text();
8
+ if (!answer.ok)
9
+ throw new Error(`House refused ${path} with ${answer.status}: ${text}`);
10
+ return JSON.parse(text);
11
+ }
package/dist/home.js ADDED
@@ -0,0 +1,24 @@
1
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ const ENROLMENT = 'credential.json';
5
+ export function kitHome() {
6
+ return process.env.HOUSE_KIT_HOME ?? join(homedir(), '.house-kit');
7
+ }
8
+ export async function readEnrolment() {
9
+ try {
10
+ return JSON.parse(await readFile(join(kitHome(), ENROLMENT), 'utf8'));
11
+ }
12
+ catch (error) {
13
+ if (error.code === 'ENOENT')
14
+ return null;
15
+ throw error;
16
+ }
17
+ }
18
+ export async function writeEnrolment(enrolment) {
19
+ const home = kitHome();
20
+ await mkdir(home, { recursive: true, mode: 0o700 });
21
+ const temporary = join(home, `${ENROLMENT}.${process.pid}`);
22
+ await writeFile(temporary, `${JSON.stringify(enrolment)}\n`, { mode: 0o600 });
23
+ await rename(temporary, join(home, ENROLMENT));
24
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ process.stderr.write('house: house runs inside an Agent conversation\n');
3
+ process.exitCode = 1;
4
+ export {};
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { login } from './login.js';
3
+ import { resident } from './resident.js';
4
+ const [command, ...argv] = process.argv.slice(2);
5
+ async function run() {
6
+ if (command === 'login')
7
+ return login(argv);
8
+ if (command === 'resident')
9
+ return resident();
10
+ process.stderr.write('usage: kit <login|resident>\n');
11
+ process.exitCode = 2;
12
+ }
13
+ run().catch((error) => {
14
+ process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
15
+ process.exitCode = 1;
16
+ });
package/dist/login.js ADDED
@@ -0,0 +1,112 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { hostname } from 'node:os';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { parseArgs } from 'node:util';
6
+ import { post } from './api.js';
7
+ import { readEnrolment, writeEnrolment } from './home.js';
8
+ const DEFAULT_HOUSE = 'https://agents.house';
9
+ function listen() {
10
+ return new Promise((resolve, reject) => {
11
+ let deliver = () => undefined;
12
+ let held = null;
13
+ const code = new Promise((settle) => {
14
+ deliver = settle;
15
+ });
16
+ const server = createServer((request, response) => {
17
+ const received = new URL(request.url ?? '/', 'http://127.0.0.1').searchParams.get('code');
18
+ if (received === null || held !== null) {
19
+ response.writeHead(404).end();
20
+ return;
21
+ }
22
+ held = response;
23
+ deliver(received);
24
+ });
25
+ const close = () => {
26
+ server.close();
27
+ server.closeAllConnections();
28
+ };
29
+ server.once('error', reject);
30
+ server.listen(0, '127.0.0.1', () => {
31
+ const address = server.address();
32
+ if (address === null || typeof address === 'string') {
33
+ reject(new Error('the login callback has no port'));
34
+ return;
35
+ }
36
+ resolve({
37
+ uri: `http://127.0.0.1:${address.port}/callback`,
38
+ code,
39
+ answer: (text) => new Promise((written) => {
40
+ if (held === null || held.destroyed || !held.socket || held.socket.destroyed)
41
+ return written();
42
+ held.once('close', () => written());
43
+ held.writeHead(200, { 'content-type': 'text/plain; charset=utf-8', connection: 'close' }).end(`${text}\n`);
44
+ }),
45
+ close,
46
+ });
47
+ });
48
+ });
49
+ }
50
+ async function typed(question) {
51
+ const reader = createInterface({ input: process.stdin, output: process.stdout });
52
+ try {
53
+ return (await reader.question(question)).trim();
54
+ }
55
+ finally {
56
+ reader.close();
57
+ }
58
+ }
59
+ export async function login(argv) {
60
+ const { values } = parseArgs({
61
+ args: argv,
62
+ options: {
63
+ house: { type: 'string' },
64
+ environment: { type: 'string' },
65
+ manual: { type: 'boolean', default: false },
66
+ },
67
+ });
68
+ const enrolled = await readEnrolment();
69
+ const house = values.house ?? enrolled?.house ?? DEFAULT_HOUSE;
70
+ const environment = values.environment ?? (enrolled?.house === house ? enrolled.environment : undefined);
71
+ const verifier = randomBytes(32).toString('base64url');
72
+ const started = {
73
+ ...(environment === undefined
74
+ ? { act: 'new_environment', label: hostname() }
75
+ : { act: 'existing_environment', environment_id: environment }),
76
+ code_challenge: createHash('sha256').update(verifier).digest('base64url'),
77
+ };
78
+ const exchange = async (code) => {
79
+ const exchanged = await post(house, '/kit/token', { code, code_verifier: verifier });
80
+ await writeEnrolment({ house, environment: exchanged.environment, credential: exchanged.credential });
81
+ process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
82
+ };
83
+ if (values.manual) {
84
+ const { start_url } = await post(house, '/kit', {
85
+ ...started,
86
+ presentation: 'manual',
87
+ });
88
+ process.stdout.write(`Open this link and confirm: ${start_url}\n`);
89
+ await exchange(await typed('Code: '));
90
+ return;
91
+ }
92
+ const callback = await listen();
93
+ try {
94
+ const { start_url } = await post(house, '/kit', {
95
+ ...started,
96
+ presentation: 'loopback',
97
+ loopback_uri: callback.uri,
98
+ });
99
+ process.stdout.write(`Open this link and confirm: ${start_url}\n`);
100
+ try {
101
+ await exchange(await callback.code);
102
+ }
103
+ catch (error) {
104
+ await callback.answer(`This Environment was not connected: ${error.message}`);
105
+ throw error;
106
+ }
107
+ await callback.answer('This Environment is connected. You can close this tab.');
108
+ }
109
+ finally {
110
+ callback.close();
111
+ }
112
+ }
@@ -0,0 +1,10 @@
1
+ import { readEnrolment } from './home.js';
2
+ import { holdStream } from './stream.js';
3
+ export async function resident() {
4
+ if ((await readEnrolment()) === null) {
5
+ process.stderr.write('kit: this Environment is not enrolled; run kit login\n');
6
+ process.exitCode = 1;
7
+ return;
8
+ }
9
+ holdStream();
10
+ }
package/dist/stream.js ADDED
@@ -0,0 +1,31 @@
1
+ import { readEnrolment } from './home.js';
2
+ const SUBPROTOCOL = 'house.kit.stream.1';
3
+ function streamUrl(house) {
4
+ const url = new URL('/kit/stream', house);
5
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
6
+ return url;
7
+ }
8
+ export function holdStream() {
9
+ let failures = 0;
10
+ const connect = async () => {
11
+ const { house, credential } = (await readEnrolment());
12
+ const opening = new WebSocket(streamUrl(house), {
13
+ protocols: [SUBPROTOCOL],
14
+ headers: { authorization: `Bearer ${credential}` },
15
+ });
16
+ let opened = false;
17
+ opening.onopen = () => {
18
+ opened = true;
19
+ failures = 0;
20
+ };
21
+ opening.onerror = () => undefined;
22
+ opening.onclose = (event) => {
23
+ process.stderr.write(`kit: control stream closed ${event.code} ${event.reason}\n`);
24
+ const delay = opened
25
+ ? 1000 + Math.random() * 2000
26
+ : Math.min(30_000, 1000 * 2 ** failures++);
27
+ setTimeout(() => void connect(), delay);
28
+ };
29
+ };
30
+ void connect();
31
+ }
package/package.json CHANGED
@@ -1,25 +1,30 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.1.0-alpha.9",
3
+ "version": "0.2.0",
4
+ "description": "House Kit connects an Environment to agents.house.",
4
5
  "license": "MIT",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/agentshouse/kit.git"
8
9
  },
9
- "private": false,
10
10
  "type": "module",
11
11
  "bin": {
12
- "kit": "bin/kit.js"
12
+ "kit": "dist/kit-main.js",
13
+ "house": "dist/house-main.js"
13
14
  },
14
- "exports": {
15
- "./bootstrap": "./bootstrap.json"
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "test": "tsc -p tsconfig.json && vitest run"
16
21
  },
17
- "dependencies": {
18
- "@agentclientprotocol/sdk": "1.5.0",
19
- "@agentshouse/mdmodel": "0.1.0-alpha.13",
20
- "ws": "8.22.0",
21
- "yaml": "2.9.1",
22
- "zod": "4.6.5"
22
+ "devDependencies": {
23
+ "@types/node": "24.13.6",
24
+ "@types/ws": "8.18.2",
25
+ "typescript": "7.0.2",
26
+ "vitest": "5.0.1",
27
+ "ws": "8.22.0"
23
28
  },
24
29
  "packageManager": "npm@11.19.0",
25
30
  "engines": {
@@ -1,134 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- IMAGE='ghcr.io/agentshouse/kit@sha256:__IMAGE_DIGEST__'
5
- NAME='house-kit'
6
- HOUSE='https://agents.house'
7
- WORKSPACE="$HOME/AgentsHouse"
8
- MANUAL=0
9
- WORKSPACE_SELECTED=0
10
- HOUSE_SELECTED=0
11
-
12
- refuse() {
13
- printf 'kit_bootstrap_refused: %s\n' "$1" >&2
14
- exit 1
15
- }
16
-
17
- bind_mount() {
18
- local source=${1//\"/\"\"}
19
- printf 'type=bind,"src=%s",dst=%s' "$source" "$2"
20
- }
21
-
22
- while (($#)); do
23
- case "$1" in
24
- --workspace|--house)
25
- (($# >= 2)) || refuse "$1 requires one value"
26
- if [[ "$1" == --workspace ]]; then
27
- ((WORKSPACE_SELECTED == 0)) || refuse 'supply exactly one workspace root'
28
- WORKSPACE="$2"
29
- WORKSPACE_SELECTED=1
30
- else
31
- ((HOUSE_SELECTED == 0)) || refuse 'supply exactly one House origin'
32
- HOUSE="$2"
33
- HOUSE_SELECTED=1
34
- fi
35
- shift 2
36
- ;;
37
- --manual)
38
- MANUAL=1
39
- shift
40
- ;;
41
- *) refuse "unknown argument $1" ;;
42
- esac
43
- done
44
-
45
- [[ "$IMAGE" =~ ^ghcr\.io/agentshouse/kit@sha256:[0-9a-f]{64}$ ]] || refuse 'this bootstrap has not been published with an immutable image digest'
46
- [[ "$(uname -s)" == Linux ]] || refuse "unsupported host $(uname -s); this bootstrap is for Linux"
47
- case "$(uname -m)" in
48
- x86_64|amd64) ARCH=amd64 ;;
49
- aarch64|arm64) ARCH=arm64 ;;
50
- *) refuse "unsupported Linux architecture $(uname -m)" ;;
51
- esac
52
- [[ "$WORKSPACE" == /* ]] || refuse 'workspace root must be an absolute path'
53
- command -v docker >/dev/null || refuse 'Docker Engine client is unavailable'
54
- DOCKER_PLATFORM=$(docker info --format '{{.OSType}}/{{.Architecture}}' 2>/dev/null) || refuse 'Docker Engine is unavailable'
55
- case "$DOCKER_PLATFORM" in
56
- linux/x86_64|linux/amd64) DOCKER_ARCH=amd64 ;;
57
- linux/aarch64|linux/arm64) DOCKER_ARCH=arm64 ;;
58
- *) refuse "unsupported Docker Engine platform $DOCKER_PLATFORM" ;;
59
- esac
60
- [[ "$DOCKER_ARCH" == "$ARCH" ]] || refuse "host linux/$ARCH does not match Docker Engine linux/$DOCKER_ARCH"
61
-
62
- umask 077
63
- KIT_HOME="$HOME/.house-kit"
64
- for path in "$KIT_HOME" "$WORKSPACE"; do
65
- [[ ! -L "$path" ]] || refuse "$path is a symbolic link"
66
- mkdir -p -m 700 "$path" || refuse "$path could not be created"
67
- [[ -d "$path" ]] || refuse "$path is not a directory"
68
- [[ "$(stat -c %u "$path")" == "$(id -u)" ]] || refuse "$path is not owned by this User"
69
- chmod 700 "$path" || refuse "$path could not be owner-protected"
70
- done
71
- KIT_HOME=$(cd "$KIT_HOME" && pwd -P)
72
- WORKSPACE=$(cd "$WORKSPACE" && pwd -P)
73
- KIT_HOME_MOUNT=$(bind_mount "$KIT_HOME" /kit-home)
74
- WORKSPACE_MOUNT=$(bind_mount "$WORKSPACE" /agents/house)
75
- if [[ -f "$KIT_HOME/credential" || -f "$KIT_HOME/environment" ]] && [[ ! -f "$KIT_HOME/house" ]]; then
76
- refuse 'stored House origin is missing for an enrolled Environment'
77
- fi
78
- if [[ -f "$KIT_HOME/house" ]]; then
79
- IFS= read -r BOUND_HOUSE < "$KIT_HOME/house" || refuse 'stored House origin is unreadable'
80
- if ((HOUSE_SELECTED)) && [[ "$HOUSE" != "$BOUND_HOUSE" ]]; then
81
- refuse 'this Environment is bound to another House origin'
82
- fi
83
- HOUSE="$BOUND_HOUSE"
84
- fi
85
- [[ "$HOUSE" == https://* || "$HOUSE" == http://127.0.0.1:* || "$HOUSE" == http://localhost:* ]] || refuse 'House origin must use HTTPS or local loopback HTTP'
86
- DOCKER_RUN=(docker run --rm --network host --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
87
-
88
- if docker container inspect "$NAME" >/dev/null 2>&1; then
89
- [[ "$(docker inspect --format '{{.Config.Image}}' "$NAME")" == "$IMAGE" ]] || refuse 'an installed Kit uses a different image; update it explicitly'
90
- [[ "$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$NAME")" == unless-stopped ]] || refuse 'the installed Kit has a different lifecycle'
91
- [[ "$(docker inspect --format '{{len .Mounts}}' "$NAME")" == 2 ]] || refuse 'the installed Kit has unexpected mounts'
92
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$KIT_HOME" ]] || refuse 'the installed Kit uses another Kit home'
93
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed Kit home is not a writable bind mount'
94
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$WORKSPACE" ]] || refuse 'the installed Kit uses another workspace root'
95
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed workspace is not a writable bind mount'
96
- [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
97
- [[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
98
- "${DOCKER_RUN[@]}" credential check --house "$HOUSE"
99
- if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
100
- printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
101
- else
102
- docker start "$NAME" >/dev/null
103
- printf 'House Kit restarted for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
104
- fi
105
- exit 0
106
- fi
107
-
108
- docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
109
-
110
- if [[ ! -f "$KIT_HOME/kit.json" ]]; then
111
- printf '{"availability":"intermittent","execution_authority":"allow","migrators":[],"instances":[],"clients":[],"host":null}\n' > "$KIT_HOME/kit.json"
112
- fi
113
-
114
- if [[ -f "$KIT_HOME/credential" ]]; then
115
- "${DOCKER_RUN[@]}" credential check --house "$HOUSE"
116
- else
117
- if ((MANUAL)); then
118
- "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login --house "$HOUSE" --manual
119
- else
120
- command -v xdg-open >/dev/null || refuse 'no host browser opener; rerun with --manual'
121
- "${DOCKER_RUN[@]}" login --house "$HOUSE" --host-browser | while IFS= read -r line; do
122
- if [[ "$line" == 'Open '* ]]; then
123
- printf 'Opening House Login in the host browser.\n'
124
- xdg-open "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
125
- else
126
- printf '%s\n' "$line"
127
- fi
128
- done
129
- fi
130
- fi
131
-
132
- "${DOCKER_RUN[@]}" apply --house "$HOUSE"
133
- docker run -d --name "$NAME" --restart unless-stopped --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --label "agentshouse.house=$HOUSE" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 "$IMAGE" resident --house "$HOUSE" >/dev/null
134
- printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"