# Mcode Island

> Push the user's terminal out of focus to a Windows desktop Dynamic Island pill so the user can watch your work without switching back to mcode. On mcode 0.2.4+ with the `io.minimax.mcode` Hooks extension enabled (forward-compatible with MiniMax-Code-Plugins PR

- Skill: `minimax-ai/mcode-island` (Agent Skill)
- Install (CLI): `npx skillmds@latest add minimax-ai/mcode-island`
- Raw SKILL.md: https://api.skillmd.com/api/skills/minimax-ai/mcode-island/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: Apache-2.0
- Author: MiniMax AI (https://skillmd.com/u/minimax-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/minimax-ai/mcode-island

---


# mcode-island — 桌面灵动岛状态通知

让用户在不切回 mcode 窗口的情况下，从桌面顶部悬浮 pill 上看到你（agent）的实时工作状态。

## What it looks like

A 320×60 pill anchored to the top center of the primary display, always-on-top, dark
theme. Six states with distinct color and motion:

| state      | color          | icon | meaning                          |
| ---------- | -------------- | ---- | -------------------------------- |
| `idle`     | gray           | —    | waiting for user input           |
| `thinking` | yellow pulse   | —    | reasoning, no tool call yet      |
| `working`  | blue pulse     | ⚙    | actively running a tool          |
| `waiting`  | orange         | ?    | tool needs user approval / input |
| `done`     | green          | ✓    | step finished, more to do        |
| `error`    | red            | ✕    | tool or step failed              |

Click the pill to switch focus back to the originating terminal tab. Run
`mcode-island pin` from inside a terminal to fix the focus target explicitly
(useful when the auto-detected HWND is wrong, e.g. Windows Terminal multi-tab).

## Two ways to drive the pill

### Mode A — Hook-driven (mcode 0.2.4+ with `io.minimax.mcode`)

When mcode accepts the `io.minimax.mcode` client extension, the runtime spawns
the script under `io.minimax.mcode/hooks/scripts/<event>.ps1` for every matching
lifecycle event. The agent does **not** need to push state manually.

| event             | script                            | pill state  |
| ----------------- | --------------------------------- | ----------- |
| `SessionStart`    | `session-start.ps1`               | `idle`      |
| `SessionEnd`      | `session-end.ps1`                 | `idle`      |
| `UserPromptSubmit`| `user-prompt-submit.ps1`          | `thinking`  |
| `PreToolUse`      | `pre-tool-use.ps1`                | `working`   |
| `PostToolUse`     | `post-tool-use.ps1`               | `done`/`error` |
| `Stop`            | `stop.ps1`                        | `done`      |
| `PreCompact`      | `pre-compact.ps1`                 | `thinking`  |
| `Notification`    | `notification.ps1`                | `idle`      |
| `SubagentStart`   | `subagent-start.ps1` (CODEX only) | `working`   |
| `SubagentStop`    | `subagent-stop.ps1`  (CODEX only) | `done`      |
| `PermissionRequest`| `permission-request.ps1` (returns `{"decision":"allow"}` so the runtime's fail-closed default does not deny) | `waiting` |
| `PermissionDenied`| `permission-denied.ps1`           | `error`     |

The hooks conform to the portable spec proposed in
`MiniMax-Code-Plugins` PR #20. Each script reads the JSON event payload from
stdin, calls `notify-island.ps1` with the appropriate state, and exits 0
(decision-bearing events also write a JSON decision to stdout). Self-push
filtering prevents the pill from churning when the agent calls
`notify-island.ps1` directly through Bash.

If you are running on mcode 0.2.4+ and the pill is updating itself before you
push anything, Mode A is active. Otherwise fall through to Mode B.

### Mode B — Agent-pushed (legacy, always works)

For older mcode, or when the `io.minimax.mcode` extension is not yet active
(registry validator has not accepted the namespace), the agent pushes state
through `notify-island.ps1` directly. The `mcode-status-detect.ps1` detector
also infers state from the runtime's `ledger.jsonl` / `messages.jsonl`, so
the pill will still move — your manual pushes just sharpen the message and
cover edge cases (notably `ask_user`).

| moment                                                | state     | example message                |
| ----------------------------------------------------- | --------- | ------------------------------ |
| receive user task, start reasoning                    | `thinking`| (none)                        |
| about to invoke any tool                              | `working` | `"bash: npm test"`            |
| tool returned 0, before reporting back                | `done`    | `"3 files modified"`          |
| tool needs approval (e.g. permission prompt)          | `waiting` | `"bash: needs approval"`       |
| about to call `ask_user` (user must pick)             | `waiting` | `"ask_user: 2 options"`       |
| user answered `ask_user`, resuming work                | `done`    | `"ask_user answered"`         |
| tool failed / threw / non-zero exit                   | `error`   | `"compile failed: missing import"` |
| conversation idle, waiting for user                   | `idle`    | (none)                        |

**`ask_user` is a special tool** — the detector cannot infer it is a "wait for
user" moment (it looks like any other tool call to the session log). When in
Mode B, the agent MUST push `waiting` immediately before invoking `ask_user`,
and `done` immediately after the user answers; otherwise the pill will sit in
`working` (yellow/blue) while the user is actually being asked to decide. In
Mode A, the same coverage comes for free because `ask_user` is a tool call
that fires `PreToolUse`/`PostToolUse`.

**Never push the same state twice in a row** — the widget de-duplicates by
state+message. Push only on transitions, or include a fresh message each time.

## Copyable example (agent side, Mode B)

The plugin ships a thin wrapper `wrap-tool.ps1` that **publishes state only**
(it does NOT execute the command). Run the command via mcode's own bash tool,
then call `wrap-tool.ps1` to publish the outcome:

```powershell
# Step 1: announce "working" before invoking mcode's bash tool
& "<plugin install dir>\wrap-tool.ps1" -Tool bash -Command "npm test" -Description "run tests"

# Step 2: after mcode's bash tool returns, publish the outcome
& "<plugin install dir>\wrap-tool.ps1" -Tool bash -Command "npm test" -ExitCode $LASTEXITCODE
```

`$LASTEXITCODE` is interpreted as: `0` → `done`, codes in `-WaitingExitCodes`
(default `[1]`) → `waiting`, anything else → `error`. The wrapper returns the
exit code unchanged so the calling shell still sees it.

The wrapper accepts `-Tool bash|read|write|edit|glob|grep|web|task|notebook` and
emits a tool-specific `done` message (e.g. `read C:\path`, `edited file.cs`,
`npm test 完成`) so the pill text is informative. For read/write/edit/glob/grep
the wrapper itself does not execute the command — mcode's own tool does; this
script only publishes the state.

For other tools (read/write/edit) — and for any state push that is not a single
command — call `notify-island.ps1` directly:

```powershell
$plugin = "<plugin install dir>"   # directory that contains notify-island.ps1
& "$plugin\notify-island.ps1" -State thinking
& "$plugin\notify-island.ps1" -State working  -Message "read source tree"
& "$plugin\notify-island.ps1" -State done     -Message "indexed 142 files"
& "$plugin\notify-island.ps1" -State error    -Message "compile failed: missing import"
& "$plugin\notify-island.ps1" -State waiting  -Message "permission prompt"
```

`<plugin install dir>` is the directory that contains `notify-island.ps1`.
Substitute the absolute path your user installed the plugin at. The Skill body
deliberately avoids hard-coded paths so any user / any install location works.

## Expected result

After each push (or after each hook fires), the widget on the user's primary
display updates within ~400 ms (one polling cycle). On click, the originating
terminal tab regains focus. The widget is intentionally hard to kill: Alt+F4
hides it, not closes it, and `mcode-island show` re-raises the hidden window
in under 1 second.

## User-side management

```cmd
mcode-island                  REM start the widget (idempotent)
mcode-island stop             REM stop the widget
mcode-island status           REM show PID + recent log
mcode-island show             REM re-raise hidden widget
mcode-island pin              REM lock focus target to current foreground window
mcode-island unpin            REM clear focus target
mcode-island autostart-on     REM register for Windows logon
mcode-island autostart-off    REM unregister
```

To enable login auto-start, the user runs once:

```powershell
& "<plugin install dir>\autostart.ps1" -Enable
```

This writes to `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` — no admin
rights required.

## Runtime data

All widget state lives under `%APPDATA%\mcode-island\`:

| file             | purpose                                               |
| ---------------- | ----------------------------------------------------- |
| `status.json`    | current state (widget polls this every 400 ms)        |
| `caller.json`    | originating terminal HWND / PID (for click-to-focus)  |
| `config.json`    | pill position, size, opacity (saved on drag)          |
| `widget.pid`     | widget process PID (used by start/stop/status)        |
| `island.log`     | append-only state transition history                  |
| `widget.log`     | widget internal debug log                             |
| `show.signal`    | transient file written by `mcode-island show`         |

No data leaves the local machine *unless* an opt-in 5-hour usage token is
configured. See the **Network access** + **Accounts** sections in
`README.md` for the exact host (`api.minimax.io/v1/coding_plan/remains`),
the rate limit (one GET per 60 s), and the storage locations
(`config.json:planApiToken` or env `MINIMAX_OAUTH_TOKEN` / `MINIMAX_API_KEY`).
When no token is configured the plugin makes no network requests at all.

## What is in this package

```
mcode-island/
├── plugin.json                       # plugin manifest
├── README.md                         # full user-facing docs
├── LICENSE                           # Apache-2.0
├── mcode-island.ps1                  # WPF widget main loop
├── mcode-island.cmd                  # CLI shim (start/stop/status/...)
├── start-island.ps1                  # launch the widget in STA
├── stop-island.ps1                   # stop the widget
├── status-island.ps1                 # print widget state
├── show-island.ps1                   # re-raise hidden widget
├── pin-island.ps1                    # lock focus target to foreground
├── autostart.ps1                     # register/unregister Windows logon
├── notify-island.ps1                 # state-push helper (agents call this)
├── wrap-tool.ps1                     # all-in-one bash wrapper
├── mcode-status-detect.ps1           # runtime-state detector
├── io.minimax.mcode/                 # client extension (PR #20 spec)
│   └── hooks/
│       ├── hooks.json                # 12-event declaration
│       └── scripts/
│           ├── _lib.ps1              # shared helper
│           ├── session-start.ps1
│           ├── session-end.ps1
│           ├── user-prompt-submit.ps1
│           ├── pre-tool-use.ps1
│           ├── post-tool-use.ps1
│           ├── stop.ps1
│           ├── pre-compact.ps1
│           ├── notification.ps1
│           ├── subagent-start.ps1
│           ├── subagent-stop.ps1
│           ├── permission-request.ps1
│           └── permission-denied.ps1
├── skills/mcode-island/SKILL.md      # this file
└── assets/                           # screenshots used in the README
```

## Limitations and known constraints

- Windows 10/11 only (uses WPF, `user32`, and `kernel32` P/Invoke).
- Single widget per user session.
- Hook-driven mode requires mcode 0.2.4+ Runtime. The portable spec
  (`io.minimax.mcode` client extension) is still pending merge in
  `MiniMax-Code-Plugins` PR #20; until the registry validator accepts the
  namespace, the hooks subdirectory is dormant and the plugin falls back to
  Mode B (agent-pushed + detector).
- No hover-expand, no media-control integration yet — see the `v0.2` roadmap in
  the upstream issue tracker.
- `wrap-tool.ps1` is a **status publisher only** — it never executes the
  command itself (mcode's tool does). The agent still runs every read / write /
  edit through mcode and then calls `wrap-tool.ps1` to publish the outcome.
  This avoids shell-injection ambiguity from a prior `Invoke-Expression` design.

