@mutirolabs/openclaw-brain 0.1.1 → 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.
- package/CHANGELOG.md +51 -3
- package/README.md +44 -217
- package/docs/assets/mutiro-openclaw-ui.png +0 -0
- package/docs/guides/manage-allowlist.md +3 -3
- package/docs/guides/use-openclaw-as-brain.md +15 -15
- package/index.ts +1 -1
- package/openclaw.plugin.json +5 -3
- package/package.json +5 -6
- package/src/agent-tools.ts +3 -3
- package/src/bridge-client.ts +1 -2
- package/src/bridge-messages.ts +2 -2
- package/src/bridge-protocol.ts +2 -2
- package/src/bridge-session.ts +2 -2
- package/src/channel.runtime.ts +54 -3
- package/src/channel.ts +61 -2
- package/src/outbound.ts +5 -7
- package/src/setup-surface.ts +39 -11
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-04-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Threading adapter: the agent's first reply in a turn threads under the
|
|
15
|
+
inbound message by default (visible quoted pill in every Mutiro client).
|
|
16
|
+
`channels.mutiro.replyToMode` overrides with `off` | `first` | `all` |
|
|
17
|
+
`batched`. `allowExplicitReplyTagsWhenOff: true` keeps agent-directed reply
|
|
18
|
+
markers working when the user opts out.
|
|
19
|
+
- `replyToMode` exposed in `openclaw.plugin.json` configSchema.
|
|
20
|
+
- Status adapter (`ChannelStatusAdapter.buildAccountSnapshot`):
|
|
21
|
+
`openclaw channels status mutiro` now reports `healthState` (stopped |
|
|
22
|
+
restarting | connecting | healthy), `mode: "bridge"`, and `dbPath`
|
|
23
|
+
pointing at the Mutiro agent workspace.
|
|
24
|
+
- Bridge crash backoff: unexpected host exits track a per-account crash
|
|
25
|
+
streak with a 5-minute reset window and hold the gateway lifecycle promise
|
|
26
|
+
through an exponential delay (1s → 2s → 5s → 15s → 60s). Clean exits
|
|
27
|
+
(code 0 or abort) short-circuit immediately. Surfaces `restartPending`,
|
|
28
|
+
`reconnectAttempts`, and structured `lastDisconnect` on the snapshot.
|
|
29
|
+
- Setup wizard pre-flight: runs `mutiro agent host status` and warns if a
|
|
30
|
+
Mutiro agent host is already running for this agent before starting the
|
|
31
|
+
gateway.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- README rewrite: sharper tagline, hero screenshot (`docs/assets/mutiro-openclaw-ui.png`),
|
|
36
|
+
Prerequisites section folded into the setup wizard, `tools.alsoAllow`
|
|
37
|
+
switched from YAML hand-edit to
|
|
38
|
+
`openclaw config set tools.alsoAllow '["mutiro*"]'`, allowlist reframed
|
|
39
|
+
as an edge-enforced security feature, sibling link to `pi-brain`.
|
|
40
|
+
- Doc links drop `.md` suffixes so they render as HTML; `/docs` replaced
|
|
41
|
+
with `/docs/manual` + `/docs/cli`.
|
|
42
|
+
- Prerequisites "built-in brain stopped" check now references
|
|
43
|
+
`mutiro agent host status` (runtime liveness) instead of
|
|
44
|
+
`mutiro agent doctor` (which only validates config).
|
|
45
|
+
- Internal cleanup: removed `pi-brain` references from source comments
|
|
46
|
+
and changelog.
|
|
47
|
+
- User-facing metadata aligned with OpenClaw's "channel" + "extension"
|
|
48
|
+
language: npm `description`, `keywords`, channel `selectionLabel`,
|
|
49
|
+
`blurb`, plugin entry `description`, and the setup wizard title no
|
|
50
|
+
longer leak the internal `chatbridge` protocol name.
|
|
51
|
+
|
|
52
|
+
### Removed
|
|
53
|
+
|
|
54
|
+
- Unused `MUTIRO_AGENT_API_KEY` from `openclaw.plugin.json` `channelEnvVars`;
|
|
55
|
+
the chathost reads that env var, not this plugin.
|
|
56
|
+
|
|
10
57
|
## [0.1.1] - 2026-04-18
|
|
11
58
|
|
|
12
59
|
### Added
|
|
@@ -20,8 +67,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
20
67
|
|
|
21
68
|
### Added
|
|
22
69
|
|
|
23
|
-
- Initial OpenClaw
|
|
24
|
-
- NDJSON envelope codec
|
|
70
|
+
- Initial OpenClaw Channel extension for Mutiro.
|
|
71
|
+
- NDJSON envelope codec for `mutiro.agent.bridge.v1`.
|
|
25
72
|
- Subprocess lifecycle for `mutiro agent host --mode=bridge`, one per configured account.
|
|
26
73
|
- Inbound pipeline: `message.observed` → OpenClaw reply dispatch.
|
|
27
74
|
- Outbound surface: `message.send`, `message.send_voice`, `message.react`,
|
|
@@ -55,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
55
102
|
with concrete fix commands, plus the paste-into-AI prompt as an alternative
|
|
56
103
|
for users who'd rather have their AI assistant drive the setup.
|
|
57
104
|
|
|
58
|
-
[Unreleased]: https://github.com/mutirolabs/openclaw-brain/compare/v0.
|
|
105
|
+
[Unreleased]: https://github.com/mutirolabs/openclaw-brain/compare/v0.2.0...HEAD
|
|
106
|
+
[0.2.0]: https://github.com/mutirolabs/openclaw-brain/compare/v0.1.1...v0.2.0
|
|
59
107
|
[0.1.1]: https://github.com/mutirolabs/openclaw-brain/compare/v0.1.0...v0.1.1
|
|
60
108
|
[0.1.0]: https://github.com/mutirolabs/openclaw-brain/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,138 +1,62 @@
|
|
|
1
|
-
# Mutiro
|
|
1
|
+
# Mutiro Channel for OpenClaw
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official Mutiro Channel extension for OpenClaw.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
OpenClaw handles the cognition. Mutiro handles the messaging surface, identity, and state.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Start below, confirm each of these passes:
|
|
9
|
+
## Why this exists
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|-------|-----------------|
|
|
14
|
-
| `mutiro version` prints a version | `curl -sSL https://mutiro.com/downloads/install.sh \| bash` |
|
|
15
|
-
| `mutiro auth whoami` prints your username | sign up: `mutiro auth signup <email> <username> "<Display Name>"` — or log in: `mutiro auth login <email>` |
|
|
16
|
-
| `mutiro agents list` shows at least one agent you own | `mutiro agents create <username> "<Display>" --engine genie --bio "<short bio>" --badge lobster` |
|
|
17
|
-
| The built-in Mutiro brain for that agent is **not** running | `mutiro agent doctor`, and stop any `mutiro agent run` / `mutiro start` process for that agent |
|
|
18
|
-
|
|
19
|
-
Want an AI assistant to drive you through those steps instead? Paste this into
|
|
20
|
-
Claude, Cursor, or Windsurf:
|
|
21
|
-
|
|
22
|
-
```text
|
|
23
|
-
Read this page from the Mutiro docs: https://mutiro.com/docs/guides/create-agent.md and help me create an agent step by step.
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Canonical reference: [Mutiro create-agent guide](https://www.mutiro.com/docs/guides/create-agent.md).
|
|
27
|
-
|
|
28
|
-
> **Show the 🦞 badge.** Pass `--badge lobster` on `mutiro agents create` to mark
|
|
29
|
-
> the agent as OpenClaw-powered in the Mutiro UI. Already have an agent? Flip
|
|
30
|
-
> the badge with:
|
|
31
|
-
> ```bash
|
|
32
|
-
> mutiro agents update-profile <agent-username> --badge lobster
|
|
33
|
-
> ```
|
|
34
|
-
|
|
35
|
-
> **Two brains, one agent = trouble.** Mutiro ships its own built-in brain. If
|
|
36
|
-
> you leave it running, it will race OpenClaw for the same conversations and
|
|
37
|
-
> both will reply (or neither will, depending on ordering). Stop it before
|
|
38
|
-
> starting the OpenClaw gateway.
|
|
11
|
+
Sovereign intelligence deserves a professional interface. Hiding a powerful OpenClaw brain behind a generic Telegram bot or a clunky webview breaks the user experience and obscures ownership. This extension implements an OpenClaw Channel that connects your agent to Mutiro's native clients (Desktop, Mobile, Web, CLI), enforcing the `by @owner` accountability standard out of the box.
|
|
39
12
|
|
|
40
13
|
## Quick Start
|
|
41
14
|
|
|
42
|
-
|
|
15
|
+
Install the Mutiro channel using OpenClaw's native extension manager:
|
|
43
16
|
|
|
44
17
|
```bash
|
|
45
18
|
openclaw plugins install --dangerously-force-unsafe-install @mutirolabs/openclaw-brain
|
|
46
19
|
```
|
|
47
20
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
# Skip node_modules if you've run `npm install` here — the install scanner
|
|
52
|
-
# walks the source tree and caps at 10k directories.
|
|
53
|
-
rm -rf node_modules
|
|
54
|
-
openclaw plugins install --dangerously-force-unsafe-install "file:$(pwd)"
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
> **Why the `--dangerously-force-unsafe-install` flag?**
|
|
58
|
-
> This plugin legitimately spawns `mutiro agent host --mode=bridge` as a
|
|
59
|
-
> subprocess — that is the entire point of the `chatbridge` adapter. OpenClaw's
|
|
60
|
-
> install scanner correctly flags any plugin that uses `child_process` as
|
|
61
|
-
> sensitive, and requires this flag as an explicit acknowledgement. Before you
|
|
62
|
-
> pass it, **confirm you are installing from the signed [mutirolabs/openclaw-brain](https://github.com/mutirolabs/openclaw-brain)
|
|
63
|
-
> source** (or the `@mutirolabs/openclaw-brain` npm package). Review the
|
|
64
|
-
> `spawn` call at [`src/bridge-client.ts`](./src/bridge-client.ts) if you want
|
|
65
|
-
> to see exactly what the plugin executes.
|
|
66
|
-
|
|
67
|
-
### 2. Configure the channel
|
|
21
|
+
> The flag is required because this extension launches a Mutiro host process to carry the channel. Install only from the signed [`@mutirolabs/openclaw-brain`](https://github.com/mutirolabs/openclaw-brain) source.
|
|
68
22
|
|
|
69
|
-
|
|
70
|
-
passing one skips the wizard and falls through to the non-interactive adapter):
|
|
23
|
+
Add the channel:
|
|
71
24
|
|
|
72
25
|
```bash
|
|
73
26
|
openclaw channels add
|
|
74
27
|
```
|
|
75
28
|
|
|
76
|
-
Pick `mutiro` from the list. The wizard
|
|
29
|
+
Pick `mutiro` from the list. The setup wizard detects the Mutiro CLI, validates your agent directory, and confirms you are authenticated.
|
|
77
30
|
|
|
78
|
-
|
|
79
|
-
- ask for your Mutiro agent directory (the folder containing `.mutiro-agent.yaml`)
|
|
80
|
-
- validate the directory and run `mutiro auth whoami`
|
|
81
|
-
- remind you to stop the built-in Mutiro brain before starting the gateway
|
|
82
|
-
|
|
83
|
-
Or set the config manually:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
openclaw config set channels.mutiro.accounts.default.agentDir /path/to/agent-directory
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### 3. Run the OpenClaw gateway
|
|
31
|
+
Start the gateway:
|
|
90
32
|
|
|
91
33
|
```bash
|
|
92
34
|
openclaw gateway run
|
|
93
35
|
```
|
|
94
36
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
./run-brain.sh /path/to/agent-directory
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### 4. Talk to your agent
|
|
102
|
-
|
|
103
|
-
Once the gateway is running, your agent is reachable from any Mutiro surface:
|
|
37
|
+
Your agent is now live on every Mutiro surface — Web, Desktop, Mobile, and CLI.
|
|
104
38
|
|
|
105
|
-
|
|
106
|
-
- **CLI chat:** `mutiro chat`
|
|
107
|
-
- **Mobile:** Mutiro app on iOS / Android
|
|
108
|
-
- **Desktop:** Mutiro desktop app on macOS / Windows / Linux
|
|
109
|
-
|
|
110
|
-
For a quick shell smoke test:
|
|
39
|
+
Send a smoke-test message:
|
|
111
40
|
|
|
112
41
|
```bash
|
|
113
42
|
mutiro user message send <agent-username> "Hello! Who are you?"
|
|
114
43
|
```
|
|
115
44
|
|
|
116
|
-
|
|
45
|
+
## Enable Mutiro-native tools
|
|
117
46
|
|
|
118
|
-
|
|
119
|
-
messages through Mutiro, add `mutiro*` to your agent's `tools.alsoAllow`:
|
|
47
|
+
Let your OpenClaw agent send voice messages, interactive cards, and forward messages through Mutiro by allowing the `mutiro*` tools:
|
|
120
48
|
|
|
121
|
-
```
|
|
122
|
-
tools
|
|
123
|
-
profile: messaging
|
|
124
|
-
alsoAllow:
|
|
125
|
-
- "mutiro*"
|
|
49
|
+
```bash
|
|
50
|
+
openclaw config set tools.alsoAllow '["mutiro*"]'
|
|
126
51
|
```
|
|
127
52
|
|
|
128
|
-
|
|
129
|
-
|
|
53
|
+
If you already curate `tools.alsoAllow`, merge `"mutiro*"` into your existing list instead of overwriting — the command above replaces the array.
|
|
54
|
+
|
|
55
|
+
## Access control, enforced at the edge
|
|
130
56
|
|
|
131
|
-
|
|
57
|
+
Mutiro runs the allowlist on its servers — not in your agent. Denied users are rejected before their messages reach OpenClaw, so agent-side bugs can never leak access to someone who shouldn't have it. This is a stronger posture than in-agent filtering and a real differentiator over generic bot channels.
|
|
132
58
|
|
|
133
|
-
|
|
134
|
-
`allowFrom`. Denied users are blocked at the Mutiro server — their messages
|
|
135
|
-
never reach OpenClaw at all. Manage it with the `mutiro` CLI:
|
|
59
|
+
One extra CLI step buys you that posture:
|
|
136
60
|
|
|
137
61
|
```bash
|
|
138
62
|
mutiro agents allowlist get <agent-username>
|
|
@@ -140,134 +64,37 @@ mutiro agents allow <agent-username> <username>
|
|
|
140
64
|
mutiro agents deny <agent-username> <username>
|
|
141
65
|
```
|
|
142
66
|
|
|
143
|
-
|
|
144
|
-
the full command reference and a paste-into-AI prompt you can hand to your
|
|
145
|
-
assistant when you want help managing sharing and security posture.
|
|
146
|
-
|
|
147
|
-
## What This Repo Is
|
|
148
|
-
|
|
149
|
-
A small reference package showing how to plug OpenClaw into Mutiro `chatbridge` as a channel plugin. Pi is a good reference for swapping Mutiro's brain with a standalone runtime; this one shows the same shape routed through OpenClaw's channel plugin contract.
|
|
150
|
-
|
|
151
|
-
- `mutiro agent host --mode=bridge` is spawned by the plugin, one process per configured Mutiro agent
|
|
152
|
-
- NDJSON envelope traffic is translated into OpenClaw inbound messages and outbound send/react/forward actions
|
|
153
|
-
- one subprocess per Mutiro agent, long-lived across conversations
|
|
154
|
-
- all outbound chat actions go back through the bridge
|
|
67
|
+
As adoption grows, we may expose the allowlist directly through the OpenClaw channel. For now it stays behind the `mutiro` CLI — a deliberate boundary that keeps access control outside the agent sandbox.
|
|
155
68
|
|
|
156
|
-
##
|
|
69
|
+
## FAQ
|
|
157
70
|
|
|
158
|
-
|
|
159
|
-
- `src/bridge-protocol.ts` — NDJSON envelope constants and `@type` URLs
|
|
160
|
-
- `src/bridge-messages.ts` — normalized message extraction and observed-turn assembly
|
|
161
|
-
- `src/bridge-client.ts` — NDJSON envelope codec plus host subprocess spawn
|
|
162
|
-
- `src/bridge-session.ts` — per-conversation observed/task/snapshot handlers
|
|
163
|
-
- `src/inbound.ts` — bridge observed message → OpenClaw inbound envelope
|
|
164
|
-
- `src/outbound.ts` — OpenClaw outbound adapter → `message.send` / `message.react` / `message.forward`
|
|
165
|
-
- `src/channel.ts` — Mutiro channel plugin definition
|
|
166
|
-
- `src/channel.runtime.ts` — runtime barrel consumed by the plugin entry
|
|
167
|
-
- `src/setup-surface.ts` — setup wizard driven by `openclaw channels add` (pick `mutiro` from the list)
|
|
168
|
-
- `src/agent-tools.ts` — `mutiro_send_voice_message`, `mutiro_send_card`, `mutiro_forward_message`
|
|
169
|
-
- `src/signal-forwarder.ts` — OpenClaw tool events → Mutiro `signal.emit` (26-entry map)
|
|
170
|
-
- `src/live-snapshot.ts` — `session.snapshot` + `task.request` handlers for live call handoff
|
|
171
|
-
- `openclaw.plugin.json` — channel manifest
|
|
172
|
-
- `run-brain.sh` — convenience launcher that boots OpenClaw's gateway against a Mutiro agent directory
|
|
173
|
-
- `docs/guides/use-openclaw-as-brain.md` — end-to-end setup guide
|
|
174
|
-
- `docs/guides/manage-allowlist.md` — paste-into-AI guide for Mutiro's server-side allowlist
|
|
71
|
+
**How do I show the OpenClaw badge on my agent?**
|
|
175
72
|
|
|
176
|
-
|
|
73
|
+
Pass `--badge lobster` when creating the agent so every Mutiro client renders the lobster next to the avatar:
|
|
177
74
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
1. Spawn `mutiro agent host --mode=bridge` from inside an OpenClaw channel plugin
|
|
183
|
-
2. Complete `ready → session.initialize → subscription.set`
|
|
184
|
-
3. Receive `message.observed` and turn it into an OpenClaw inbound envelope
|
|
185
|
-
4. Route OpenClaw outbound replies through bridge-local commands (`message.send`, `message.react`, `message.forward`, `media.upload`, `signal.emit`, `recall.search/get`)
|
|
186
|
-
5. Finish turns with `turn.end`
|
|
187
|
-
|
|
188
|
-
## Important Bridge Notes
|
|
189
|
-
|
|
190
|
-
- `message.send` is a bridge-local command, not a raw backend `SendToConversationRequest`
|
|
191
|
-
- the portable payload type is `mutiro.chatbridge.ChatBridgeSendMessageCommand`
|
|
192
|
-
- `message.send_voice` is also bridge-local and keeps TTS inside the host
|
|
193
|
-
- this reference usually replies by `conversation_id`
|
|
194
|
-
- the bridge also supports `to_username` for direct sends
|
|
195
|
-
|
|
196
|
-
## Adapter Model
|
|
197
|
-
|
|
198
|
-
The plugin process is an OpenClaw channel. It:
|
|
199
|
-
|
|
200
|
-
- spawns `mutiro agent host --mode=bridge` (one per configured Mutiro agent directory)
|
|
201
|
-
- reads and writes bridge envelopes on stdio
|
|
202
|
-
- delivers `message.observed` payloads as OpenClaw inbound messages
|
|
203
|
-
- exposes outbound send/react/forward through the standard OpenClaw `ChannelOutboundAdapter`
|
|
204
|
-
|
|
205
|
-
OpenClaw's agent runtime owns the brain layer. The plugin does not talk to Mutiro SDKs directly; everything portable flows through the chatbridge envelope.
|
|
206
|
-
|
|
207
|
-
## Supported Bridge Operations
|
|
208
|
-
|
|
209
|
-
This adapter exercises:
|
|
210
|
-
|
|
211
|
-
- `message.send`
|
|
212
|
-
- `message.send_voice`
|
|
213
|
-
- `message.react`
|
|
214
|
-
- `message.forward`
|
|
215
|
-
- `media.upload`
|
|
216
|
-
- `signal.emit`
|
|
217
|
-
- `turn.end`
|
|
218
|
-
- `recall.search`
|
|
219
|
-
- `recall.get`
|
|
220
|
-
|
|
221
|
-
## Session Model
|
|
222
|
-
|
|
223
|
-
- one Mutiro `conversation_id` maps to one OpenClaw conversation binding
|
|
224
|
-
- later turns in the same conversation reuse the same OpenClaw session, just as pi-brain reuses a Pi session
|
|
225
|
-
- `session.snapshot` is answered from recent messages cached per-conversation in the plugin
|
|
226
|
-
|
|
227
|
-
OpenClaw already owns transcript continuity across turns, so the plugin keeps its own cache narrow — just enough to answer `session.snapshot` for bridge consumers.
|
|
228
|
-
|
|
229
|
-
## Handshake
|
|
230
|
-
|
|
231
|
-
Startup flow:
|
|
232
|
-
|
|
233
|
-
1. host sends `ready`
|
|
234
|
-
2. plugin sends `session.initialize`
|
|
235
|
-
3. plugin sends `subscription.set`
|
|
236
|
-
4. host starts delivering `message.observed`
|
|
237
|
-
|
|
238
|
-
Per turn:
|
|
239
|
-
|
|
240
|
-
1. plugin acknowledges `message.observed`
|
|
241
|
-
2. plugin dispatches the observed envelope into OpenClaw's inbound pipeline
|
|
242
|
-
3. OpenClaw's reply-dispatch drives zero or more outbound bridge operations
|
|
243
|
-
4. plugin sends `turn.end`
|
|
244
|
-
|
|
245
|
-
## Debugging
|
|
246
|
-
|
|
247
|
-
Useful signals while integrating:
|
|
248
|
-
|
|
249
|
-
- `Handshake failed`
|
|
250
|
-
Bridge startup or negotiation problem.
|
|
251
|
-
- `Host error`
|
|
252
|
-
A bridge request failed outside a pending request path.
|
|
253
|
-
- `outbound bridge call failed`
|
|
254
|
-
The plugin reached the bridge and got a real host-side error.
|
|
75
|
+
```bash
|
|
76
|
+
mutiro agents create <username> "<Display>" --engine genie --badge lobster
|
|
77
|
+
```
|
|
255
78
|
|
|
256
|
-
|
|
79
|
+
For an agent that already exists, flip the badge on with:
|
|
257
80
|
|
|
258
81
|
```bash
|
|
259
|
-
|
|
82
|
+
mutiro agents update-profile <agent-username> --badge lobster
|
|
260
83
|
```
|
|
261
84
|
|
|
262
|
-
|
|
85
|
+
**I don't have a Mutiro agent yet — what's the fastest way to create one?**
|
|
86
|
+
|
|
87
|
+
Paste this prompt into your AI assistant (Claude, Cursor, Windsurf, …):
|
|
88
|
+
|
|
89
|
+
> Read https://mutiro.com/docs/guides/create-agent and help me create a Mutiro agent step by step. Use `--badge lobster` on `mutiro agents create` so the agent shows the OpenClaw badge.
|
|
263
90
|
|
|
264
|
-
|
|
91
|
+
Or follow the [Mutiro create-agent guide](https://www.mutiro.com/docs/guides/create-agent) by hand.
|
|
265
92
|
|
|
266
|
-
|
|
93
|
+
## Resources
|
|
267
94
|
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
-
|
|
95
|
+
- [Use OpenClaw as brain](./docs/guides/use-openclaw-as-brain.md)
|
|
96
|
+
- [Manage the Mutiro allowlist](./docs/guides/manage-allowlist.md)
|
|
97
|
+
- [Mutiro manual](https://mutiro.com/docs/manual)
|
|
98
|
+
- [Mutiro CLI reference](https://mutiro.com/docs/cli)
|
|
99
|
+
- [OpenClaw documentation](https://openclaw.ai)
|
|
100
|
+
- Sibling repo: [`pi-brain`](https://github.com/mutirolabs/pi-brain) — the Pi equivalent, a standalone bridge rather than an OpenClaw extension
|
|
Binary file
|
|
@@ -15,9 +15,9 @@ Copy the prompt below into your AI assistant (Claude, Cursor, Windsurf, or simil
|
|
|
15
15
|
|
|
16
16
|
````
|
|
17
17
|
You are helping me manage who can message my Mutiro agent. My agent is driven
|
|
18
|
-
by OpenClaw
|
|
19
|
-
allowlist is the authoritative gate — denied users are
|
|
20
|
-
The OpenClaw allowFrom is a second filter on top.
|
|
18
|
+
by OpenClaw via the Mutiro Channel extension, so there are two allowlists.
|
|
19
|
+
The Mutiro backend allowlist is the authoritative gate — denied users are
|
|
20
|
+
blocked at the server. The OpenClaw `allowFrom` is a second filter on top.
|
|
21
21
|
|
|
22
22
|
Be proactive — inspect current state before changing anything, and confirm
|
|
23
23
|
destructive changes (especially `set` calls that replace the whole list).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Use OpenClaw as the Brain for Your Mutiro Agent
|
|
2
2
|
|
|
3
|
-
Copy the prompt below into your AI assistant (Claude, Cursor, Windsurf, or similar) and it will walk you through pointing [OpenClaw](https://openclaw.ai) at a [Mutiro](https://mutiro.com) agent
|
|
3
|
+
Copy the prompt below into your AI assistant (Claude, Cursor, Windsurf, or similar) and it will walk you through pointing [OpenClaw](https://openclaw.ai) at a [Mutiro](https://mutiro.com) agent via the Mutiro Channel extension. Mutiro stays the messaging platform; OpenClaw becomes the brain.
|
|
4
4
|
|
|
5
5
|
## The Prompt
|
|
6
6
|
|
|
7
7
|
````
|
|
8
|
-
You are helping me run an existing Mutiro agent with OpenClaw as its brain
|
|
8
|
+
You are helping me run an existing Mutiro agent with OpenClaw as its brain, connected through the Mutiro Channel extension for OpenClaw. Mutiro keeps the agent identity, connectivity, messaging, auth, and media plumbing. OpenClaw becomes the thinking layer and drives outbound replies through the Mutiro Channel.
|
|
9
9
|
|
|
10
10
|
Walk me through this step by step. Be proactive — run commands, check outputs, and make smart decisions based on what you find. Don't ask me things you can figure out by running a command. Only pause to ask when you genuinely need my input (like which LLM provider to use or what personality I want). When you need my input, ask me directly and wait for my response.
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ Walk me through this step by step. Be proactive — run commands, check outputs,
|
|
|
17
17
|
|
|
18
18
|
This guide assumes I already have a working Mutiro agent directory. If I don't, stop and point me at the Mutiro create-agent guide first:
|
|
19
19
|
|
|
20
|
-
> https://www.mutiro.com/docs/guides/create-agent
|
|
20
|
+
> https://www.mutiro.com/docs/guides/create-agent
|
|
21
21
|
|
|
22
22
|
Check what's already set up:
|
|
23
23
|
|
|
@@ -84,9 +84,9 @@ openclaw doctor
|
|
|
84
84
|
|
|
85
85
|
---
|
|
86
86
|
|
|
87
|
-
### Step 4: Install the
|
|
87
|
+
### Step 4: Install the Mutiro Channel extension
|
|
88
88
|
|
|
89
|
-
This
|
|
89
|
+
This extension is the piece that lets OpenClaw drive a Mutiro agent. It spawns `mutiro agent host --mode=bridge` as a subprocess and translates the Mutiro Channel protocol into OpenClaw inbound messages and outbound send/react/forward/voice/card calls.
|
|
90
90
|
|
|
91
91
|
Install from the published package:
|
|
92
92
|
|
|
@@ -105,15 +105,15 @@ cd ~/src/openclaw-brain
|
|
|
105
105
|
openclaw plugins install --dangerously-force-unsafe-install "file:$(pwd)"
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
**About `--dangerously-force-unsafe-install`**: this
|
|
109
|
-
spawns `mutiro agent host --mode=bridge` as a subprocess — that is the
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
explicit acknowledgement. Before you pass it, confirm you are
|
|
113
|
-
from the signed [`mutirolabs/openclaw-brain`](https://github.com/mutirolabs/openclaw-brain)
|
|
108
|
+
**About `--dangerously-force-unsafe-install`**: this extension legitimately
|
|
109
|
+
spawns `mutiro agent host --mode=bridge` as a subprocess — that is how the
|
|
110
|
+
Mutiro Channel carries traffic. OpenClaw's install scanner correctly flags
|
|
111
|
+
any extension that uses `child_process` as sensitive and requires this flag
|
|
112
|
+
as an explicit acknowledgement. Before you pass it, confirm you are
|
|
113
|
+
installing from the signed [`mutirolabs/openclaw-brain`](https://github.com/mutirolabs/openclaw-brain)
|
|
114
114
|
source (or the `@mutirolabs/openclaw-brain` npm package). Review the
|
|
115
115
|
`spawn` call at [`src/bridge-client.ts`](https://github.com/mutirolabs/openclaw-brain/blob/main/src/bridge-client.ts)
|
|
116
|
-
if you want to see exactly what the
|
|
116
|
+
if you want to see exactly what the extension executes.
|
|
117
117
|
|
|
118
118
|
Verify OpenClaw sees the channel:
|
|
119
119
|
|
|
@@ -320,9 +320,9 @@ Restart with `openclaw gateway run` after any config change.
|
|
|
320
320
|
|
|
321
321
|
### Step 12: Signals and Live Call Handoff
|
|
322
322
|
|
|
323
|
-
OpenClaw's tool activity is forwarded to Mutiro as
|
|
323
|
+
OpenClaw's tool activity is forwarded to Mutiro as channel signals, so Mutiro surfaces "thinking", "web searching", "recalling", "sending voice", etc. in real time while the agent works. The extension maps 26 OpenClaw tool names to Mutiro `SignalType` enums; anything outside the map falls back to `SIGNAL_TYPE_CUSTOM` with a detail label.
|
|
324
324
|
|
|
325
|
-
For live voice calls, Mutiro sends `task.request` with a compact observed-turn payload and expects a plain-text result. The
|
|
325
|
+
For live voice calls, Mutiro sends `task.request` with a compact observed-turn payload and expects a plain-text result. The extension accumulates the agent's reply text and returns it as the task result. It also answers `session.snapshot` from recent messages cached per-conversation so Mutiro can bootstrap the live lane.
|
|
326
326
|
|
|
327
327
|
Voice call **summaries** flow as normal `message.observed` envelopes tagged `live_call`. OpenClaw treats them as regular inbound turns — nothing extra to configure.
|
|
328
328
|
|
|
@@ -396,6 +396,6 @@ Help me review OpenClaw's `tools.profile` / `tools.alsoAllow` and the Mutiro `al
|
|
|
396
396
|
**Docs:**
|
|
397
397
|
- OpenClaw: https://openclaw.ai
|
|
398
398
|
- Mutiro: https://mutiro.com
|
|
399
|
-
- Create a Mutiro agent: https://www.mutiro.com/docs/guides/create-agent
|
|
399
|
+
- Create a Mutiro agent: https://www.mutiro.com/docs/guides/create-agent
|
|
400
400
|
- openclaw-brain repo: https://github.com/mutirolabs/openclaw-brain
|
|
401
401
|
````
|
package/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-con
|
|
|
12
12
|
export default defineBundledChannelEntry({
|
|
13
13
|
id: "mutiro",
|
|
14
14
|
name: "Mutiro",
|
|
15
|
-
description: "Mutiro
|
|
15
|
+
description: "The official Mutiro Channel extension for OpenClaw.",
|
|
16
16
|
importMetaUrl: import.meta.url,
|
|
17
17
|
plugin: {
|
|
18
18
|
specifier: "./src/channel.js",
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "mutiro",
|
|
3
3
|
"channels": ["mutiro"],
|
|
4
|
-
"channelEnvVars": {
|
|
5
|
-
"mutiro": ["MUTIRO_AGENT_API_KEY"]
|
|
6
|
-
},
|
|
7
4
|
"configSchema": {
|
|
8
5
|
"type": "object",
|
|
9
6
|
"additionalProperties": false,
|
|
@@ -24,6 +21,11 @@
|
|
|
24
21
|
"enabled": {
|
|
25
22
|
"type": "boolean"
|
|
26
23
|
},
|
|
24
|
+
"replyToMode": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"enum": ["off", "first", "all", "batched"],
|
|
27
|
+
"description": "How the agent's outbound messages thread under the inbound one. Default: \"first\" (quote the inbound on the first agent reply, then free-standing follow-ups)."
|
|
28
|
+
},
|
|
27
29
|
"accounts": {
|
|
28
30
|
"type": "object",
|
|
29
31
|
"additionalProperties": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutirolabs/openclaw-brain",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The official Mutiro Channel extension for OpenClaw. OpenClaw is the brain; Mutiro is the messaging surface, identity, and state.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"repository": {
|
|
@@ -22,10 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"mutiro",
|
|
25
|
-
"chatbridge",
|
|
26
25
|
"openclaw",
|
|
27
26
|
"channel",
|
|
28
|
-
"
|
|
27
|
+
"extension",
|
|
29
28
|
"agent"
|
|
30
29
|
],
|
|
31
30
|
"files": [
|
|
@@ -63,10 +62,10 @@
|
|
|
63
62
|
"channel": {
|
|
64
63
|
"id": "mutiro",
|
|
65
64
|
"label": "Mutiro",
|
|
66
|
-
"selectionLabel": "Mutiro
|
|
65
|
+
"selectionLabel": "Mutiro",
|
|
67
66
|
"docsPath": "/channels/mutiro",
|
|
68
67
|
"docsLabel": "mutiro",
|
|
69
|
-
"blurb": "
|
|
68
|
+
"blurb": "Official Mutiro Channel for OpenClaw. Point at a Mutiro agent directory to enable.",
|
|
70
69
|
"order": 80,
|
|
71
70
|
"quickstartAllowFrom": true
|
|
72
71
|
},
|
package/src/agent-tools.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Channel-owned agent tools. Exposed to OpenClaw's agent loop via
|
|
2
|
-
// `ChannelPlugin.agentTools`. The first one
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// `ChannelPlugin.agentTools`. The first one is a text-to-speech voice
|
|
3
|
+
// message delivered through the bridge's `message.send_voice` command
|
|
4
|
+
// (host-side TTS, not client-side).
|
|
5
5
|
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import type { ChannelAgentTool } from "openclaw/plugin-sdk/channel-contract";
|
package/src/bridge-client.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// NDJSON envelope codec + subprocess manager for the Mutiro chatbridge.
|
|
2
|
-
//
|
|
3
|
-
// transport-shaped so the rest of the plugin can treat the bridge as a
|
|
2
|
+
// Kept transport-shaped so the rest of the plugin can treat the bridge as a
|
|
4
3
|
// request/response channel regardless of which brain is on the other side.
|
|
5
4
|
|
|
6
5
|
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
package/src/bridge-messages.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Message normalization helpers
|
|
2
|
-
//
|
|
1
|
+
// Message normalization helpers for inbound bridge envelopes. The host
|
|
2
|
+
// delivers `envelope.payload.message` as a pre-normalized bag of parts;
|
|
3
3
|
// these helpers turn that into plain text for the brain and into structured
|
|
4
4
|
// ObservedTurn records for downstream dispatch.
|
|
5
5
|
|
package/src/bridge-protocol.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// NDJSON protocol constants used by the Mutiro chatbridge envelope.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// These type URLs and helpers mirror the Mutiro bridge's protobuf surface
|
|
3
|
+
// envelope-for-envelope.
|
|
4
4
|
|
|
5
5
|
export const PROTOCOL_VERSION = "mutiro.agent.bridge.v1";
|
|
6
6
|
|
package/src/bridge-session.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Long-lived bridge session: owns the subprocess, performs the handshake,
|
|
2
2
|
// dispatches inbound envelopes, and keeps a narrow per-conversation cache for
|
|
3
|
-
// `session.snapshot`.
|
|
4
|
-
//
|
|
3
|
+
// `session.snapshot`. Structured so the OpenClaw plugin runtime can
|
|
4
|
+
// start/stop one session per configured Mutiro account.
|
|
5
5
|
|
|
6
6
|
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
7
7
|
|
package/src/channel.runtime.ts
CHANGED
|
@@ -30,6 +30,16 @@ type StartContext = ChannelGatewayContext<ResolvedMutiroAccount>;
|
|
|
30
30
|
|
|
31
31
|
const sessions = new Map<string, BridgeSession>();
|
|
32
32
|
|
|
33
|
+
// Crash-backoff state per account. Repeated crashes escalate the delay so we
|
|
34
|
+
// don't thrash the gateway's restart loop when the host is consistently
|
|
35
|
+
// failing (bad config, missing credential, host crash on boot, etc.). The
|
|
36
|
+
// streak resets when the last crash is older than `CRASH_STREAK_RESET_MS`,
|
|
37
|
+
// so a host that ran healthy for a while then crashed once starts over at
|
|
38
|
+
// the shortest backoff.
|
|
39
|
+
const CRASH_BACKOFF_MS = [1_000, 2_000, 5_000, 15_000, 60_000];
|
|
40
|
+
const CRASH_STREAK_RESET_MS = 5 * 60_000;
|
|
41
|
+
const crashState = new Map<string, { count: number; lastCrashAt: number }>();
|
|
42
|
+
|
|
33
43
|
const sessionKey = (channel: string, accountId: string) => `${channel}:${accountId}`;
|
|
34
44
|
|
|
35
45
|
const requireSessionForAccount = (accountId: string | null | undefined): BridgeSession => {
|
|
@@ -299,14 +309,55 @@ export const startMutiroAccount = async (ctx: StartContext) => {
|
|
|
299
309
|
: undefined,
|
|
300
310
|
onHostExit: (code) => {
|
|
301
311
|
sessions.delete(key);
|
|
302
|
-
|
|
312
|
+
const now = Date.now();
|
|
313
|
+
const isAbort = ctx.abortSignal.aborted;
|
|
314
|
+
const isCleanExit = code === 0 || isAbort;
|
|
315
|
+
|
|
316
|
+
if (isCleanExit) {
|
|
317
|
+
crashState.delete(ctx.accountId);
|
|
318
|
+
ctx.log?.info?.(
|
|
319
|
+
`mutiro: host (${ctx.accountId}) exited with code ${code}${isAbort ? " (abort)" : ""}`,
|
|
320
|
+
);
|
|
321
|
+
ctx.setStatus({
|
|
322
|
+
...ctx.getStatus(),
|
|
323
|
+
running: false,
|
|
324
|
+
connected: false,
|
|
325
|
+
lastDisconnect: { at: now, status: code ?? undefined },
|
|
326
|
+
});
|
|
327
|
+
settleLifecycle();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Unexpected exit: track the streak, compute backoff, and hold the
|
|
332
|
+
// lifecycle promise until the delay elapses. The gateway's restart
|
|
333
|
+
// loop won't fire until we settle, so this delay is the effective
|
|
334
|
+
// backoff without touching gateway internals.
|
|
335
|
+
const prior = crashState.get(ctx.accountId);
|
|
336
|
+
const streak =
|
|
337
|
+
prior && now - prior.lastCrashAt < CRASH_STREAK_RESET_MS ? prior.count + 1 : 1;
|
|
338
|
+
crashState.set(ctx.accountId, { count: streak, lastCrashAt: now });
|
|
339
|
+
|
|
340
|
+
const delayMs = CRASH_BACKOFF_MS[Math.min(streak - 1, CRASH_BACKOFF_MS.length - 1)];
|
|
341
|
+
ctx.log?.warn?.(
|
|
342
|
+
`mutiro: host (${ctx.accountId}) exited unexpectedly with code ${code}; ` +
|
|
343
|
+
`restarting in ${Math.round(delayMs / 1000)}s (attempt ${streak})`,
|
|
344
|
+
);
|
|
303
345
|
ctx.setStatus({
|
|
304
346
|
...ctx.getStatus(),
|
|
305
347
|
running: false,
|
|
306
348
|
connected: false,
|
|
307
|
-
|
|
349
|
+
restartPending: true,
|
|
350
|
+
reconnectAttempts: streak,
|
|
351
|
+
lastDisconnect: {
|
|
352
|
+
at: now,
|
|
353
|
+
status: code ?? undefined,
|
|
354
|
+
error: `exit_code=${code ?? "null"}`,
|
|
355
|
+
},
|
|
308
356
|
});
|
|
309
|
-
|
|
357
|
+
|
|
358
|
+
setTimeout(() => {
|
|
359
|
+
settleLifecycle();
|
|
360
|
+
}, delayMs);
|
|
310
361
|
},
|
|
311
362
|
});
|
|
312
363
|
|
package/src/channel.ts
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
import type {
|
|
9
9
|
ChannelOutboundAdapter,
|
|
10
10
|
ChannelPlugin,
|
|
11
|
+
OpenClawConfig,
|
|
11
12
|
} from "openclaw/plugin-sdk/core";
|
|
12
13
|
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
13
14
|
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
|
14
15
|
|
|
16
|
+
type ReplyToMode = "off" | "first" | "all" | "batched";
|
|
17
|
+
|
|
15
18
|
import { mutiroMessageActions } from "./actions.js";
|
|
16
19
|
import { mutiroAgentTools } from "./agent-tools.js";
|
|
17
20
|
import { mutiroConfigAdapter, type ResolvedMutiroAccount } from "./config.js";
|
|
@@ -43,6 +46,23 @@ const outbound: ChannelOutboundAdapter = {
|
|
|
43
46
|
},
|
|
44
47
|
};
|
|
45
48
|
|
|
49
|
+
// Read `channels.mutiro.replyToMode` as an override; otherwise default to
|
|
50
|
+
// `"first"` so the agent's first reply in a turn threads under the inbound
|
|
51
|
+
// message. Mutiro clients render reply-to as a visible quoted pill, so this
|
|
52
|
+
// anchors context nicely in groups without being noisy in DMs. Set
|
|
53
|
+
// `channels.mutiro.replyToMode` to `"off"`, `"all"`, or `"batched"` in the
|
|
54
|
+
// OpenClaw config to override.
|
|
55
|
+
const resolveMutiroReplyToMode = ({ cfg }: { cfg: OpenClawConfig }): ReplyToMode => {
|
|
56
|
+
const section = (cfg as { channels?: Record<string, unknown> }).channels?.mutiro as
|
|
57
|
+
| { replyToMode?: unknown }
|
|
58
|
+
| undefined;
|
|
59
|
+
const configured = section?.replyToMode;
|
|
60
|
+
if (configured === "off" || configured === "first" || configured === "all" || configured === "batched") {
|
|
61
|
+
return configured;
|
|
62
|
+
}
|
|
63
|
+
return "first";
|
|
64
|
+
};
|
|
65
|
+
|
|
46
66
|
export const mutiroPlugin: ChannelPlugin<ResolvedMutiroAccount> = createChatChannelPlugin<
|
|
47
67
|
ResolvedMutiroAccount
|
|
48
68
|
>({
|
|
@@ -51,10 +71,10 @@ export const mutiroPlugin: ChannelPlugin<ResolvedMutiroAccount> = createChatChan
|
|
|
51
71
|
meta: {
|
|
52
72
|
id: "mutiro",
|
|
53
73
|
label: "Mutiro",
|
|
54
|
-
selectionLabel: "Mutiro
|
|
74
|
+
selectionLabel: "Mutiro",
|
|
55
75
|
docsPath: "/channels/mutiro",
|
|
56
76
|
docsLabel: "mutiro",
|
|
57
|
-
blurb: "
|
|
77
|
+
blurb: "Official Mutiro Channel for OpenClaw. Point at a Mutiro agent directory to enable.",
|
|
58
78
|
order: 80,
|
|
59
79
|
quickstartAllowFrom: true,
|
|
60
80
|
markdownCapable: true,
|
|
@@ -125,6 +145,45 @@ export const mutiroPlugin: ChannelPlugin<ResolvedMutiroAccount> = createChatChan
|
|
|
125
145
|
await runtime.stopMutiroAccount(ctx);
|
|
126
146
|
},
|
|
127
147
|
},
|
|
148
|
+
|
|
149
|
+
// Status adapter: answers `openclaw channels status mutiro`. The runtime
|
|
150
|
+
// already updates `running` / `connected` / `lastConnectedAt` /
|
|
151
|
+
// `reconnectAttempts` via `ctx.setStatus()` when the bridge subprocess
|
|
152
|
+
// starts, handshakes, exits, or is in backoff. Here we just enrich the
|
|
153
|
+
// snapshot with Mutiro-specific context (agent workspace path, bridge
|
|
154
|
+
// mode, derived health string).
|
|
155
|
+
status: {
|
|
156
|
+
buildAccountSnapshot: ({ account, runtime }) => {
|
|
157
|
+
const base = runtime ?? { accountId: account.accountId };
|
|
158
|
+
const running = base.running ?? false;
|
|
159
|
+
const connected = base.connected ?? false;
|
|
160
|
+
const restartPending = base.restartPending ?? false;
|
|
161
|
+
const healthState = !running
|
|
162
|
+
? restartPending
|
|
163
|
+
? "restarting"
|
|
164
|
+
: "stopped"
|
|
165
|
+
: connected
|
|
166
|
+
? "healthy"
|
|
167
|
+
: "connecting";
|
|
168
|
+
return {
|
|
169
|
+
...base,
|
|
170
|
+
accountId: account.accountId,
|
|
171
|
+
configured: account.configured,
|
|
172
|
+
enabled: account.enabled,
|
|
173
|
+
mode: "bridge",
|
|
174
|
+
healthState,
|
|
175
|
+
dbPath: account.config.agentDir ?? null,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
// Threading adapter: Mutiro natively supports `reply_to_message_id`, so wire
|
|
181
|
+
// OpenClaw's reply-dispatch into it. `allowExplicitReplyTagsWhenOff` keeps
|
|
182
|
+
// agent-directed reply markers working even when the user has disabled
|
|
183
|
+
// automatic reply-threading.
|
|
184
|
+
threading: {
|
|
185
|
+
resolveReplyToMode: resolveMutiroReplyToMode,
|
|
186
|
+
allowExplicitReplyTagsWhenOff: true,
|
|
128
187
|
},
|
|
129
188
|
outbound,
|
|
130
189
|
});
|
package/src/outbound.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Outbound adapter that translates OpenClaw reply-dispatch calls into
|
|
2
|
-
// bridge-local commands
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// ChannelOutboundAdapter is the consumer instead of a Pi tool runtime.
|
|
2
|
+
// bridge-local commands: send_message, send_voice_message, send_card,
|
|
3
|
+
// react_to_message, send_file_message, forward_message, recall,
|
|
4
|
+
// recall_get. Shaped around OpenClaw's ChannelOutboundAdapter contract.
|
|
6
5
|
|
|
7
6
|
import * as path from "node:path";
|
|
8
7
|
|
|
@@ -112,9 +111,8 @@ const buildCardJson = (
|
|
|
112
111
|
|
|
113
112
|
return {
|
|
114
113
|
// Field names must match Mutiro's CardPart protobuf schema (see
|
|
115
|
-
// spec/protobuf/shared/messaging.proto).
|
|
116
|
-
//
|
|
117
|
-
// decoder rejects as unknown fields.
|
|
114
|
+
// spec/protobuf/shared/messaging.proto). The host's strict JSON-to-proto
|
|
115
|
+
// decoder rejects unknown field names like `json_data` / `version`.
|
|
118
116
|
a2ui_json: lines.join("\n"),
|
|
119
117
|
schema_version: "0.8",
|
|
120
118
|
card_id: cardId || `openclaw-card-${Math.random().toString(36).slice(2, 10)}`,
|
package/src/setup-surface.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { listMutiroAccountIds, resolveMutiroAccount } from "./config.js";
|
|
|
26
26
|
|
|
27
27
|
const channel = "mutiro" as const;
|
|
28
28
|
const INSTALL_URL = "https://mutiro.com/downloads/install.sh";
|
|
29
|
-
const CREATE_AGENT_GUIDE = "https://www.mutiro.com/docs/guides/create-agent
|
|
29
|
+
const CREATE_AGENT_GUIDE = "https://www.mutiro.com/docs/guides/create-agent";
|
|
30
30
|
|
|
31
31
|
const MUTIRO_INTRO_LINES = [
|
|
32
32
|
"Point OpenClaw at an existing Mutiro agent directory.",
|
|
@@ -167,7 +167,7 @@ export const mutiroSetupWizard: ChannelSetupWizard = {
|
|
|
167
167
|
resolveExtraStatusLines: ({ cfg }) => [`Accounts: ${listMutiroAccountIds(cfg).length || 0}`],
|
|
168
168
|
}),
|
|
169
169
|
introNote: {
|
|
170
|
-
title: "Mutiro
|
|
170
|
+
title: "Mutiro Channel setup",
|
|
171
171
|
lines: MUTIRO_INTRO_LINES,
|
|
172
172
|
},
|
|
173
173
|
prepare: async ({ prompter }) => {
|
|
@@ -225,25 +225,53 @@ export const mutiroSetupWizard: ChannelSetupWizard = {
|
|
|
225
225
|
const dir = resolveMutiroAccount(cfg, accountId).config.agentDir;
|
|
226
226
|
if (!dir) return undefined;
|
|
227
227
|
|
|
228
|
+
const issues: string[] = [];
|
|
229
|
+
|
|
228
230
|
const whoami = await runPluginCommandWithTimeout({
|
|
229
231
|
argv: ["mutiro", "auth", "whoami"],
|
|
230
232
|
timeoutMs: 5_000,
|
|
231
233
|
cwd: dir,
|
|
232
234
|
});
|
|
233
|
-
|
|
234
235
|
if (whoami.code !== 0) {
|
|
235
|
-
|
|
236
|
+
issues.push(
|
|
236
237
|
[
|
|
237
|
-
"
|
|
238
|
-
"",
|
|
239
|
-
"Finish Mutiro-side setup before starting the gateway:",
|
|
238
|
+
"Mutiro auth not confirmed. Log in before starting the gateway:",
|
|
240
239
|
` cd ${dir}`,
|
|
241
240
|
" mutiro auth login <email>",
|
|
242
|
-
"",
|
|
243
|
-
"Also make sure the built-in Mutiro brain is NOT running for this agent —",
|
|
244
|
-
"running two brains at once will fight over the same conversations:",
|
|
245
|
-
" mutiro agent doctor",
|
|
246
241
|
].join("\n"),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Built-in brain check: `mutiro agent host status` exits 0 when a host
|
|
246
|
+
// process is already running for this agent. Starting OpenClaw's gateway
|
|
247
|
+
// on top of a running host would put two brains on one agent and make
|
|
248
|
+
// them race on every turn. `host doctor` (setup validation) is separate
|
|
249
|
+
// from `host status` (runtime liveness) — we want the latter here.
|
|
250
|
+
const hostStatus = await runPluginCommandWithTimeout({
|
|
251
|
+
argv: ["mutiro", "agent", "host", "status"],
|
|
252
|
+
timeoutMs: 5_000,
|
|
253
|
+
cwd: dir,
|
|
254
|
+
});
|
|
255
|
+
if (hostStatus.code === 0) {
|
|
256
|
+
issues.push(
|
|
257
|
+
[
|
|
258
|
+
"A Mutiro agent host is already running for this agent.",
|
|
259
|
+
"Stop it before starting OpenClaw — two brains on one agent will",
|
|
260
|
+
"race on every turn:",
|
|
261
|
+
" pkill -f 'mutiro agent host' # or stop whichever process started it",
|
|
262
|
+
].join("\n"),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (issues.length > 0) {
|
|
267
|
+
await prompter.note(
|
|
268
|
+
[
|
|
269
|
+
"Readiness checks flagged the following:",
|
|
270
|
+
"",
|
|
271
|
+
...issues.flatMap((issue) => [issue, ""]),
|
|
272
|
+
]
|
|
273
|
+
.join("\n")
|
|
274
|
+
.trimEnd(),
|
|
247
275
|
"Mutiro agent readiness",
|
|
248
276
|
);
|
|
249
277
|
}
|