openclaw-quiubo 2.6.29 → 2.6.31
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/MULTI_AGENT.md +281 -0
- package/README.md +19 -129
- package/dist/index.js +20 -20
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -2
package/MULTI_AGENT.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Multi-Agent Setup
|
|
2
|
+
|
|
3
|
+
One gateway. Multiple agents. Each with its own chat, model, workspace, and schedule — completely isolated from each other.
|
|
4
|
+
|
|
5
|
+
## What You Get
|
|
6
|
+
|
|
7
|
+
- **Own chat** — separate Quiubo identity and conversation thread
|
|
8
|
+
- **Own model** — Opus for heavy thinking, Haiku for lightweight jobs
|
|
9
|
+
- **Own workspace** — files, memory, history are private
|
|
10
|
+
- **Own schedule** — cron jobs scoped per agent
|
|
11
|
+
- **One key** — all agents share your SDK API key (and its quota — see [API Key Quota](#api-key-quota))
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
**Bob** is your personal assistant (Opus, handles research and writing).
|
|
16
|
+
**Sam** is your project manager (Sonnet, runs standups and tracks tasks).
|
|
17
|
+
|
|
18
|
+
Both appear as separate chats in Quiubo — independent conversations, independent workspaces.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
### 1. Add a bot identity
|
|
23
|
+
|
|
24
|
+
Run the interactive wizard to add a new account:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
openclaw channels add
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The wizard asks for three things:
|
|
31
|
+
- **Account ID** — short name for this agent (e.g. `pm`, `research`). Your first agent uses `default`.
|
|
32
|
+
- **SDK API Key** — reuse your existing `qub_...` key.
|
|
33
|
+
- **Bot identity** — choose "Create new bot identity" to give this agent its own name in Quiubo.
|
|
34
|
+
|
|
35
|
+
### 2. Register the agent
|
|
36
|
+
|
|
37
|
+
Add an entry to `agents.list` in `~/.openclaw/openclaw.json`. Give it a unique ID and its own workspace path:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"agents": {
|
|
42
|
+
"defaults": {
|
|
43
|
+
"workspace": "~/.openclaw/workspace"
|
|
44
|
+
},
|
|
45
|
+
"list": [
|
|
46
|
+
{
|
|
47
|
+
"id": "main",
|
|
48
|
+
"subagents": { "allowAgents": ["*"] }
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "project-manager",
|
|
52
|
+
"name": "Sam",
|
|
53
|
+
"model": "anthropic/claude-sonnet-4-5",
|
|
54
|
+
"workspace": "~/.openclaw/workspace-project-manager",
|
|
55
|
+
"agentDir": "~/.openclaw/agents/project-manager/agent",
|
|
56
|
+
"subagents": { "allowAgents": ["*"] }
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `main` agent inherits `workspace` from `agents.defaults`. Non-main agents override it with their own path.
|
|
64
|
+
|
|
65
|
+
- **`workspace`** — the agent's working directory (files it creates, edits, and reads during tasks).
|
|
66
|
+
- **`agentDir`** — the agent's identity/state directory (`IDENTITY.md`, `SOUL.md`, `AGENTS.md`, memory). Optional — defaults to a path under `~/.openclaw/agents/{id}/agent`. Set it explicitly if you want control over where identity files live.
|
|
67
|
+
|
|
68
|
+
### 3. Bind account to agent
|
|
69
|
+
|
|
70
|
+
Tell OpenClaw which Quiubo account maps to which agent using the `bindings` array:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"bindings": [
|
|
75
|
+
{
|
|
76
|
+
"agentId": "main",
|
|
77
|
+
"match": {
|
|
78
|
+
"channel": "quiubo",
|
|
79
|
+
"accountId": "default"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"agentId": "project-manager",
|
|
84
|
+
"match": {
|
|
85
|
+
"channel": "quiubo",
|
|
86
|
+
"accountId": "pm"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
*Without bindings, all messages route to the first agent in the list.*
|
|
94
|
+
|
|
95
|
+
### 4. Create the workspace
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
mkdir -p ~/.openclaw/workspace-project-manager
|
|
99
|
+
mkdir -p ~/.openclaw/agents/project-manager/agent/memory
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Seed the agent dir with identity files: `SOUL.md`, `AGENTS.md`, `USER.md`, `IDENTITY.md`, `MEMORY.md`.
|
|
103
|
+
|
|
104
|
+
To help the agent understand the multi-agent setup, add this to its `AGENTS.md`:
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
## Multi-Agent Architecture
|
|
108
|
+
This agent runs as part of a multi-agent OpenClaw gateway.
|
|
109
|
+
Each agent has its own chat, workspace, and cron jobs.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> **Note:** If you use auth profiles (e.g. GitHub tokens), each agent workspace needs its own `config.json` or shared auth config.
|
|
113
|
+
|
|
114
|
+
### 5. Restart
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
openclaw gateway restart
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The new agent gets its own welcome chat automatically.
|
|
121
|
+
|
|
122
|
+
## Config Integrity Warning
|
|
123
|
+
|
|
124
|
+
Your config will have account data in **two** places:
|
|
125
|
+
|
|
126
|
+
| Location | Who writes it | Who reads it |
|
|
127
|
+
|----------|---------------|--------------|
|
|
128
|
+
| `channels.quiubo.accounts` | `openclaw channels add` wizard | **The plugin** (source of truth) |
|
|
129
|
+
| `plugins.entries.openclaw-quiubo.config.accounts` | OpenClaw core (mirrors plugin config) | OpenClaw core |
|
|
130
|
+
|
|
131
|
+
The plugin reads **only** from `channels.quiubo.accounts`. If that section is missing or empty, the plugin silently does nothing — no `[quiubo]` logs, no errors, just gone.
|
|
132
|
+
|
|
133
|
+
> **The #1 footgun:** an agent running `config.patch` that omits `channels` will wipe `channels.quiubo.accounts` entirely. The plugin dies on the next restart while everything else looks fine.
|
|
134
|
+
>
|
|
135
|
+
> **Rule of thumb:** never do a full config write/patch that doesn't preserve `channels`. Always merge rather than replace.
|
|
136
|
+
|
|
137
|
+
## API Key Quota
|
|
138
|
+
|
|
139
|
+
All accounts sharing the same `qub_...` API key share its monthly request quota. If one agent is chatty enough to exhaust the limit, **all** agents using that key stop working on the next restart.
|
|
140
|
+
|
|
141
|
+
Options:
|
|
142
|
+
- **One key, shared quota** — simpler setup, fine for low-traffic agents.
|
|
143
|
+
- **Separate keys per account** — independent quotas, each agent can hit its own ceiling without affecting others.
|
|
144
|
+
|
|
145
|
+
## How It Works
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
┌─────────────────────────┐
|
|
149
|
+
│ OpenClaw Gateway │
|
|
150
|
+
└────────┬────────────────┘
|
|
151
|
+
│
|
|
152
|
+
┌──────────────┼──────────────┐
|
|
153
|
+
▼ ▼ ▼
|
|
154
|
+
account:default account:pm account:research
|
|
155
|
+
│ │ │
|
|
156
|
+
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
|
|
157
|
+
│ gateway │ │ gateway │ │ gateway │
|
|
158
|
+
│ client │ │ client │ │ client │
|
|
159
|
+
│ logger │ │ logger │ │ logger │
|
|
160
|
+
│ cursor │ │ cursor │ │ cursor │
|
|
161
|
+
└────┬────┘ └────┬────┘ └────┬────┘
|
|
162
|
+
│ │ │
|
|
163
|
+
binding ──→ binding ──→ binding ──→
|
|
164
|
+
agent:main agent:pm agent:research
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Isolation boundaries:**
|
|
168
|
+
|
|
169
|
+
- **Per-account Maps** — each account gets its own gateway, API client, config, and logger.
|
|
170
|
+
- **Per-account cursors** — `~/.openclaw/cron/quiubo-cursors-{accountId}.json` — no clobbering between agents.
|
|
171
|
+
- **Per-account E2EE keys** — `~/.openclaw/cron/quiubo-keys-{accountId}.json` — each account's key seed. If lost, the agent re-enrolls its keys (may break E2EE for existing conversations).
|
|
172
|
+
- **Session keys** — main agent uses `quiubo:{groupId}`, others use `agent:{agentId}:quiubo:{groupId}` — conversations never cross.
|
|
173
|
+
- **Cron jobs** — filtered by `agentId` in `getActivityData()` — each agent's chat shows only its own scheduled jobs.
|
|
174
|
+
- **Auto-provisioning** — each new account gets a welcome `agent_channel` group with `externalGroupId: auto-{accountId}`.
|
|
175
|
+
- **Auto-registration** — on first startup, each account auto-registers an agent record in the Quiubo directory if none exists for its bot identity.
|
|
176
|
+
|
|
177
|
+
**Agent resolution** (`resolveAgentId`): searches `bindings[]` for matching channel + accountId. Falls back to first agent in `agents.list`, then to `"main"`.
|
|
178
|
+
|
|
179
|
+
## Cron Jobs
|
|
180
|
+
|
|
181
|
+
Cron jobs are scoped per agent via the `agentId` field:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"jobs": [
|
|
186
|
+
{
|
|
187
|
+
"id": "daily-standup",
|
|
188
|
+
"agentId": "project-manager",
|
|
189
|
+
"schedule": "0 9 * * 1-5",
|
|
190
|
+
"enabled": true,
|
|
191
|
+
"delivery": {
|
|
192
|
+
"channel": "quiubo",
|
|
193
|
+
"to": "<group-uuid>"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- **`agentId`**: Ensures only the "project-manager" agent sees/runs this job. **Required** — jobs without `agentId` are invisible to all agents.
|
|
201
|
+
- **`delivery.channel`**: Must be `"quiubo"`.
|
|
202
|
+
- **`delivery.to`**: The raw group UUID (e.g. `eec8a4ce-d08d-483a-ba5d-90a43ce8f521`). Find it in the agent's welcome chat or via the API.
|
|
203
|
+
|
|
204
|
+
> **Multi-agent caveat:** The fallback resolver (`resolveAnnounceGroupId`) reads cron jobs from disk but does not filter by accountId. If multiple agents have cron jobs, it may resolve to the wrong agent's target group. Keep one cron job per agent, or ensure each agent's delivery target is unambiguous.
|
|
205
|
+
|
|
206
|
+
## Full Config Reference
|
|
207
|
+
|
|
208
|
+
Quiubo-relevant sections of `openclaw.json` for a 2-agent setup:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"agents": {
|
|
213
|
+
"defaults": {
|
|
214
|
+
"workspace": "~/.openclaw/workspace"
|
|
215
|
+
},
|
|
216
|
+
"list": [
|
|
217
|
+
{
|
|
218
|
+
"id": "main",
|
|
219
|
+
"subagents": { "allowAgents": ["*"] }
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"id": "project-manager",
|
|
223
|
+
"name": "Sam",
|
|
224
|
+
"model": "anthropic/claude-sonnet-4-5",
|
|
225
|
+
"workspace": "~/.openclaw/workspace-project-manager",
|
|
226
|
+
"agentDir": "~/.openclaw/agents/project-manager/agent",
|
|
227
|
+
"subagents": { "allowAgents": ["*"] }
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
"bindings": [
|
|
232
|
+
{
|
|
233
|
+
"agentId": "main",
|
|
234
|
+
"match": {
|
|
235
|
+
"channel": "quiubo",
|
|
236
|
+
"accountId": "default"
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"agentId": "project-manager",
|
|
241
|
+
"match": {
|
|
242
|
+
"channel": "quiubo",
|
|
243
|
+
"accountId": "pm"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"channels": {
|
|
248
|
+
"quiubo": {
|
|
249
|
+
"accounts": {
|
|
250
|
+
"default": {
|
|
251
|
+
"enabled": true,
|
|
252
|
+
"apiKey": "qub_your_key_here",
|
|
253
|
+
"botIdentityId": "<main-bot-uuid>",
|
|
254
|
+
"apiUrl": "https://api.quiubo.io"
|
|
255
|
+
},
|
|
256
|
+
"pm": {
|
|
257
|
+
"enabled": true,
|
|
258
|
+
"apiKey": "qub_your_key_here",
|
|
259
|
+
"botIdentityId": "<pm-bot-uuid>",
|
|
260
|
+
"apiUrl": "https://api.quiubo.io"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
> **Note:** OpenClaw also mirrors account data into `plugins.entries.openclaw-quiubo.config.accounts`. The plugin ignores that copy — `channels.quiubo.accounts` is the only location that matters. See [Config Integrity Warning](#config-integrity-warning).
|
|
269
|
+
|
|
270
|
+
## Troubleshooting
|
|
271
|
+
|
|
272
|
+
| Symptom | Fix |
|
|
273
|
+
|---------|-----|
|
|
274
|
+
| Plugin silently stops — no `[quiubo]` logs on startup | `channels.quiubo.accounts` is missing from config. An agent's `config.patch` likely wiped it. Restore the `channels` block and restart. |
|
|
275
|
+
| Second agent doesn't respond | Check `bindings[]` — accountId must match exactly. Run `openclaw gateway restart`. |
|
|
276
|
+
| Both agents reply to the same message | Missing or duplicate bindings. Each accountId needs exactly one binding. |
|
|
277
|
+
| Agent dies on restart, others work fine | Check logs for `[FATAL] STARTUP FAILED`. Common causes: invalid/expired API key (401), permission error (403), or quota exhausted. Rotate the key or check usage. |
|
|
278
|
+
| Cron job runs in wrong chat | Verify `agentId` on the job matches the target agent, and `delivery.to` uses the correct group UUID. |
|
|
279
|
+
| "welcome group already exists" on restart | Normal — auto-provisioning is safe. The existing group is reused. |
|
|
280
|
+
| Agent shows other agent's cron jobs | The `agentId` on the job doesn't match. Double-check spelling. |
|
|
281
|
+
| Cron job never runs / missing from activity | Missing `agentId` field on the job. Untagged jobs are invisible to all agents — the filter requires an exact match. |
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ openclaw plugins update openclaw-quiubo
|
|
|
17
17
|
## Setup
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
openclaw channels add
|
|
20
|
+
openclaw channels add
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
The interactive wizard will:
|
|
@@ -58,7 +58,7 @@ channels:
|
|
|
58
58
|
|
|
59
59
|
### Multiple accounts
|
|
60
60
|
|
|
61
|
-
Run the setup wizard again and enter a different Account ID when prompted (e.g., `support-bot`, `sales-bot`). Each account gets its own gateway instance, cursor file, and bot config cache.
|
|
61
|
+
Run the setup wizard again and enter a different Account ID when prompted (e.g., `support-bot`, `sales-bot`). Each account gets its own gateway instance, cursor file, and bot config cache. For full multi-agent isolation (separate workspaces, models, cron jobs), see the **[Multi-Agent Setup guide](./MULTI_AGENT.md)**.
|
|
62
62
|
|
|
63
63
|
## Architecture
|
|
64
64
|
|
|
@@ -98,7 +98,7 @@ Four layers prevent old messages from being reprocessed on restart:
|
|
|
98
98
|
|
|
99
99
|
The plugin supports Quiubo's E2EE protocol for groups that require it:
|
|
100
100
|
|
|
101
|
-
- **Key generation**: Deterministic Ed25519 + X25519 keypairs derived from a 32-byte seed (persisted
|
|
101
|
+
- **Key generation**: Deterministic Ed25519 + X25519 keypairs derived from a 32-byte seed (persisted to `~/.openclaw/cron/quiubo-keys-{accountId}.json`)
|
|
102
102
|
- **Auto-enrollment**: On first startup, generates keypair and enrolls via challenge-response (`requestKeyChallenge` > `signChallenge` > `verifyKeyChallenge`)
|
|
103
103
|
- **Inbound decryption**: GroupEnvelopeV2 messages decrypted using XChaCha20-Poly1305 with epoch keys
|
|
104
104
|
- **Outbound encryption**: Plaintext encrypted before sending when E2EE is granted for the group
|
|
@@ -143,125 +143,16 @@ On first gateway startup, if the bot has no groups, the plugin automatically cre
|
|
|
143
143
|
|
|
144
144
|
## Multi-Agent Setup
|
|
145
145
|
|
|
146
|
-
Run multiple
|
|
146
|
+
Run multiple agents on a single gateway — each with its own chat, model, workspace, and cron jobs.
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
1. `openclaw channels add` — create a new bot identity
|
|
149
|
+
2. Add the agent and binding to `~/.openclaw/openclaw.json`
|
|
150
|
+
3. Create the agent's workspace
|
|
151
|
+
4. `openclaw gateway restart`
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
All agents share one SDK API key (and its quota). Each gets fully isolated conversations, cursors, and session keys.
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
openclaw channels add --channel quiubo
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
The wizard will:
|
|
159
|
-
|
|
160
|
-
1. Ask for an **Account ID** — enter a short name for this agent (e.g. `pm`, `support`, `research`). Your first/default agent uses `default`.
|
|
161
|
-
2. Ask for your **SDK API Key** — you can reuse the same `qub_...` key across all agents.
|
|
162
|
-
3. **Create or select a bot identity** — choose "Create new bot identity" to give this agent its own username and display name in Quiubo.
|
|
163
|
-
|
|
164
|
-
> **Where to get an SDK API Key:** In the Quiubo app, go to **Settings > Developer > API Keys**. One key works for all agents under the same app.
|
|
165
|
-
|
|
166
|
-
After the wizard completes, the new account is saved to `~/.openclaw/openclaw.json` automatically:
|
|
167
|
-
|
|
168
|
-
```json
|
|
169
|
-
{
|
|
170
|
-
"plugins": {
|
|
171
|
-
"quiubo": {
|
|
172
|
-
"accounts": {
|
|
173
|
-
"default": {
|
|
174
|
-
"apiKey": "qub_...",
|
|
175
|
-
"botIdentityId": "<main-bot-uuid>"
|
|
176
|
-
},
|
|
177
|
-
"pm": {
|
|
178
|
-
"apiKey": "qub_...",
|
|
179
|
-
"botIdentityId": "<pm-bot-uuid>"
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Step 2: Add the agent to OpenClaw
|
|
188
|
-
|
|
189
|
-
In `~/.openclaw/openclaw.json`, add an entry to the agents list:
|
|
190
|
-
|
|
191
|
-
```json
|
|
192
|
-
{
|
|
193
|
-
"agents": {
|
|
194
|
-
"list": [
|
|
195
|
-
{ "id": "main" },
|
|
196
|
-
{
|
|
197
|
-
"id": "project-manager",
|
|
198
|
-
"model": "anthropic/claude-sonnet-4-5",
|
|
199
|
-
"workspace": "~/.openclaw/workspace-project-manager"
|
|
200
|
-
}
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Each agent can have its own model, workspace, and system prompt.
|
|
207
|
-
|
|
208
|
-
### Step 3: Bind the Quiubo account to the agent
|
|
209
|
-
|
|
210
|
-
Tell OpenClaw which Quiubo account maps to which agent:
|
|
211
|
-
|
|
212
|
-
```json
|
|
213
|
-
{
|
|
214
|
-
"bindings": [
|
|
215
|
-
{
|
|
216
|
-
"match": { "channel": "quiubo", "accountId": "default" },
|
|
217
|
-
"agentId": "main"
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"match": { "channel": "quiubo", "accountId": "pm" },
|
|
221
|
-
"agentId": "project-manager"
|
|
222
|
-
}
|
|
223
|
-
]
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Without bindings, all messages route to the first agent.
|
|
228
|
-
|
|
229
|
-
### Step 4: Create the agent's workspace
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
mkdir -p ~/.openclaw/workspace-project-manager/memory
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Seed it with: `SOUL.md`, `AGENTS.md`, `USER.md`, `IDENTITY.md`, `MEMORY.md`.
|
|
236
|
-
|
|
237
|
-
### Step 5: Restart
|
|
238
|
-
|
|
239
|
-
```bash
|
|
240
|
-
openclaw gateway restart
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### How routing works
|
|
244
|
-
|
|
245
|
-
```
|
|
246
|
-
Quiubo Chat (You + Main Bot) → account: default → binding → agent: main
|
|
247
|
-
Quiubo Chat (You + PM Bot) → account: pm → binding → agent: project-manager
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
- **Messages**: AccountId resolves the agent via bindings
|
|
251
|
-
- **Cron jobs**: Filtered by agentId — each agent's chat shows only its own
|
|
252
|
-
- **Sessions**: Isolated session namespaces per agent, no cross-talk
|
|
253
|
-
- **Workspaces**: Fully isolated — agents can't see each other's files
|
|
254
|
-
- **Cursors**: Each account has its own cursor file — no clobbering
|
|
255
|
-
|
|
256
|
-
### Adding more agents
|
|
257
|
-
|
|
258
|
-
Repeat steps 1–5 for each new agent. The key command is:
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
openclaw channels add --channel quiubo
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Enter a unique Account ID each time (e.g. `support`, `research`). The wizard handles identity creation and config — then add the agent, binding, and workspace as shown above.
|
|
155
|
+
**[Full guide](./MULTI_AGENT.md)** — architecture, config examples, cron setup, and troubleshooting.
|
|
265
156
|
|
|
266
157
|
## Markdown Attachments
|
|
267
158
|
|
|
@@ -269,7 +160,7 @@ Agents can send `.md` file attachments alongside messages. Attachments appear as
|
|
|
269
160
|
|
|
270
161
|
OpenClaw's `MEDIA:` token protocol is used: when an agent writes a file and includes `MEDIA: /path/to/file.md` in its response, the plugin reads the file and sends it as a structured attachment.
|
|
271
162
|
|
|
272
|
-
**Supported:** `.md` files
|
|
163
|
+
**Supported:** `.md` files (max 1MB) and images — `.jpg`, `.jpeg`, `.png`, `.webp` (max 5MB, uploaded via S3 presign). Source tracking distinguishes `agent` vs `subagent` attachments.
|
|
273
164
|
|
|
274
165
|
Add this to your agent's `AGENTS.md`:
|
|
275
166
|
|
|
@@ -286,20 +177,19 @@ Example response:
|
|
|
286
177
|
MEDIA: /tmp/daily-report.md
|
|
287
178
|
|
|
288
179
|
The file will be delivered as a tappable attachment card in the chat.
|
|
289
|
-
|
|
180
|
+
Supported: `.md` (max 1MB) and images `.jpg`/`.png`/`.webp` (max 5MB).
|
|
290
181
|
```
|
|
291
182
|
|
|
292
183
|
## Operational Notes
|
|
293
184
|
|
|
294
|
-
###
|
|
185
|
+
### Per-account state files
|
|
295
186
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
```
|
|
299
|
-
~/.openclaw/cron/quiubo-cursors-{accountId}.json
|
|
300
|
-
```
|
|
187
|
+
Each account persists its own state files under `~/.openclaw/cron/`:
|
|
301
188
|
|
|
302
|
-
|
|
189
|
+
| File | Purpose | Safe to delete? |
|
|
190
|
+
|------|---------|-----------------|
|
|
191
|
+
| `quiubo-cursors-{accountId}.json` | Message cursor positions | Yes — gateway will `seekToLatest()` on next start (no replay) |
|
|
192
|
+
| `quiubo-keys-{accountId}.json` | E2EE key seed (32 bytes) | **No** — deleting forces re-enrollment, may break E2EE for existing conversations |
|
|
303
193
|
|
|
304
194
|
### Heartbeat
|
|
305
195
|
|
|
@@ -313,7 +203,7 @@ A typing indicator is sent immediately when processing begins, then repeated eve
|
|
|
313
203
|
|
|
314
204
|
```bash
|
|
315
205
|
openclaw channels disable --channel quiubo # Disable
|
|
316
|
-
openclaw channels add
|
|
206
|
+
openclaw channels add # Re-configure
|
|
317
207
|
```
|
|
318
208
|
|
|
319
209
|
## Development
|
package/dist/index.js
CHANGED
|
@@ -31,9 +31,9 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
|
|
|
31
31
|
mod2
|
|
32
32
|
));
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// node_modules/pusher-js/dist/node/pusher.js
|
|
35
35
|
var require_pusher = __commonJS({
|
|
36
|
-
"
|
|
36
|
+
"node_modules/pusher-js/dist/node/pusher.js"(exports, module) {
|
|
37
37
|
"use strict";
|
|
38
38
|
module.exports = /******/
|
|
39
39
|
(function(modules) {
|
|
@@ -9265,11 +9265,11 @@ var import_pusher_js = __toESM(require_pusher(), 1);
|
|
|
9265
9265
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
9266
9266
|
import { join } from "path";
|
|
9267
9267
|
|
|
9268
|
-
//
|
|
9268
|
+
// node_modules/@noble/hashes/esm/cryptoNode.js
|
|
9269
9269
|
import * as nc from "node:crypto";
|
|
9270
9270
|
var crypto = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0;
|
|
9271
9271
|
|
|
9272
|
-
//
|
|
9272
|
+
// node_modules/@noble/hashes/esm/utils.js
|
|
9273
9273
|
function isBytes(a) {
|
|
9274
9274
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
9275
9275
|
}
|
|
@@ -9405,7 +9405,7 @@ function randomBytes(bytesLength = 32) {
|
|
|
9405
9405
|
throw new Error("crypto.getRandomValues must be defined");
|
|
9406
9406
|
}
|
|
9407
9407
|
|
|
9408
|
-
//
|
|
9408
|
+
// node_modules/@noble/hashes/esm/_md.js
|
|
9409
9409
|
function setBigUint64(view, byteOffset, value, isLE2) {
|
|
9410
9410
|
if (typeof view.setBigUint64 === "function")
|
|
9411
9411
|
return view.setBigUint64(byteOffset, value, isLE2);
|
|
@@ -9543,7 +9543,7 @@ var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
|
|
|
9543
9543
|
327033209
|
|
9544
9544
|
]);
|
|
9545
9545
|
|
|
9546
|
-
//
|
|
9546
|
+
// node_modules/@noble/hashes/esm/_u64.js
|
|
9547
9547
|
var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
9548
9548
|
var _32n = /* @__PURE__ */ BigInt(32);
|
|
9549
9549
|
function fromBig(n, le = false) {
|
|
@@ -9578,7 +9578,7 @@ var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0
|
|
|
9578
9578
|
var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
|
|
9579
9579
|
var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
|
|
9580
9580
|
|
|
9581
|
-
//
|
|
9581
|
+
// node_modules/@noble/hashes/esm/sha2.js
|
|
9582
9582
|
var SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
9583
9583
|
1116352408,
|
|
9584
9584
|
1899447441,
|
|
@@ -9916,7 +9916,7 @@ var SHA512 = class extends HashMD {
|
|
|
9916
9916
|
var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
9917
9917
|
var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
|
|
9918
9918
|
|
|
9919
|
-
//
|
|
9919
|
+
// node_modules/@noble/curves/esm/utils.js
|
|
9920
9920
|
var _0n = /* @__PURE__ */ BigInt(0);
|
|
9921
9921
|
var _1n = /* @__PURE__ */ BigInt(1);
|
|
9922
9922
|
function _abool2(value, title = "") {
|
|
@@ -10029,7 +10029,7 @@ function memoized(fn) {
|
|
|
10029
10029
|
};
|
|
10030
10030
|
}
|
|
10031
10031
|
|
|
10032
|
-
//
|
|
10032
|
+
// node_modules/@noble/curves/esm/abstract/modular.js
|
|
10033
10033
|
var _0n2 = BigInt(0);
|
|
10034
10034
|
var _1n2 = BigInt(1);
|
|
10035
10035
|
var _2n = /* @__PURE__ */ BigInt(2);
|
|
@@ -10355,7 +10355,7 @@ function Field(ORDER, bitLenOrOpts, isLE2 = false, opts = {}) {
|
|
|
10355
10355
|
return Object.freeze(f);
|
|
10356
10356
|
}
|
|
10357
10357
|
|
|
10358
|
-
//
|
|
10358
|
+
// node_modules/@noble/curves/esm/abstract/curve.js
|
|
10359
10359
|
var _0n3 = BigInt(0);
|
|
10360
10360
|
var _1n3 = BigInt(1);
|
|
10361
10361
|
function negateCt(condition, item) {
|
|
@@ -10618,7 +10618,7 @@ function _createCurveFields(type, CURVE, curveOpts = {}, FpFnLE) {
|
|
|
10618
10618
|
return { CURVE, Fp: Fp2, Fn: Fn2 };
|
|
10619
10619
|
}
|
|
10620
10620
|
|
|
10621
|
-
//
|
|
10621
|
+
// node_modules/@noble/curves/esm/abstract/edwards.js
|
|
10622
10622
|
var _0n4 = BigInt(0);
|
|
10623
10623
|
var _1n4 = BigInt(1);
|
|
10624
10624
|
var _2n2 = BigInt(2);
|
|
@@ -11169,7 +11169,7 @@ function twistedEdwards(c) {
|
|
|
11169
11169
|
return _eddsa_new_output_to_legacy(c, EDDSA);
|
|
11170
11170
|
}
|
|
11171
11171
|
|
|
11172
|
-
//
|
|
11172
|
+
// node_modules/@noble/curves/esm/abstract/montgomery.js
|
|
11173
11173
|
var _0n5 = BigInt(0);
|
|
11174
11174
|
var _1n5 = BigInt(1);
|
|
11175
11175
|
var _2n3 = BigInt(2);
|
|
@@ -11289,7 +11289,7 @@ function montgomery(curveDef) {
|
|
|
11289
11289
|
};
|
|
11290
11290
|
}
|
|
11291
11291
|
|
|
11292
|
-
//
|
|
11292
|
+
// node_modules/@noble/curves/esm/ed25519.js
|
|
11293
11293
|
var _0n6 = /* @__PURE__ */ BigInt(0);
|
|
11294
11294
|
var _1n6 = BigInt(1);
|
|
11295
11295
|
var _2n4 = BigInt(2);
|
|
@@ -11524,7 +11524,7 @@ _RistrettoPoint.ZERO = /* @__PURE__ */ (() => new _RistrettoPoint(ed25519.Point.
|
|
|
11524
11524
|
_RistrettoPoint.Fp = /* @__PURE__ */ (() => Fp)();
|
|
11525
11525
|
_RistrettoPoint.Fn = /* @__PURE__ */ (() => Fn)();
|
|
11526
11526
|
|
|
11527
|
-
//
|
|
11527
|
+
// node_modules/@noble/ciphers/esm/utils.js
|
|
11528
11528
|
function isBytes2(a) {
|
|
11529
11529
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
11530
11530
|
}
|
|
@@ -11680,7 +11680,7 @@ function copyBytes2(bytes) {
|
|
|
11680
11680
|
return Uint8Array.from(bytes);
|
|
11681
11681
|
}
|
|
11682
11682
|
|
|
11683
|
-
//
|
|
11683
|
+
// node_modules/@noble/ciphers/esm/_arx.js
|
|
11684
11684
|
var _utf8ToBytes = (str) => Uint8Array.from(str.split("").map((c) => c.charCodeAt(0)));
|
|
11685
11685
|
var sigma16 = _utf8ToBytes("expand 16-byte k");
|
|
11686
11686
|
var sigma32 = _utf8ToBytes("expand 32-byte k");
|
|
@@ -11788,7 +11788,7 @@ function createCipher(core, opts) {
|
|
|
11788
11788
|
};
|
|
11789
11789
|
}
|
|
11790
11790
|
|
|
11791
|
-
//
|
|
11791
|
+
// node_modules/@noble/ciphers/esm/_poly1305.js
|
|
11792
11792
|
var u8to16 = (a, i) => a[i++] & 255 | (a[i++] & 255) << 8;
|
|
11793
11793
|
var Poly1305 = class {
|
|
11794
11794
|
constructor(key) {
|
|
@@ -12043,7 +12043,7 @@ function wrapConstructorWithKey(hashCons) {
|
|
|
12043
12043
|
}
|
|
12044
12044
|
var poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
|
|
12045
12045
|
|
|
12046
|
-
//
|
|
12046
|
+
// node_modules/@noble/ciphers/esm/chacha.js
|
|
12047
12047
|
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
|
12048
12048
|
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2];
|
|
12049
12049
|
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
|
|
@@ -12274,10 +12274,10 @@ var xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength:
|
|
|
12274
12274
|
// src/crypto.ts
|
|
12275
12275
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12276
12276
|
|
|
12277
|
-
//
|
|
12277
|
+
// node_modules/@noble/hashes/esm/sha256.js
|
|
12278
12278
|
var sha2562 = sha256;
|
|
12279
12279
|
|
|
12280
|
-
//
|
|
12280
|
+
// node_modules/@noble/hashes/esm/hmac.js
|
|
12281
12281
|
var HMAC = class extends Hash {
|
|
12282
12282
|
constructor(hash, _key) {
|
|
12283
12283
|
super();
|
|
@@ -12345,7 +12345,7 @@ var HMAC = class extends Hash {
|
|
|
12345
12345
|
var hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
|
|
12346
12346
|
hmac.create = (hash, key) => new HMAC(hash, key);
|
|
12347
12347
|
|
|
12348
|
-
//
|
|
12348
|
+
// node_modules/@noble/hashes/esm/hkdf.js
|
|
12349
12349
|
function extract(hash, ikm, salt) {
|
|
12350
12350
|
ahash(hash);
|
|
12351
12351
|
if (salt === void 0)
|