@mutirolabs/openclaw-brain 0.1.0 → 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 CHANGED
@@ -7,12 +7,68 @@ 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
+
57
+ ## [0.1.1] - 2026-04-18
58
+
59
+ ### Added
60
+
61
+ - README prerequisites and wizard help text now suggest `--badge lobster` on
62
+ `mutiro agents create` so the agent shows the OpenClaw lobster badge in the
63
+ Mutiro UI. For existing agents, `mutiro agents update-profile <username>
64
+ --badge lobster` flips it on after the fact.
65
+
10
66
  ## [0.1.0] - 2026-04-18
11
67
 
12
68
  ### Added
13
69
 
14
- - Initial OpenClaw channel plugin for the Mutiro `chatbridge` protocol.
15
- - NDJSON envelope codec ported from `pi-brain`.
70
+ - Initial OpenClaw Channel extension for Mutiro.
71
+ - NDJSON envelope codec for `mutiro.agent.bridge.v1`.
16
72
  - Subprocess lifecycle for `mutiro agent host --mode=bridge`, one per configured account.
17
73
  - Inbound pipeline: `message.observed` → OpenClaw reply dispatch.
18
74
  - Outbound surface: `message.send`, `message.send_voice`, `message.react`,
@@ -46,5 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
46
102
  with concrete fix commands, plus the paste-into-AI prompt as an alternative
47
103
  for users who'd rather have their AI assistant drive the setup.
48
104
 
49
- [Unreleased]: https://github.com/mutirolabs/openclaw-brain/compare/v0.1.0...HEAD
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
107
+ [0.1.1]: https://github.com/mutirolabs/openclaw-brain/compare/v0.1.0...v0.1.1
50
108
  [0.1.0]: https://github.com/mutirolabs/openclaw-brain/releases/tag/v0.1.0
package/README.md CHANGED
@@ -1,131 +1,62 @@
1
- # Mutiro OpenClaw Channel Reference
1
+ # Mutiro Channel for OpenClaw
2
2
 
