zooid 0.9.1 → 0.10.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.
package/README.md
CHANGED
|
@@ -6,10 +6,12 @@ Zooid is an open-source, self-hostable chat app for collaborating with AI agents
|
|
|
6
6
|
|
|
7
7
|
**Full docs: [zooid.dev/docs](https://zooid.dev/docs)** · **Join the community server: [community.zoon.eco](https://community.zoon.eco)**
|
|
8
8
|
|
|
9
|
-
- **Protocol-first.** Matrix for transport (E2E encryption, federation), ACP for the agent contract. Pre-built images for Claude Code, opencode, and Codex; any other ACP-compatible harness (
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
9
|
+
- **Protocol-first.** Matrix for transport (E2E encryption, federation), ACP for the agent contract. Pre-built images for Claude Code, opencode, and Codex; any other ACP-compatible harness (GitHub Copilot CLI, Cursor CLI, Gemini CLI, pi, or your own) connects too.
|
|
10
|
+
- **Run it anywhere.** Each daemon is a *workstation* with its own identity, running as many agents as you like — put it on your laptop, a box, a pod, or a cluster; it doesn't have to live next to the homeserver. Several workstations can share one homeserver.
|
|
11
|
+
- **Containerized runtime.** Docker or Podman. Each agent runs in its own long-lived container with mounts, env, and capabilities declared in `zooid.yaml`.
|
|
12
12
|
- **Multi-agent collaboration.** Agents are standard Matrix users, so an architect bot can `@`-mention a reviewer bot to delegate.
|
|
13
|
+
- **Workforce as code.** Declare agents declaratively; review team-structure changes in pull requests, not a web UI.
|
|
14
|
+
- **Push or pull.** Pick how the daemon and server connect. *Push* suits a cloud deployment running many agents at high throughput — the server pushes messages straight to the daemon, which advertises a URL. *Pull* has the daemon fetch them itself, nothing to expose — so it runs behind a firewall or on your laptop and catches up on whatever it missed while off.
|
|
13
15
|
|
|
14
16
|
## Quickstart
|
|
15
17
|
|
|
@@ -34,22 +36,46 @@ Boot the local stack:
|
|
|
34
36
|
zooid dev
|
|
35
37
|
```
|
|
36
38
|
|
|
39
|
+
> **Prerequisite:** `zooid dev` runs Tuwunel inside a container, so you need a container engine installed and running first — either **Docker** (with the daemon started) or **Podman** aliased as `docker`.
|
|
40
|
+
|
|
37
41
|
`zooid dev` starts a Tuwunel Matrix homeserver in a container, generates the Application Service registration, registers an `admin:admin` user, runs the daemon, and serves the [Zooid web client](https://github.com/zooid-ai/clients).
|
|
38
42
|
|
|
39
43
|
Open `http://localhost:5173`, log in as `admin` / `admin`, join `#welcome`, and `@`-mention your agent.
|
|
40
44
|
|
|
41
45
|
For deployment recipes, the `zooid.yaml` reference, and a deeper tour of how the runtime works, see **[zooid.dev/docs](https://zooid.dev/docs)**.
|
|
42
46
|
|
|
47
|
+
## Workstations & transport modes
|
|
48
|
+
|
|
49
|
+
A Zooid daemon is a **workstation** — set a top-level `workstation:` in `zooid.yaml` and its agents get an exclusive `@{workstation}.{agent}:server` namespace. Several workstations (a box, your laptop, a teammate's) can connect to a single homeserver, each owning its own agents.
|
|
50
|
+
|
|
51
|
+
How a workstation connects is set per-transport with `mode:` — the two choices (push/pull above), named by the value you put in the YAML:
|
|
52
|
+
|
|
53
|
+
- **`appservice` (push)** — the homeserver delivers events to the daemon, so the daemon advertises an address the homeserver can reach: `port` (default `9099`) when they share a host or network, or an `advertise_url` when the daemon lives on a separate machine, pod, or cluster. Best for cloud deployments running many agents; this is what `zooid dev` uses.
|
|
54
|
+
- **`client` (pull)** — the daemon fetches events itself, one outbound sync per agent, with nothing to expose. Runs behind a firewall or on your laptop against a remote homeserver, and catches up on whatever it missed after sleep or restart.
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
workstation: my-laptop
|
|
58
|
+
runtime: local
|
|
59
|
+
transports:
|
|
60
|
+
matrix:
|
|
61
|
+
homeserver: https://your-homeserver.example
|
|
62
|
+
mode: client
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Acting (sending, joining) is identical in both modes — only how the daemon ingests events differs.
|
|
66
|
+
|
|
43
67
|
## The stack
|
|
44
68
|
|
|
45
69
|
Every layer is open and replaceable.
|
|
46
70
|
|
|
47
|
-
| Layer
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
71
|
+
| Layer | Project | License | Backing |
|
|
72
|
+
| -------------- | ---------------- | ------------- | ------------------------------------------------- |
|
|
73
|
+
| Agent protocol | **ACP** | Open standard | Backed by Zed and JetBrains |
|
|
74
|
+
| Bridge | **Zooid daemon** | MIT | This project — the ACP–Matrix bridge |
|
|
75
|
+
| Server | **Matrix** | Open standard | Any homeserver — adopted by Germany, France, NATO |
|
|
76
|
+
| Client | **Zooid web** | MIT | Built on `matrix-js-sdk` (Apache-2.0) |
|
|
77
|
+
|
|
78
|
+
`zooid dev` runs [Tuwunel](https://github.com/matrix-construct/tuwunel) (Apache-2.0, in production at the Swiss government) as the local homeserver, but Zooid points at any Matrix homeserver you already run.
|
|
53
79
|
|
|
54
80
|
## Agent images
|
|
55
81
|
|
package/dist/bin.js
CHANGED
|
@@ -25632,7 +25632,9 @@ var MatrixClient = class {
|
|
|
25632
25632
|
];
|
|
25633
25633
|
}
|
|
25634
25634
|
if (opts.userPowerLevels && Object.keys(opts.userPowerLevels).length > 0) {
|
|
25635
|
-
|
|
25635
|
+
const users = { ...opts.userPowerLevels };
|
|
25636
|
+
if (users[opts.senderUserId] === void 0) users[opts.senderUserId] = 100;
|
|
25637
|
+
body.power_level_content_override = { users };
|
|
25636
25638
|
}
|
|
25637
25639
|
const r = await this.fetch(
|
|
25638
25640
|
`${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.senderUserId)}`,
|
|
@@ -26088,11 +26090,16 @@ function route(event, agents, threadStates) {
|
|
|
26088
26090
|
continue;
|
|
26089
26091
|
}
|
|
26090
26092
|
if (threadState) {
|
|
26091
|
-
const
|
|
26092
|
-
if (
|
|
26093
|
-
if (
|
|
26094
|
-
} else
|
|
26095
|
-
|
|
26093
|
+
const senderAgent = agents.find((x) => x.userId === event.sender);
|
|
26094
|
+
if (senderAgent) {
|
|
26095
|
+
if (threadState.callers[senderAgent.name] === a.name) matches.push(a);
|
|
26096
|
+
} else {
|
|
26097
|
+
const lastPoster = threadState.participants.at(-1);
|
|
26098
|
+
if (lastPoster) {
|
|
26099
|
+
if (lastPoster === a.name) matches.push(a);
|
|
26100
|
+
} else if (threadState.rootMentions.includes(a.name)) {
|
|
26101
|
+
matches.push(a);
|
|
26102
|
+
}
|
|
26096
26103
|
}
|
|
26097
26104
|
}
|
|
26098
26105
|
}
|
|
@@ -26220,14 +26227,20 @@ var BotPool = class {
|
|
|
26220
26227
|
this.agents,
|
|
26221
26228
|
room
|
|
26222
26229
|
);
|
|
26223
|
-
|
|
26224
|
-
|
|
26225
|
-
|
|
26226
|
-
|
|
26227
|
-
|
|
26228
|
-
|
|
26229
|
-
|
|
26230
|
-
|
|
26230
|
+
try {
|
|
26231
|
+
resolved = await this.client.createRoom({
|
|
26232
|
+
roomAliasName: aliasLocalpart,
|
|
26233
|
+
invite: opts.adminUserId ? [opts.adminUserId] : [],
|
|
26234
|
+
senderUserId: sender,
|
|
26235
|
+
name: aliasLocalpart,
|
|
26236
|
+
...opts.spaceRoomId ? { restrictedToSpaceId: opts.spaceRoomId } : {},
|
|
26237
|
+
...userPowerLevels ? { userPowerLevels } : {}
|
|
26238
|
+
});
|
|
26239
|
+
} catch (err) {
|
|
26240
|
+
const raced = await this.client.resolveAlias(room);
|
|
26241
|
+
if (!raced) throw err;
|
|
26242
|
+
resolved = raced;
|
|
26243
|
+
}
|
|
26231
26244
|
}
|
|
26232
26245
|
aliasToId.set(room, resolved);
|
|
26233
26246
|
}
|
|
@@ -26942,14 +26955,15 @@ function createMatrixTransport(opts) {
|
|
|
26942
26955
|
if (matches.length > 0 && promotedRoot) {
|
|
26943
26956
|
let st = threadStates.get(promotedRoot);
|
|
26944
26957
|
if (!st) {
|
|
26945
|
-
st = { participants: [], rootMentions: [] };
|
|
26958
|
+
st = { participants: [], rootMentions: [], callers: {} };
|
|
26946
26959
|
threadStates.set(promotedRoot, st);
|
|
26947
26960
|
}
|
|
26948
26961
|
const msgMentions = new Set(extractMentions(evt));
|
|
26962
|
+
const senderAgent = bindings.find((b) => b.userId === evt.sender);
|
|
26949
26963
|
for (const a of bindings) {
|
|
26950
|
-
if (msgMentions.has(a.userId)
|
|
26951
|
-
|
|
26952
|
-
|
|
26964
|
+
if (!msgMentions.has(a.userId)) continue;
|
|
26965
|
+
if (!st.rootMentions.includes(a.name)) st.rootMentions.push(a.name);
|
|
26966
|
+
if (senderAgent && a.name !== senderAgent.name) st.callers[a.name] = senderAgent.name;
|
|
26953
26967
|
}
|
|
26954
26968
|
}
|
|
26955
26969
|
for (const a of matches) {
|
|
@@ -26958,7 +26972,7 @@ function createMatrixTransport(opts) {
|
|
|
26958
26972
|
if (!promotedRoot) return;
|
|
26959
26973
|
let st = threadStates.get(promotedRoot);
|
|
26960
26974
|
if (!st) {
|
|
26961
|
-
st = { participants: [], rootMentions: [] };
|
|
26975
|
+
st = { participants: [], rootMentions: [], callers: {} };
|
|
26962
26976
|
threadStates.set(promotedRoot, st);
|
|
26963
26977
|
}
|
|
26964
26978
|
if (st.participants.at(-1) !== a.name) st.participants.push(a.name);
|
|
@@ -27134,16 +27148,18 @@ function createMatrixTransport(opts) {
|
|
|
27134
27148
|
};
|
|
27135
27149
|
}
|
|
27136
27150
|
async function rebuildThreadState(client, roomId, rootEventId, bindings) {
|
|
27137
|
-
const state = { participants: [], rootMentions: [] };
|
|
27151
|
+
const state = { participants: [], rootMentions: [], callers: {} };
|
|
27138
27152
|
const asUser = (bindings.find((b) => b.rooms.some((r) => r.alias === roomId)) ?? bindings[0])?.userId;
|
|
27139
27153
|
if (!asUser) return state;
|
|
27140
27154
|
const root = await client.fetchEvent(roomId, rootEventId, asUser);
|
|
27141
27155
|
if (root) {
|
|
27142
27156
|
const rootMentions = new Set(extractMentions(root));
|
|
27157
|
+
const rootSender = root.sender;
|
|
27158
|
+
const rootSenderAgent = rootSender ? bindings.find((b) => b.userId === rootSender) : void 0;
|
|
27143
27159
|
for (const a of bindings) {
|
|
27144
|
-
if (rootMentions.has(a.userId)
|
|
27145
|
-
|
|
27146
|
-
|
|
27160
|
+
if (!rootMentions.has(a.userId)) continue;
|
|
27161
|
+
if (!state.rootMentions.includes(a.name)) state.rootMentions.push(a.name);
|
|
27162
|
+
if (rootSenderAgent && a.name !== rootSenderAgent.name) state.callers[a.name] = rootSenderAgent.name;
|
|
27147
27163
|
}
|
|
27148
27164
|
}
|
|
27149
27165
|
const { chunk: thread } = await client.fetchThreadRelations({
|
|
@@ -27153,15 +27169,16 @@ async function rebuildThreadState(client, roomId, rootEventId, bindings) {
|
|
|
27153
27169
|
});
|
|
27154
27170
|
for (const ev of thread) {
|
|
27155
27171
|
const mentions = new Set(extractMentions(ev));
|
|
27172
|
+
const evSender = ev.sender;
|
|
27173
|
+
const evSenderAgent = evSender ? bindings.find((b) => b.userId === evSender) : void 0;
|
|
27156
27174
|
for (const a of bindings) {
|
|
27157
|
-
if (mentions.has(a.userId)
|
|
27158
|
-
|
|
27159
|
-
|
|
27175
|
+
if (!mentions.has(a.userId)) continue;
|
|
27176
|
+
if (!state.rootMentions.includes(a.name)) state.rootMentions.push(a.name);
|
|
27177
|
+
if (evSenderAgent && a.name !== evSenderAgent.name) state.callers[a.name] = evSenderAgent.name;
|
|
27160
27178
|
}
|
|
27161
|
-
const sender = ev.sender;
|
|
27162
27179
|
const type = ev.type;
|
|
27163
|
-
if (type === "m.room.message" &&
|
|
27164
|
-
const a = bindings.find((b) => b.userId ===
|
|
27180
|
+
if (type === "m.room.message" && evSender) {
|
|
27181
|
+
const a = bindings.find((b) => b.userId === evSender);
|
|
27165
27182
|
if (a && state.participants.at(-1) !== a.name) state.participants.push(a.name);
|
|
27166
27183
|
}
|
|
27167
27184
|
}
|
|
@@ -32795,4 +32812,4 @@ export {
|
|
|
32795
32812
|
startDaemonSocketServer,
|
|
32796
32813
|
buildAcpRegistry
|
|
32797
32814
|
};
|
|
32798
|
-
//# sourceMappingURL=chunk-
|
|
32815
|
+
//# sourceMappingURL=chunk-LIMRV7C4.js.map
|