@agentshouse/kit 0.1.0-alpha.4 → 0.1.0-alpha.41
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 +41 -1671
- package/bin/connect-linux.sh +501 -53
- package/bin/connect-windows.ps1 +440 -0
- package/bin/kit.js +200 -159
- package/bootstrap.json +28 -0
- package/docs/configuration.md +104 -0
- package/docs/google.md +33 -0
- package/docs/linux.md +77 -0
- package/docs/macos.md +26 -0
- package/docs/telegram.md +29 -0
- package/docs/windows.md +37 -0
- package/package.json +7 -2
- package/src/adapter-mcp.js +92 -12
- package/src/agents.js +63 -8
- package/src/binding.js +52 -0
- package/src/blocks.js +2 -26
- package/src/bridge.js +28 -14
- package/src/bytes.js +19 -8
- package/src/configuration.js +14 -211
- package/src/connector-login.js +3 -84
- package/src/contract.js +44 -12
- package/src/conversation-originals.js +1 -3
- package/src/delivery.js +4 -7
- package/src/directory.js +6 -26
- package/src/draft.js +4 -82
- package/src/git-main.js +138 -0
- package/src/git.js +62 -0
- package/src/google/calendar.js +39 -45
- package/src/google/drive.js +61 -186
- package/src/google/entries.js +349 -0
- package/src/google/gateway.js +3 -6
- package/src/google/gmail.js +33 -23
- package/src/google/oauth.js +106 -66
- package/src/google/source.js +21 -23
- package/src/host.js +6 -2
- package/src/house-main.js +65 -8
- package/src/house.js +446 -127
- package/src/how-we-work.md +25 -0
- package/src/inbound.js +2 -18
- package/src/kit-work-stream.js +120 -0
- package/src/launcher.js +46 -10
- package/src/local-files.js +3 -11
- package/src/login.js +27 -14
- package/src/mcp.js +18 -0
- package/src/mount.js +9 -18
- package/src/original.js +4 -4
- package/src/pacing.js +41 -0
- package/src/plan.js +37 -45
- package/src/repository.js +3 -61
- package/src/session-bin/git +2 -0
- package/src/setup.js +14 -15
- package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
- package/src/skill-set/ask-matt/SKILL.md +91 -0
- package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
- package/src/skill-set/code-review/LICENSE +21 -0
- package/src/skill-set/code-review/SKILL.md +89 -0
- package/src/skill-set/code-review/agents/openai.yaml +3 -0
- package/src/skill-set/codebase-design/DEEPENING.md +37 -0
- package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/src/skill-set/codebase-design/LICENSE +21 -0
- package/src/skill-set/codebase-design/SKILL.md +116 -0
- package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
- package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
- package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
- package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/src/skill-set/domain-modeling/LICENSE +21 -0
- package/src/skill-set/domain-modeling/SKILL.md +76 -0
- package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
- package/src/skill-set/grill-me/LICENSE +21 -0
- package/src/skill-set/grill-me/SKILL.md +8 -0
- package/src/skill-set/grill-me/agents/openai.yaml +4 -0
- package/src/skill-set/grill-with-docs/LICENSE +21 -0
- package/src/skill-set/grill-with-docs/SKILL.md +8 -0
- package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
- package/src/skill-set/grilling/LICENSE +21 -0
- package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
- package/src/skill-set/handoff/LICENSE +21 -0
- package/src/skill-set/handoff/SKILL.md +17 -0
- package/src/skill-set/handoff/agents/openai.yaml +4 -0
- package/src/skill-set/implement/LICENSE +21 -0
- package/src/skill-set/implement/SKILL.md +16 -0
- package/src/skill-set/implement/agents/openai.yaml +4 -0
- package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
- package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
- package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
- package/src/skill-set/prototype/LICENSE +21 -0
- package/src/skill-set/prototype/LOGIC.md +67 -0
- package/src/skill-set/prototype/SKILL.md +28 -0
- package/src/skill-set/prototype/UI.md +112 -0
- package/src/skill-set/prototype/agents/openai.yaml +3 -0
- package/src/skill-set/research/LICENSE +21 -0
- package/src/skill-set/research/SKILL.md +14 -0
- package/src/skill-set/research/agents/openai.yaml +3 -0
- package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
- package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
- package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/LICENSE +21 -0
- package/src/skill-set/tdd/SKILL.md +40 -0
- package/src/skill-set/tdd/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/mocking.md +59 -0
- package/src/skill-set/tdd/tests.md +77 -0
- package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
- package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/src/skill-set/teach/LICENSE +21 -0
- package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
- package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
- package/src/skill-set/teach/SKILL.md +141 -0
- package/src/skill-set/teach/agents/openai.yaml +4 -0
- package/src/skill-set/to-questionnaire/LICENSE +21 -0
- package/src/skill-set/to-questionnaire/SKILL.md +55 -0
- package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
- package/src/skill-set/to-spec/LICENSE +21 -0
- package/src/skill-set/to-spec/SKILL.md +76 -0
- package/src/skill-set/to-spec/agents/openai.yaml +4 -0
- package/src/skill-set/to-tickets/LICENSE +21 -0
- package/src/skill-set/to-tickets/SKILL.md +106 -0
- package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
- package/src/skill-set/wait-what/LICENSE +21 -0
- package/src/skill-set/wait-what/SKILL.md +8 -0
- package/src/skill-set/wait-what/agents/openai.yaml +4 -0
- package/src/skill-set/wayfinder/LICENSE +21 -0
- package/src/skill-set/wayfinder/SKILL.md +129 -0
- package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
- package/src/skill-set/writing-for-agents/LICENSE +21 -0
- package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/src/skill-set/writing-for-agents/SKILL.md +83 -0
- package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
- package/src/skill-set.js +85 -0
- package/src/source.js +93 -54
- package/src/state.js +19 -1
- package/src/sync-library.js +7 -11
- package/src/sync-state.js +12 -3
- package/src/sync.js +142 -43
- package/src/tasks.js +92 -0
- package/src/telegram-view.js +217 -0
- package/src/telegram.js +204 -69
- package/src/turn-frames.js +69 -0
- package/src/worker.js +738 -189
- package/src/working-copy.js +1274 -0
- package/src/workspace.js +299 -88
- package/src/google/declared.js +0 -146
- package/src/google/id.js +0 -1
- package/src/instance/google.js +0 -1
- package/src/starter-set.js +0 -17
- /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
- /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
package/bootstrap.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "0.1.0-alpha.41",
|
|
4
|
+
"hosts": {
|
|
5
|
+
"linux": {
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.41/connect-linux.sh)",
|
|
7
|
+
"asset": {
|
|
8
|
+
"name": "connect-linux.sh",
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.41/connect-linux.sh"
|
|
10
|
+
},
|
|
11
|
+
"workspace": {
|
|
12
|
+
"argument": "--workspace",
|
|
13
|
+
"quoting": "posix"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"windows": {
|
|
17
|
+
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.41/connect-windows.ps1)))",
|
|
18
|
+
"asset": {
|
|
19
|
+
"name": "connect-windows.ps1",
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.41/connect-windows.ps1"
|
|
21
|
+
},
|
|
22
|
+
"workspace": {
|
|
23
|
+
"argument": "--workspace",
|
|
24
|
+
"quoting": "powershell"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`kit.json` in Kit home is the one configuration file. Every section below is
|
|
4
|
+
required; a file that omits one is refused.
|
|
5
|
+
|
|
6
|
+
```json
|
|
7
|
+
{
|
|
8
|
+
"availability": "continuous",
|
|
9
|
+
"execution_authority": "allow",
|
|
10
|
+
"migrators": [],
|
|
11
|
+
"instances": [
|
|
12
|
+
{
|
|
13
|
+
"id": "acme",
|
|
14
|
+
"preset": "custom",
|
|
15
|
+
"services": [
|
|
16
|
+
{
|
|
17
|
+
"service": "notes",
|
|
18
|
+
"source_ref": "src_acme_notes",
|
|
19
|
+
"rooms": ["work-room"],
|
|
20
|
+
"backfill": { "days": 30 },
|
|
21
|
+
"connector": {
|
|
22
|
+
"command": ["/usr/local/bin/acme-export", "--ndjson"],
|
|
23
|
+
"timeout_ms": 120000,
|
|
24
|
+
"environment": { "ACME_REGION": "eu" },
|
|
25
|
+
"secrets": {
|
|
26
|
+
"ACME_TOKEN": { "environment": "ACME_TOKEN_SOURCE" },
|
|
27
|
+
"ACME_KEY": { "file": "/run/secrets/acme-key" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"clients": [
|
|
35
|
+
{
|
|
36
|
+
"kind": "claude-code",
|
|
37
|
+
"settings": "/home/ada/.claude/settings.json"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"host": null,
|
|
41
|
+
"skills": true
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `availability`: `"continuous"` for a computer that stays online,
|
|
46
|
+
`"intermittent"` for a laptop.
|
|
47
|
+
- `execution_authority`: `"allow"` or `"refuse"`, this computer's answer to
|
|
48
|
+
every permission request an agent makes.
|
|
49
|
+
- `instances`: your own sources, each with a `custom` connector (below).
|
|
50
|
+
- `clients`: agent clients outside Kit whose settings get House's session-start
|
|
51
|
+
hook. `claude-code` is the one supported kind; declare `[]` for none.
|
|
52
|
+
- `migrators`: `[]` unless you run a migrator.
|
|
53
|
+
- `host`: `null` unless this computer reports its capacity to House.
|
|
54
|
+
- `skills`: `true` accepts the Skill set, `false` declines it. The connect
|
|
55
|
+
command sets it.
|
|
56
|
+
|
|
57
|
+
Agents and agent CLIs are chosen in House, not here. Send your changes to
|
|
58
|
+
House with:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
kit apply
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Secrets
|
|
65
|
+
|
|
66
|
+
Never write a secret into `kit.json`. Under `secrets`, name an environment
|
|
67
|
+
variable (`{ "environment": "NAME" }`) or a file (`{ "file": "/path" }`); Kit
|
|
68
|
+
passes its value to the connector only.
|
|
69
|
+
|
|
70
|
+
## Custom connectors
|
|
71
|
+
|
|
72
|
+
A connector is your own command. Kit runs it without a shell, with only `PATH`,
|
|
73
|
+
`environment` and the resolved secrets, and never gives it a House credential.
|
|
74
|
+
It must finish within `timeout_ms` (at most 120000).
|
|
75
|
+
|
|
76
|
+
Kit writes one request line to its standard input:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "contract": "2026-08-22", "instance": "acme", "cursor": null, "limit": 100 }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The command prints NDJSON: `item` lines, then at most one `cursor` line.
|
|
83
|
+
`cursor` in the next request is the last one it printed.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
{"type":"item","namespace":"acme/notes","native_object_id":"note-91","native_version":"7","observed_at":"2026-08-20T09:15:00.000Z","body":"the note","author":"ada","url":"https://acme.example/notes/91","attachments":[{"url":"https://acme.example/files/1","name":"plan.pdf"}]}
|
|
87
|
+
{"type":"cursor","cursor":"page-2"}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`namespace`, `native_object_id`, `observed_at` and `body` are required. An
|
|
91
|
+
attachment is a `url`, or a local `file` with its exact `path`, `bytes`,
|
|
92
|
+
`sha256` and optional `media_type`. A malformed line, a non-zero exit or a
|
|
93
|
+
timeout rejects the whole run and nothing is delivered.
|
|
94
|
+
|
|
95
|
+
`backfill` bounds the first capture: `{ "days": 30 }`, `{ "count": 500 }` or
|
|
96
|
+
`{ "all": true }`.
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
kit source capture acme # capture every service of an instance now
|
|
100
|
+
kit source capture acme notes # one service
|
|
101
|
+
kit source status acme # health, cursor, gaps and unsaved files
|
|
102
|
+
kit source retry-files acme # save files a capture could not save
|
|
103
|
+
kit source reset acme notes # start again from the backfill boundary
|
|
104
|
+
```
|
package/docs/google.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Google sources
|
|
2
|
+
|
|
3
|
+
You add Gmail, Calendar and Drive sources in the House App: an account name you
|
|
4
|
+
choose, the service, the Rooms it goes to, and how much history the first
|
|
5
|
+
capture takes. Nothing is configured on this computer.
|
|
6
|
+
|
|
7
|
+
Each service captures, read-only:
|
|
8
|
+
|
|
9
|
+
- Gmail: every message except Spam and Trash.
|
|
10
|
+
- Calendar: the account's primary calendar.
|
|
11
|
+
- Drive: the files the account owns.
|
|
12
|
+
|
|
13
|
+
## Signing in
|
|
14
|
+
|
|
15
|
+
Kit ships no Google OAuth client. Each account signs in with your own:
|
|
16
|
+
|
|
17
|
+
1. In your Google Cloud project, create an OAuth client of type **Desktop app**
|
|
18
|
+
and enable the API of each service you use.
|
|
19
|
+
2. When House asks, enter the client id and secret on its page.
|
|
20
|
+
3. Approve Google's consent. Google then sends the browser to a
|
|
21
|
+
`http://127.0.0.1:<port>/` address that does not load; copy that whole
|
|
22
|
+
address and paste it into the House page.
|
|
23
|
+
|
|
24
|
+
Kit then captures each signed-in source every 15 minutes.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
kit source capture work # capture every source of the account now
|
|
28
|
+
kit source capture work gmail # one service
|
|
29
|
+
kit source status work
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If `kit source status` reports `api_disabled`, enable the API it names in your
|
|
33
|
+
Google Cloud project.
|
package/docs/linux.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Linux
|
|
2
|
+
|
|
3
|
+
Run the Linux command from the [README](../README.md#connect-a-computer) in a
|
|
4
|
+
terminal. It needs `bash`, `curl` and Docker Engine.
|
|
5
|
+
|
|
6
|
+
## Docker Engine
|
|
7
|
+
|
|
8
|
+
- A Docker Engine that already answers is used as it is, on any distribution.
|
|
9
|
+
- On Ubuntu 22.04, 24.04 or 26.04 LTS, anything missing is installed from
|
|
10
|
+
Docker's own apt repository through `sudo`, which asks for your password.
|
|
11
|
+
The command starts the `docker` service and adds you to the `docker` group.
|
|
12
|
+
- Ubuntu packages that conflict with Docker's (`docker.io`, `docker-compose`,
|
|
13
|
+
`docker-compose-v2`, `docker-doc`, `docker-buildx`, `podman-docker`,
|
|
14
|
+
`containerd`, `runc`) are not removed for you: remove them and run again.
|
|
15
|
+
- On any other distribution, install Docker Engine first.
|
|
16
|
+
|
|
17
|
+
A new `docker` group membership applies from your next sign-in. If a later run
|
|
18
|
+
says so, sign out and back in, then run the command again.
|
|
19
|
+
|
|
20
|
+
## What it creates
|
|
21
|
+
|
|
22
|
+
- `~/.house-kit`: Kit home, with your credential and `kit.json`.
|
|
23
|
+
- `~/AgentsHouse`: your workspace, mounted in the container as
|
|
24
|
+
`/agents/house`. Pass `--workspace /absolute/path` to use another directory.
|
|
25
|
+
|
|
26
|
+
Both are readable only by you. The credential is never printed.
|
|
27
|
+
|
|
28
|
+
## Updating
|
|
29
|
+
|
|
30
|
+
Run a newer release's command. While an Agent session is still running, the
|
|
31
|
+
update names it and leaves Kit as it is; run the command again once it ends.
|
|
32
|
+
Your credential, Kit home and workspace are kept.
|
|
33
|
+
|
|
34
|
+
## Pushing a working copy
|
|
35
|
+
|
|
36
|
+
From a directory under your workspace, such as a Room copy under
|
|
37
|
+
`working-copies/rooms/`, run:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
bash <(curl -fsSL …/connect-linux.sh) house git push --owner
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## A House on this computer
|
|
44
|
+
|
|
45
|
+
`--house http://127.0.0.1:<port>` or `--house http://localhost:<port>` runs Kit
|
|
46
|
+
on the host network so it can reach a House served on this computer.
|
|
47
|
+
|
|
48
|
+
## An Always-on server that hosts containers
|
|
49
|
+
|
|
50
|
+
An Always-on Ubuntu server that will run persistent container Environments
|
|
51
|
+
connects natively instead of in a container. Run the command with `--linux`:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
bash <(curl -fsSL …/connect-linux.sh) --linux
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It needs Ubuntu 26.04 LTS on `amd64`; any other host is refused. Every other
|
|
58
|
+
computer takes the ordinary command. It sets up:
|
|
59
|
+
|
|
60
|
+
- Docker Engine, as above, and `git` and `tmux` from Ubuntu's apt repository.
|
|
61
|
+
- `/opt/house-kit`: House Kit and the Node.js it runs on, copied from the
|
|
62
|
+
release's image and owned by root.
|
|
63
|
+
- `~/.house-kit`: Kit home, readable only by you.
|
|
64
|
+
- `/agents/house`: your Agents' workspace, readable only by you.
|
|
65
|
+
- `house-kit.service`: the systemd service that runs Kit as you and starts it
|
|
66
|
+
with the server.
|
|
67
|
+
|
|
68
|
+
The server connects as a continuous Environment. Put `--linux` before `kit` or
|
|
69
|
+
`house` to run a Kit command, as in
|
|
70
|
+
`bash <(curl -fsSL …/connect-linux.sh) --linux kit apply`, and run a newer
|
|
71
|
+
release's command with `--linux` to update it, as above.
|
|
72
|
+
|
|
73
|
+
The same run updates the container Environments the server hosts to that
|
|
74
|
+
release. A running one waits, and is named, until its own Agent sessions end;
|
|
75
|
+
run the command again then. A stopped one is updated and stays stopped. Each
|
|
76
|
+
keeps its files, sign-ins and CPU, memory and disk limits; only what was
|
|
77
|
+
installed outside its workspace and Kit home is not kept.
|
package/docs/macos.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
|
|
3
|
+
Run the Linux command from the [README](../README.md#connect-a-computer) in
|
|
4
|
+
Terminal. It works on Apple silicon and Intel Macs, including from a terminal
|
|
5
|
+
running under Rosetta.
|
|
6
|
+
|
|
7
|
+
## Docker Desktop
|
|
8
|
+
|
|
9
|
+
Kit on a Mac needs Docker Desktop.
|
|
10
|
+
|
|
11
|
+
- A Docker Desktop that already answers is used as it is.
|
|
12
|
+
- Other engines, such as Colima or OrbStack, are refused.
|
|
13
|
+
- Without `/Applications/Docker.app`, the command downloads Docker Desktop for
|
|
14
|
+
your chip and installs it through `sudo`, which asks for your password.
|
|
15
|
+
- The command opens Docker Desktop and waits up to five minutes while you
|
|
16
|
+
accept its terms or finish what its window asks. If it is still not ready,
|
|
17
|
+
run the command again.
|
|
18
|
+
|
|
19
|
+
## Notes
|
|
20
|
+
|
|
21
|
+
- A `--workspace` directory must be one Docker Desktop shares, such as one
|
|
22
|
+
under `/Users`.
|
|
23
|
+
- A House on this computer (`--house http://127.0.0.1:<port>`) needs host
|
|
24
|
+
networking enabled in Docker Desktop's settings.
|
|
25
|
+
|
|
26
|
+
Everything else works as on [Linux](linux.md).
|
package/docs/telegram.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Telegram bot
|
|
2
|
+
|
|
3
|
+
Kit can connect your own Telegram bot so your House conversations appear there.
|
|
4
|
+
The bot token stays on this computer; House never receives it.
|
|
5
|
+
|
|
6
|
+
1. Create a bot with BotFather and turn on its Threaded Mode.
|
|
7
|
+
2. Send the bot one ordinary private message outside any topic, so Kit can
|
|
8
|
+
tell the bot is yours.
|
|
9
|
+
3. Connect it, entering the token at the hidden prompt:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
kit telegram connect
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
4. Serve it; this keeps running until you stop it:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
kit telegram serve
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To release the bot:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
kit telegram disconnect
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run each command through the connect command as the
|
|
28
|
+
[README](../README.md#run-a-kit-command) shows. Only one computer can serve a
|
|
29
|
+
given bot.
|
package/docs/windows.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Windows
|
|
2
|
+
|
|
3
|
+
Run the Windows command from the [README](../README.md#connect-a-computer) in
|
|
4
|
+
the 64-bit Windows PowerShell. It needs Windows 11 23H2 (build 22631) or later
|
|
5
|
+
on `amd64`. Windows Server, Windows 10 and Windows on ARM are refused.
|
|
6
|
+
|
|
7
|
+
## WSL 2 and Docker Desktop
|
|
8
|
+
|
|
9
|
+
- A Docker Desktop that already runs Linux containers on WSL 2 is used as it is.
|
|
10
|
+
- Without WSL, the command runs `wsl --install --no-distribution`. Approve the
|
|
11
|
+
administrator prompt, restart Windows, and run the command again. An old WSL
|
|
12
|
+
is updated the same way.
|
|
13
|
+
- Without Docker Desktop, the command downloads it and installs it for your
|
|
14
|
+
account only, without administrator rights.
|
|
15
|
+
- The command starts Docker Desktop and waits up to five minutes while you
|
|
16
|
+
accept its terms or finish what its window asks. If it is still not ready,
|
|
17
|
+
run the command again.
|
|
18
|
+
- Windows containers, the Hyper-V engine and other Docker engines are refused.
|
|
19
|
+
|
|
20
|
+
## What it creates
|
|
21
|
+
|
|
22
|
+
- The Docker volume `house-kit-home`: Kit home, with your credential and
|
|
23
|
+
`kit.json`, mounted in the container as `/kit-home`. It lives on Docker
|
|
24
|
+
Desktop's own WSL 2 disk, not on your Windows drive. Docker Desktop's
|
|
25
|
+
Volumes view shows its files; double-click `kit.json` there to edit it.
|
|
26
|
+
- `%USERPROFILE%\AgentsHouse`: your workspace. Pass
|
|
27
|
+
`--workspace 'D:\Agents'` to use another directory. It is restricted to your
|
|
28
|
+
Windows account, and a path containing a comma is refused.
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
|
|
32
|
+
- Append `kit` and a command to run it, as on [Linux](linux.md). Each run
|
|
33
|
+
leaves its exit status in `$LASTEXITCODE`.
|
|
34
|
+
- A command whose output is redirected also writes its prompts and errors there.
|
|
35
|
+
- A House on this computer needs host networking enabled in Docker Desktop's
|
|
36
|
+
settings.
|
|
37
|
+
- This bootstrap has not yet been run on a real Windows computer.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.41",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,11 +9,16 @@
|
|
|
9
9
|
"private": false,
|
|
10
10
|
"type": "module",
|
|
11
11
|
"bin": {
|
|
12
|
+
"house": "src/session-bin/house",
|
|
12
13
|
"kit": "bin/kit.js"
|
|
13
14
|
},
|
|
15
|
+
"exports": {
|
|
16
|
+
"./bootstrap": "./bootstrap.json"
|
|
17
|
+
},
|
|
14
18
|
"dependencies": {
|
|
15
19
|
"@agentclientprotocol/sdk": "1.5.0",
|
|
16
|
-
"@agentshouse/mdmodel": "0.1.0-alpha.
|
|
20
|
+
"@agentshouse/mdmodel": "0.1.0-alpha.15",
|
|
21
|
+
"ws": "8.22.0",
|
|
17
22
|
"yaml": "2.9.1",
|
|
18
23
|
"zod": "4.6.5"
|
|
19
24
|
},
|
package/src/adapter-mcp.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
COMMAND_ADAPTER_INITIALIZE_TIMEOUT_MS,
|
|
4
|
+
HOW_WE_WORK_PATH,
|
|
5
|
+
MCP_PROTOCOL_VERSION,
|
|
6
|
+
SHELL_RENDER,
|
|
7
|
+
} from "./contract.js";
|
|
8
|
+
import { answerText, bridgeCall, editRefusal } from "./mcp.js";
|
|
3
9
|
import { KitRefusal } from "./refusal.js";
|
|
4
10
|
|
|
11
|
+
const HOW_WE_WORK_TEXT = readFileSync(new URL("how-we-work.md", import.meta.url), "utf8");
|
|
12
|
+
|
|
5
13
|
function readableDiscovery(discovery) {
|
|
6
14
|
const identity = discovery?._meta?.["io.modelcontextprotocol/serverInfo"];
|
|
7
15
|
return Array.isArray(discovery?.supportedVersions) &&
|
|
@@ -30,15 +38,87 @@ export async function commandHouseInstructions(bridge) {
|
|
|
30
38
|
return frame.result.instructions;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const CUT_NOTICE = "shell: output_cut ";
|
|
42
|
+
const CONTINUATION = "; continue with: ";
|
|
43
|
+
const BYTE_CONTINUATION = /\| tail -c \+[1-9][0-9]*$/u;
|
|
44
|
+
|
|
45
|
+
function shellAnswer(text) {
|
|
46
|
+
const lines = text.split("\n");
|
|
47
|
+
if (!lines[0].startsWith(`protocol: render=${SHELL_RENDER} `)) return null;
|
|
48
|
+
const exit = Number(lines[2].slice("exit: ".length));
|
|
49
|
+
const truncation = lines[3].slice("truncation: ".length);
|
|
50
|
+
let at = lines[5]?.startsWith("authority: ") ? 6 : 5;
|
|
51
|
+
if (exit === 0 && truncation === "none") {
|
|
52
|
+
return { exit, truncation, stderr: [], stdout: lines.slice(at).join("\n"), continuation: null };
|
|
53
|
+
}
|
|
54
|
+
const stderr = [];
|
|
55
|
+
let continuation = null;
|
|
56
|
+
while (continuation === null && lines[at]?.startsWith("stderr: ")) {
|
|
57
|
+
const line = lines[at++].slice("stderr: ".length);
|
|
58
|
+
stderr.push(line);
|
|
59
|
+
const separator = line.indexOf(CONTINUATION);
|
|
60
|
+
if (line.startsWith(CUT_NOTICE) && separator !== -1) {
|
|
61
|
+
continuation = line.slice(separator + CONTINUATION.length);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { exit, truncation, stderr, stdout: lines.slice(at).join("\n"), continuation };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function howWeWorkReply(bridge, command, first) {
|
|
68
|
+
const frame = await bridgeCall(bridge, {
|
|
69
|
+
method: "tools/call",
|
|
70
|
+
params: { name: "shell", arguments: { command } },
|
|
71
|
+
});
|
|
72
|
+
if (frame.error !== undefined) {
|
|
73
|
+
throw new KitRefusal("kit_how_we_work_unreadable", frame.error.message ?? null);
|
|
74
|
+
}
|
|
75
|
+
const text = answerText(frame.result);
|
|
76
|
+
if (frame.result?.isError === true) {
|
|
77
|
+
if (first && text.split(":")[0] === "room_not_found") return null;
|
|
78
|
+
throw new KitRefusal("kit_how_we_work_unreadable", text);
|
|
79
|
+
}
|
|
80
|
+
const reply = shellAnswer(text);
|
|
81
|
+
if (reply === null) throw new KitRefusal("kit_how_we_work_unreadable", text);
|
|
82
|
+
if (reply.exit !== 0) {
|
|
83
|
+
if (first && reply.stderr.length === 1 && reply.stderr[0].split(" ")[1] === "path_not_found") return null;
|
|
84
|
+
throw new KitRefusal("kit_how_we_work_unreadable", reply.stderr.join("\n"));
|
|
85
|
+
}
|
|
86
|
+
if (reply.truncation !== "none" && reply.continuation === null) {
|
|
87
|
+
throw new KitRefusal("kit_how_we_work_unreadable", reply.stderr.join("\n"));
|
|
88
|
+
}
|
|
89
|
+
return reply;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function commandHouseHowWeWork(bridge) {
|
|
93
|
+
let document = "";
|
|
94
|
+
let command = `cat ${HOW_WE_WORK_PATH}`;
|
|
95
|
+
let first = true;
|
|
96
|
+
while (command !== null) {
|
|
97
|
+
const reply = await howWeWorkReply(bridge, command, first);
|
|
98
|
+
if (reply === null) return "";
|
|
99
|
+
document += reply.stdout;
|
|
100
|
+
command = reply.continuation;
|
|
101
|
+
if (command !== null && !BYTE_CONTINUATION.test(command)) document += "\n";
|
|
102
|
+
first = false;
|
|
103
|
+
}
|
|
104
|
+
return document;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function commandHouseCreateHowWeWork(bridge) {
|
|
108
|
+
const frame = await bridgeCall(bridge, {
|
|
109
|
+
method: "tools/call",
|
|
110
|
+
params: {
|
|
111
|
+
name: "edit",
|
|
112
|
+
arguments: { changes: [{ op: "create", path: HOW_WE_WORK_PATH, content: HOW_WE_WORK_TEXT }] },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
if (frame.error !== undefined) {
|
|
116
|
+
throw new KitRefusal("kit_how_we_work_unwritable", frame.error.message ?? null);
|
|
43
117
|
}
|
|
118
|
+
if (frame.result?.isError !== true) return true;
|
|
119
|
+
const text = answerText(frame.result);
|
|
120
|
+
const { code, conflicts } = editRefusal(text);
|
|
121
|
+
if (code === "room_not_found" || code === "operation_denied") return false;
|
|
122
|
+
if (code === "edit_conflict" && conflicts?.some((conflict) => conflict.code === "path_exists")) return true;
|
|
123
|
+
throw new KitRefusal("kit_how_we_work_unwritable", text);
|
|
44
124
|
}
|
package/src/agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { readFile, readdir, rm } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./adapter.js";
|
|
12
12
|
import { inheritedEnvironment } from "./command.js";
|
|
13
13
|
import {
|
|
14
|
+
ADAPTER_KINDS,
|
|
14
15
|
AGENT_HOME,
|
|
15
16
|
AGENT_INSTALL_TIMEOUT_MS,
|
|
16
17
|
AGENT_RELEASES_HOME,
|
|
@@ -261,6 +262,60 @@ export async function updateDeclaredAgents(options, configuration, dependencies
|
|
|
261
262
|
return updated;
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
export async function removeUndeclaredAgents(directory, wanted, keep = false) {
|
|
266
|
+
const home = join(directory, AGENT_HOME);
|
|
267
|
+
let entries;
|
|
268
|
+
try {
|
|
269
|
+
entries = await readdir(home, { withFileTypes: true });
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (error.code === "ENOENT") return [];
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
const retained = [];
|
|
275
|
+
for (const entry of entries) {
|
|
276
|
+
if (!wanted.includes(entry.name)) {
|
|
277
|
+
if (keep && ADAPTER_KINDS.includes(entry.name)) retained.push(entry.name);
|
|
278
|
+
else await rm(join(home, entry.name), { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return retained;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function reconcileAgentKinds(
|
|
285
|
+
options,
|
|
286
|
+
session,
|
|
287
|
+
configuration,
|
|
288
|
+
dependencies = {},
|
|
289
|
+
signal = session.signal,
|
|
290
|
+
state = {},
|
|
291
|
+
) {
|
|
292
|
+
const selected = { ...configuration, agents: await desiredAgents(session, signal) };
|
|
293
|
+
state.onDesired?.(selected.agents);
|
|
294
|
+
const newlyWanted = selected.agents.filter((kind) => !state.lastDesired?.includes(kind));
|
|
295
|
+
const retained = await removeUndeclaredAgents(options.directory, selected.agents, state.busy);
|
|
296
|
+
const pointerKinds = state.busy
|
|
297
|
+
? (
|
|
298
|
+
await Promise.all(
|
|
299
|
+
newlyWanted.map(async (kind) =>
|
|
300
|
+
(await readAgentState(options.directory, kind)).release === null ? kind : null,
|
|
301
|
+
),
|
|
302
|
+
)
|
|
303
|
+
).filter((kind) => kind !== null)
|
|
304
|
+
: newlyWanted;
|
|
305
|
+
const updated = await updateDeclaredAgents(
|
|
306
|
+
{ directory: options.directory, at: dependencies.now?.(), forced: state.forced },
|
|
307
|
+
{ ...selected, agents: state.pointer ? pointerKinds : selected.agents },
|
|
308
|
+
dependencies,
|
|
309
|
+
);
|
|
310
|
+
const readiness = await agentReadiness(
|
|
311
|
+
{ directory: options.directory },
|
|
312
|
+
{ ...selected, agents: [...selected.agents, ...retained] },
|
|
313
|
+
dependencies,
|
|
314
|
+
);
|
|
315
|
+
await reportAgentCapabilities(session, readiness.agents, signal);
|
|
316
|
+
return { selected, updated, reported: readiness.agents, refused: readiness.refused, retained };
|
|
317
|
+
}
|
|
318
|
+
|
|
264
319
|
export async function agentSignedIn(directory, kind, dependencies = {}) {
|
|
265
320
|
const contract = compiledContract(kind);
|
|
266
321
|
const state = await readAgentState(directory, kind);
|
|
@@ -341,15 +396,15 @@ export async function updateAgents(options, dependencies = {}) {
|
|
|
341
396
|
return maintainControl(
|
|
342
397
|
session,
|
|
343
398
|
async (signal) => {
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
399
|
+
const result = await reconcileAgentKinds(
|
|
400
|
+
options,
|
|
401
|
+
session,
|
|
402
|
+
configuration,
|
|
348
403
|
dependencies,
|
|
404
|
+
signal,
|
|
405
|
+
{ forced: true },
|
|
349
406
|
);
|
|
350
|
-
|
|
351
|
-
await reportAgentCapabilities(session, agents, signal);
|
|
352
|
-
return { updated, reported: agents };
|
|
407
|
+
return { updated: result.updated, reported: result.reported };
|
|
353
408
|
},
|
|
354
409
|
options.signal,
|
|
355
410
|
);
|
package/src/binding.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { finalPlan, planSnapshot } from "./plan.js";
|
|
3
4
|
import { KitRefusal } from "./refusal.js";
|
|
4
5
|
import { readNote, writeNote } from "./state.js";
|
|
5
6
|
import { reportedNote, retainedReport } from "./terminal.js";
|
|
@@ -15,6 +16,18 @@ function bindingPath(directory, tmux) {
|
|
|
15
16
|
return join(bindingDirectory(directory), `${tmux}.json`);
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function stoppedNote(stopped) {
|
|
20
|
+
if (stopped === null || stopped === undefined) return null;
|
|
21
|
+
return {
|
|
22
|
+
turn_completed: stopped.turnCompleted,
|
|
23
|
+
capacity_exhausted: stopped.capacityExhausted,
|
|
24
|
+
provider_refused: stopped.providerRefused,
|
|
25
|
+
continuable: stopped.continuable,
|
|
26
|
+
failure: stopped.failure,
|
|
27
|
+
text_accepted: stopped.textAccepted,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
export async function writeBinding(directory, binding) {
|
|
19
32
|
const reported = binding.reported ?? null;
|
|
20
33
|
await writeNote(bindingPath(directory, binding.tmux), {
|
|
@@ -29,7 +42,9 @@ export async function writeBinding(directory, binding) {
|
|
|
29
42
|
sealed: binding.sealed === null || binding.sealed === undefined
|
|
30
43
|
? null
|
|
31
44
|
: { operation: binding.sealed.operation, blocks: binding.sealed.blocks },
|
|
45
|
+
stopped: stoppedNote(binding.stopped),
|
|
32
46
|
reported: reported === null ? null : reportedNote(reported),
|
|
47
|
+
plan: binding.plan ?? null,
|
|
33
48
|
});
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -45,6 +60,37 @@ function retainedSeal(stored) {
|
|
|
45
60
|
return { operation: stored.operation, blocks: stored.blocks };
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
function retainedPlan(stored) {
|
|
64
|
+
if (stored === null || stored === undefined) return null;
|
|
65
|
+
const plan = finalPlan(planSnapshot(stored));
|
|
66
|
+
return plan === null || JSON.stringify(plan) !== JSON.stringify(stored) ? undefined : plan;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function retainedFailure(stored) {
|
|
70
|
+
if (stored === null) return null;
|
|
71
|
+
if (typeof stored !== "object" || Array.isArray(stored)) return undefined;
|
|
72
|
+
if (typeof stored.code !== "string" || stored.code.length === 0) return undefined;
|
|
73
|
+
if (stored.detail !== null && typeof stored.detail !== "string") return undefined;
|
|
74
|
+
return { code: stored.code, detail: stored.detail };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function retainedStop(stored) {
|
|
78
|
+
if (stored === null || stored === undefined) return null;
|
|
79
|
+
if (typeof stored !== "object" || Array.isArray(stored)) return undefined;
|
|
80
|
+
const flags = ["turn_completed", "capacity_exhausted", "provider_refused", "continuable", "text_accepted"];
|
|
81
|
+
if (flags.some((flag) => typeof stored[flag] !== "boolean")) return undefined;
|
|
82
|
+
const failure = retainedFailure(stored.failure);
|
|
83
|
+
if (failure === undefined) return undefined;
|
|
84
|
+
return {
|
|
85
|
+
turnCompleted: stored.turn_completed,
|
|
86
|
+
capacityExhausted: stored.capacity_exhausted,
|
|
87
|
+
providerRefused: stored.provider_refused,
|
|
88
|
+
continuable: stored.continuable,
|
|
89
|
+
failure,
|
|
90
|
+
textAccepted: stored.text_accepted,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
48
94
|
function completeBinding(directory, stored) {
|
|
49
95
|
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) return null;
|
|
50
96
|
const named = [stored.run, stored.session, stored.tmux, stored.socket, stored.nonce];
|
|
@@ -54,8 +100,12 @@ function completeBinding(directory, stored) {
|
|
|
54
100
|
if (stored.owner_stop !== null && typeof stored.owner_stop !== "string") return null;
|
|
55
101
|
const sealed = retainedSeal(stored.sealed);
|
|
56
102
|
if (sealed === undefined) return null;
|
|
103
|
+
const stopped = retainedStop(stored.stopped);
|
|
104
|
+
if (stopped === undefined) return null;
|
|
57
105
|
const reported = retainedReport(stored.reported);
|
|
58
106
|
if (reported === undefined) return null;
|
|
107
|
+
const plan = retainedPlan(stored.plan);
|
|
108
|
+
if (plan === undefined) return null;
|
|
59
109
|
return {
|
|
60
110
|
run: stored.run,
|
|
61
111
|
session: stored.session,
|
|
@@ -65,7 +115,9 @@ function completeBinding(directory, stored) {
|
|
|
65
115
|
conversation: stored.conversation === true,
|
|
66
116
|
ownerStop: stored.owner_stop,
|
|
67
117
|
sealed,
|
|
118
|
+
stopped,
|
|
68
119
|
reported,
|
|
120
|
+
plan,
|
|
69
121
|
};
|
|
70
122
|
}
|
|
71
123
|
|