3
- Use this repo if you want to plug [OpenClaw](https://openclaw.ai) into a Mutiro agent over `chatbridge`, so OpenClaw becomes the external brain and Mutiro becomes just another OpenClaw channel.
3
+ The official Mutiro Channel extension for OpenClaw.
4
4
 
5
- This is the OpenClaw-shaped sibling of [`../pi-brain`](../pi-brain). The bridge codec is a faithful port of `mutiro-pi-bridge.ts`; the brain side is structured as an OpenClaw third-party channel plugin instead of a one-off standalone adapter.
5
+ OpenClaw handles the cognition. Mutiro handles the messaging surface, identity, and state.
6
6
 
7
- ## Prerequisites
7
+ ![Mutiro UI with OpenClaw Badge](docs/assets/mutiro-openclaw-ui.png)
8
8
 
9
- This plugin assumes you already have a working Mutiro agent. Before the Quick
10
- Start below, confirm each of these passes:
9
+ ## Why this exists
11
10
 
12
- | Check | Fix if it fails |
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>"` |
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
- > **Two brains, one agent = trouble.** Mutiro ships its own built-in brain. If
29
- > you leave it running, it will race OpenClaw for the same conversations and
30
- > both will reply (or neither will, depending on ordering). Stop it before
31
- > 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.
32
12
 
33
13
  ## Quick Start
34
14
 
35
- ### 1. Install this plugin into OpenClaw
15
+ Install the Mutiro channel using OpenClaw's native extension manager:
36
16
 
37
17
  ```bash
38
18
  openclaw plugins install --dangerously-force-unsafe-install @mutirolabs/openclaw-brain
39
19
  ```
40
20
 
41
- Or, for local development:
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.
42
22
 
43
- ```bash
44
- # Skip node_modules if you've run `npm install` here — the install scanner
45
- # walks the source tree and caps at 10k directories.
46
- rm -rf node_modules
47
- openclaw plugins install --dangerously-force-unsafe-install "file:$(pwd)"
48
- ```
49
-
50
- > **Why the `--dangerously-force-unsafe-install` flag?**
51
- > This plugin legitimately spawns `mutiro agent host --mode=bridge` as a
52
- > subprocess — that is the entire point of the `chatbridge` adapter. OpenClaw's
53
- > install scanner correctly flags any plugin that uses `child_process` as
54
- > sensitive, and requires this flag as an explicit acknowledgement. Before you
55
- > pass it, **confirm you are installing from the signed [mutirolabs/openclaw-brain](https://github.com/mutirolabs/openclaw-brain)
56
- > source** (or the `@mutirolabs/openclaw-brain` npm package). Review the
57
- > `spawn` call at [`src/bridge-client.ts`](./src/bridge-client.ts) if you want
58
- > to see exactly what the plugin executes.
59
-
60
- ### 2. Configure the channel
61
-
62
- The plugin ships a setup wizard. Run the bare command (no `--channel` flag —
63
- passing one skips the wizard and falls through to the non-interactive adapter):
23
+ Add the channel:
64
24
 
65
25
  ```bash
66
26
  openclaw channels add
67
27
  ```
68
28
 
69
- Pick `mutiro` from the list. The wizard will:
70
-
71
- - detect the `mutiro` CLI (and point you at the install command if missing)
72
- - ask for your Mutiro agent directory (the folder containing `.mutiro-agent.yaml`)
73
- - validate the directory and run `mutiro auth whoami`
74
- - remind you to stop the built-in Mutiro brain before starting the gateway
75
-
76
- Or set the config manually:
77
-
78
- ```bash
79
- openclaw config set channels.mutiro.accounts.default.agentDir /path/to/agent-directory
80
- ```
29
+ Pick `mutiro` from the list. The setup wizard detects the Mutiro CLI, validates your agent directory, and confirms you are authenticated.
81
30
 
82
- ### 3. Run the OpenClaw gateway
31
+ Start the gateway:
83
32
 
84
33
  ```bash
85
34
  openclaw gateway run
86
35
  ```
87
36
 
88
- Or use the shortcut:
89
-
90
- ```bash
91
- ./run-brain.sh /path/to/agent-directory
92
- ```
93
-
94
- ### 4. Talk to your agent
95
-
96
- 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.
97
38
 
98
- - **Web app:** [https://app.mutiro.com](https://app.mutiro.com)
99
- - **CLI chat:** `mutiro chat`
100
- - **Mobile:** Mutiro app on iOS / Android
101
- - **Desktop:** Mutiro desktop app on macOS / Windows / Linux
102
-
103
- For a quick shell smoke test:
39
+ Send a smoke-test message:
104
40
 
105
41
  ```bash
106
42
  mutiro user message send <agent-username> "Hello! Who are you?"
107
43
  ```
108
44
 
109
- ### 5. Allow Mutiro-specific agent tools
45
+ ## Enable Mutiro-native tools
110
46
 
111
- To let the OpenClaw agent send voice messages, interactive cards, or forward
112
- 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:
113
48
 
114
- ```yaml
115
- tools:
116
- profile: messaging
117
- alsoAllow:
118
- - "mutiro*"
49
+ ```bash
50
+ openclaw config set tools.alsoAllow '["mutiro*"]'
119
51
  ```
120
52
 
121
- See [`docs/guides/use-openclaw-as-brain.md`](./docs/guides/use-openclaw-as-brain.md)
122
- for a full walkthrough.
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
123
56
 
124
- ### 6. Share the agent with other users
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.
125
58
 
126
- Mutiro has a **server-side allowlist** that's separate from OpenClaw's own
127
- `allowFrom`. Denied users are blocked at the Mutiro server — their messages
128
- never reach OpenClaw at all. Manage it with the `mutiro` CLI:
59
+ One extra CLI step buys you that posture:
129
60
 
130
61
  ```bash
131
62
  mutiro agents allowlist get <agent-username>
@@ -133,134 +64,37 @@ mutiro agents allow <agent-username> <username>
133
64
  mutiro agents deny <agent-username> <username>
134
65
  ```
135
66
 
136
- See [`docs/guides/manage-allowlist.md`](./docs/guides/manage-allowlist.md) for
137
- the full command reference and a paste-into-AI prompt you can hand to your
138
- assistant when you want help managing sharing and security posture.
139
-
140
- ## What This Repo Is
141
-
142
- 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.
143
-
144
- - `mutiro agent host --mode=bridge` is spawned by the plugin, one process per configured Mutiro agent
145
- - NDJSON envelope traffic is translated into OpenClaw inbound messages and outbound send/react/forward actions
146
- - one subprocess per Mutiro agent, long-lived across conversations
147
- - 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.
148
68
 
149
- ## What Is Here
69
+ ## FAQ
150
70
 
151
- - `index.ts` plugin entry via `defineBundledChannelEntry`
152
- - `src/bridge-protocol.ts` — NDJSON envelope constants and `@type` URLs
153
- - `src/bridge-messages.ts` — normalized message extraction and observed-turn assembly
154
- - `src/bridge-client.ts` — NDJSON envelope codec plus host subprocess spawn
155
- - `src/bridge-session.ts` — per-conversation observed/task/snapshot handlers
156
- - `src/inbound.ts` — bridge observed message → OpenClaw inbound envelope
157
- - `src/outbound.ts` — OpenClaw outbound adapter → `message.send` / `message.react` / `message.forward`
158
- - `src/channel.ts` — Mutiro channel plugin definition
159
- - `src/channel.runtime.ts` — runtime barrel consumed by the plugin entry
160
- - `src/setup-surface.ts` — setup wizard driven by `openclaw channels add` (pick `mutiro` from the list)
161
- - `src/agent-tools.ts` — `mutiro_send_voice_message`, `mutiro_send_card`, `mutiro_forward_message`
162
- - `src/signal-forwarder.ts` — OpenClaw tool events → Mutiro `signal.emit` (26-entry map)
163
- - `src/live-snapshot.ts` — `session.snapshot` + `task.request` handlers for live call handoff
164
- - `openclaw.plugin.json` — channel manifest
165
- - `run-brain.sh` — convenience launcher that boots OpenClaw's gateway against a Mutiro agent directory
166
- - `docs/guides/use-openclaw-as-brain.md` — end-to-end setup guide
167
- - `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?**
168
72
 
169
- ## Why This Exists
73
+ Pass `--badge lobster` when creating the agent so every Mutiro client renders the lobster next to the avatar:
170
74
 
171
- Use this folder as a reference if you want to consume Mutiro's chatbridge from OpenClaw, or another gateway-shaped runtime that already owns its own channel/plugin contract.
172
-
173
- It shows how to:
174
-
175
- 1. Spawn `mutiro agent host --mode=bridge` from inside an OpenClaw channel plugin
176
- 2. Complete `ready → session.initialize → subscription.set`
177
- 3. Receive `message.observed` and turn it into an OpenClaw inbound envelope
178
- 4. Route OpenClaw outbound replies through bridge-local commands (`message.send`, `message.react`, `message.forward`, `media.upload`, `signal.emit`, `recall.search/get`)
179
- 5. Finish turns with `turn.end`
180
-
181
- ## Important Bridge Notes
182
-
183
- - `message.send` is a bridge-local command, not a raw backend `SendToConversationRequest`
184
- - the portable payload type is `mutiro.chatbridge.ChatBridgeSendMessageCommand`
185
- - `message.send_voice` is also bridge-local and keeps TTS inside the host
186
- - this reference usually replies by `conversation_id`
187
- - the bridge also supports `to_username` for direct sends
188
-
189
- ## Adapter Model
190
-
191
- The plugin process is an OpenClaw channel. It:
192
-
193
- - spawns `mutiro agent host --mode=bridge` (one per configured Mutiro agent directory)
194
- - reads and writes bridge envelopes on stdio
195
- - delivers `message.observed` payloads as OpenClaw inbound messages
196
- - exposes outbound send/react/forward through the standard OpenClaw `ChannelOutboundAdapter`
197
-
198
- OpenClaw's agent runtime owns the brain layer. The plugin does not talk to Mutiro SDKs directly; everything portable flows through the chatbridge envelope.
199
-
200
- ## Supported Bridge Operations
201
-
202
- This adapter exercises:
203
-
204
- - `message.send`
205
- - `message.send_voice`
206
- - `message.react`
207
- - `message.forward`
208
- - `media.upload`
209
- - `signal.emit`
210
- - `turn.end`
211
- - `recall.search`
212
- - `recall.get`
213
-
214
- ## Session Model
215
-
216
- - one Mutiro `conversation_id` maps to one OpenClaw conversation binding
217
- - later turns in the same conversation reuse the same OpenClaw session, just as pi-brain reuses a Pi session
218
- - `session.snapshot` is answered from recent messages cached per-conversation in the plugin
219
-
220
- OpenClaw already owns transcript continuity across turns, so the plugin keeps its own cache narrow — just enough to answer `session.snapshot` for bridge consumers.
221
-
222
- ## Handshake
223
-
224
- Startup flow:
225
-
226
- 1. host sends `ready`
227
- 2. plugin sends `session.initialize`
228
- 3. plugin sends `subscription.set`
229
- 4. host starts delivering `message.observed`
230
-
231
- Per turn:
232
-
233
- 1. plugin acknowledges `message.observed`
234
- 2. plugin dispatches the observed envelope into OpenClaw's inbound pipeline
235
- 3. OpenClaw's reply-dispatch drives zero or more outbound bridge operations
236
- 4. plugin sends `turn.end`
237
-
238
- ## Debugging
239
-
240
- Useful signals while integrating:
241
-
242
- - `Handshake failed`
243
- Bridge startup or negotiation problem.
244
- - `Host error`
245
- A bridge request failed outside a pending request path.
246
- - `outbound bridge call failed`
247
- 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
+ ```
248
78
 
249
- ## Type Checking
79
+ For an agent that already exists, flip the badge on with:
250
80
 
251
81
  ```bash
252
- npm run check
82
+ mutiro agents update-profile <agent-username> --badge lobster
253
83
  ```
254
84
 
255
- It runs with `skipLibCheck` because the OpenClaw plugin SDK's dependency tree includes external type issues that are not specific to this reference code.
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.
256
90
 
257
- ## What To Copy
91
+ Or follow the [Mutiro create-agent guide](https://www.mutiro.com/docs/guides/create-agent) by hand.
258
92
 
259
- If you are integrating another gateway-shaped runtime, the most useful pieces to copy are:
93
+ ## Resources
260
94
 
261
- - bridge handshake flow (`src/bridge-session.ts` + `src/bridge-client.ts`)
262
- - pending-request correlation by `request_id`
263
- - `message.observed` acknowledgement behavior (ack delivery now, reply later)
264
- - per-conversation recent-message cache for `session.snapshot`
265
- - outbound operation wrappers (`src/outbound.ts`)
266
- - final `turn.end` behavior
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
@@ -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 over `chatbridge`, so there are two allowlists. The Mutiro backend
19
- allowlist is the authoritative gate — denied users are blocked at the server.
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 through `chatbridge`. Mutiro stays the messaging platform; OpenClaw becomes the brain.
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 over `chatbridge`. Mutiro keeps the agent identity, connectivity, messaging, auth, and media plumbing. OpenClaw becomes the thinking layer and drives outbound replies through the bridge.
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.md
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 openclaw-brain Plugin
87
+ ### Step 4: Install the Mutiro Channel extension
88
88
 
89
- This plugin is the piece that lets OpenClaw speak Mutiro's `chatbridge`. It spawns `mutiro agent host --mode=bridge` as a subprocess and translates NDJSON envelopes into OpenClaw inbound messages and outbound send/react/forward/voice/card/forward calls.
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 plugin legitimately
109
- spawns `mutiro agent host --mode=bridge` as a subprocess — that is the
110
- entire `chatbridge` adapter. OpenClaw's install scanner correctly flags any
111
- plugin that uses `child_process` as sensitive and requires this flag as an
112
- explicit acknowledgement. Before you pass it, confirm you are installing
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 plugin executes.
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 bridge signals, so Mutiro surfaces "thinking", "web searching", "recalling", "sending voice", etc. in real time while the agent works. The plugin maps 26 OpenClaw tool names to Mutiro `SignalType` enums; anything outside the map falls back to `SIGNAL_TYPE_CUSTOM` with a detail label.
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 plugin handles this by accumulating the agent's reply text and returning it as `ChatBridgeTaskResult.text`. It also answers `session.snapshot` from recent messages cached per-conversation so Mutiro can bootstrap the live lane.
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.md
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 chatbridge channel plugin",
15
+ description: "The official Mutiro Channel extension for OpenClaw.",
16
16
  importMetaUrl: import.meta.url,
17
17
  plugin: {
18
18
  specifier: "./src/channel.js",
@@ -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.1.0",
4
- "description": "OpenClaw channel plugin that drives a Mutiro agent over chatbridge. OpenClaw becomes the brain; Mutiro stays the messaging surface.",
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
- "plugin",
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 (plugin)",
65
+ "selectionLabel": "Mutiro",
67
66
  "docsPath": "/channels/mutiro",
68
67
  "docsLabel": "mutiro",
69
- "blurb": "chatbridge channel; configure a Mutiro agent directory to enable.",
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
  },
@@ -1,7 +1,7 @@
1
1
  // Channel-owned agent tools. Exposed to OpenClaw's agent loop via
2
- // `ChannelPlugin.agentTools`. The first one mirrors pi-brain's
3
- // `send_voice_message`: a text-to-speech voice message delivered through the
4
- // bridge's `message.send_voice` command (host-side TTS, not client-side).
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";
@@ -1,6 +1,5 @@
1
1
  // NDJSON envelope codec + subprocess manager for the Mutiro chatbridge.
2
- // Ported from pi-brain's `createHostProcess` and `createBridgeClient`, kept
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";
@@ -1,5 +1,5 @@
1
- // Message normalization helpers ported from pi-brain/mutiro-pi-bridge.ts.
2
- // The host delivers `envelope.payload.message` as a pre-normalized bag of parts;
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
 
@@ -1,6 +1,6 @@
1
1
  // NDJSON protocol constants used by the Mutiro chatbridge envelope.
2
- // Ported verbatim from pi-brain/mutiro-pi-bridge.ts so that behavior matches
3
- // the reference adapter envelope-for-envelope.
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
 
@@ -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`. Ported from pi-brain's `main()` but restructured so the
4
- // OpenClaw plugin runtime can start/stop it per configured account.
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
 
@@ -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
- ctx.log?.info?.(`mutiro: host (${ctx.accountId}) exited with code ${code}`);
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
- lastDisconnect: { at: Date.now(), status: code ?? undefined },
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
- settleLifecycle();
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 (plugin)",
74
+ selectionLabel: "Mutiro",
55
75
  docsPath: "/channels/mutiro",
56
76
  docsLabel: "mutiro",
57
- blurb: "chatbridge channel; configure a Mutiro agent directory to enable.",
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. Mirrors pi-brain's tool surface (send_message,
3
- // send_voice_message, send_card, react_to_message, send_file_message,
4
- // forward_message, recall, recall_get) but reshaped so OpenClaw's
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). pi-brain uses stale names
116
- // (`json_data` / `version`) which the host's strict JSON-to-proto
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)}`,
@@ -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.md";
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 chatbridge setup",
170
+ title: "Mutiro Channel setup",
171
171
  lines: MUTIRO_INTRO_LINES,
172
172
  },
173
173
  prepare: async ({ prompter }) => {
@@ -183,7 +183,7 @@ export const mutiroSetupWizard: ChannelSetupWizard = {
183
183
  "",
184
184
  "Then log in and create (or pick) an agent:",
185
185
  " mutiro auth login <email>",
186
- ' mutiro agents create <username> "<Display Name>" --engine genie --bio "<bio>"',
186
+ ' mutiro agents create <username> "<Display Name>" --engine genie --bio "<bio>" --badge lobster',
187
187
  "",
188
188
  `Guide: ${CREATE_AGENT_GUIDE}`,
189
189
  ].join("\n"),
@@ -204,7 +204,7 @@ export const mutiroSetupWizard: ChannelSetupWizard = {
204
204
  "should drive. Each configured account points to one agent directory.",
205
205
  "",
206
206
  "If you don't have one yet:",
207
- ' mutiro agents create <username> "<Display Name>" --engine genie --bio "<bio>"',
207
+ ' mutiro agents create <username> "<Display Name>" --engine genie --bio "<bio>" --badge lobster',
208
208
  `Guide: ${CREATE_AGENT_GUIDE}`,
209
209
  ],
210
210
  currentValue: ({ cfg, accountId }) =>
@@ -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
- await prompter.note(
236
+ issues.push(
236
237
  [
237
- "Could not confirm `mutiro auth whoami`.",
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
  }