Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 83 additions & 1 deletion .github/workflows/mcode-island-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,34 @@ on:
#
# `[code]smith` is SKIPPED on this repository, so this windows-latest
# job is the CI evidence for the round-5 review.
#
# Round-9 review (hetaoBackend, 2026-09-10T01:43:05Z) on commit
# 5a0040e (PR #37 mcode-island v0.4.0):
# 1. path-traversal guard in apply.mjs / restore.mjs
# (`--release` flag, symlink escape, realpath containment)
# 2. atomic write in apply.mjs (staging + rename, permission
# preservation, mid-write failure isolation)
# 3. negative-injection tests covering both contracts
#
# Step 5 below runs `hooks/win32-ava-patch/test-apply.mjs`, which
# exercises all three contracts in a sandboxed temp dir (no real
# ~/.minimax-code/releases/ is touched). The test file is the same
# one a developer runs locally; CI just confirms the contract on
# the real windows-latest image. 13 cases must pass.
#
# Step 6 below runs `hooks/win32-ava-patch/smoke-runtime.mjs`, which
# is the host-level "parse and dispatch" smoke the round-9 review
# asked for. 7 cases must pass: bundled hooks.json well-formed,
# Fwe allowlist intersection, and the 5 in-Fwe hook scripts exit
# 0 via a Node-replica of the patched `Ava` (`Ava` +
# `|| process.platform === "win32"` -> `Dva(bZ())`).

permissions:
contents: read

jobs:
mcode-island-windows:
name: mcode-island on windows-latest (parse + token + hook + mock-API)
name: mcode-island on windows-latest (parse + token + hook + mock-API + win32-ava-patch contract + runtime smoke)
runs-on: windows-latest
timeout-minutes: 10
defaults:
Expand Down Expand Up @@ -411,3 +432,64 @@ jobs:
}
Write-Host "test c (no token): OK returned null"
Write-Host "Get-5hUsage via dot-source + matching fixture + token-source precedence: 3/3 OK"

# 5) win32-ava-patch contract (PR #37 round-9 review):
# path validation + atomic write + idempotent round-trip.
# test-apply.mjs runs entirely inside os.tmpdir(); the real
# ~/.minimax-code/releases/ install is never touched. A
# failure here means a contract regressed. The test summary
# is at the end of the step output; we additionally fail
# the job on non-zero exit.
#
# Negative-injection coverage (one entry per case the
# review called out):
# Test 1 (7 cases) -- --release value validation:
# ../ traversal, absolute path, semver-violating
# name with shell meta, NUL byte (blocked at the OS
# layer), empty string, drive letter, \\?\ extended path
# Test 2 -- symlink escape containment (realpath check)
# Test 3 (2 cases) -- atomic write contract: mid-write
# failure leaves target byte-identical; permission mode
# preserved across apply
# Test 4 (2 cases) -- idempotent round-trip:
# apply -> apply(no-op) -> restore -> apply cycle;
# restore on unpatched is a no-op
# Test 5 -- listReleases() filters non-semver and
# dot-prefixed directory entries
- name: win32-ava-patch contract: path validation + atomic write (round-9 review)
run: |
cd '${{ github.workspace }}'
$r = node 'plugins/antianqi/mcode-island/hooks/win32-ava-patch/test-apply.mjs'
if ($LASTEXITCODE -ne 0) {
throw "test-apply.mjs exited $LASTEXITCODE (expected 0; see step output for failing case)"
}

# 6) Host-level runtime smoke (PR #37 round-9 review):
# the review asked for "a real host-level smoke: parse and
# dispatch the submitted io.minimax.mcode/hooks/hooks.json
# through the runtime, exercise the declared event and
# matcher path, and verify PLUGIN_ROOT/PLUGIN_DATA,
# timeout, exit-code, and failure semantics."
#
# smoke-runtime.mjs replicates the patched `Ava` (Ava
# with `|| process.platform === "win32"`) in pure Node and
# exercises the 5 in-Fwe hook scripts (SessionStart,
# SessionEnd, UserPromptSubmit, PreToolUse, PostToolUse)
# with a synthetic event payload. The 0.3.10 dispatch
# allowlist (`Fwe`) covers exactly those 5 events; the
# other 7 declared in hooks.json (Stop, PreCompact,
# Notification, SubagentStart, SubagentStop,
# PermissionRequest, PermissionDenied) are forward-only
# and are recorded as such.
#
# This is the "parse and dispatch" assertion the review
# asked for. The mcode runtime itself is not required
# to be installed in CI; the smoke validates the hook
# document contract that the runtime would enforce.
- name: Runtime smoke: bundled hooks.json + 5 in-Fwe hook scripts via patched Ava (round-9 review)
run: |
cd '${{ github.workspace }}'
$r = node 'plugins/antianqi/mcode-island/hooks/win32-ava-patch/smoke-runtime.mjs'
if ($LASTEXITCODE -ne 0) {
throw "smoke-runtime.mjs exited $LASTEXITCODE (expected 0; see step output for failing case)"
}
111 changes: 74 additions & 37 deletions plugins/antianqi/mcode-island/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,51 @@ visible at a glance, without forcing the user to switch back.

## How the pill is driven

`mcode-island` v0.3.0 supports two modes. The widget behaves the same in
`mcode-island` v0.4.0 supports two modes. The widget behaves the same in
both — what changes is who decides the state.

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

mcode 0.2.4 ships a `io.minimax.mcode` client-extension namespace for
mcode 0.3.10 ships a `io.minimax.mcode` client-extension namespace for
lifecycle Hooks. When the registry accepts it (companion proposal:
[`MiniMax-Code-Plugins` PR #20](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/20)),
the runtime spawns a script from this plugin for every matching event:

| event | pill state | script | 0.2.4 dispatch |
| ----------------- | ----------- | ------------------------------- | -------------- |
| `SessionStart` | `idle` | `session-start.ps1` | yes |
| `SessionEnd` | `idle` | `session-end.ps1` | yes |
| `UserPromptSubmit`| `thinking` | `user-prompt-submit.ps1` | yes |
| `PreToolUse` | `working` | `pre-tool-use.ps1` | yes |
| `PostToolUse` | `done`/`error` | `post-tool-use.ps1` | yes |
| `Stop` | `done` | `stop.ps1` | **forward** — see below |
| `PreCompact` | `thinking` | `pre-compact.ps1` | **forward** — see below |
| `Notification` | `idle` | `notification.ps1` | **forward** — see below |
| `SubagentStart` | `working` (CODEX only) | `subagent-start.ps1` | **forward** — see below |
| `SubagentStop` | `done` (CODEX only) | `subagent-stop.ps1` | **forward** — see below |
| `PermissionRequest`| `waiting` | `permission-request.ps1` | **forward** — see below |
| `PermissionDenied`| `error` | `permission-denied.ps1` | **forward** — see below |
[`MiniMax-Code-Plugins` PR #36](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/36),
the 0.3.10-runtime-compat follow-up to the original
[PR #20](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/20)),
the runtime spawns a script from this plugin for every matching event.
**Verified on `@minimax-ai/code@0.3.10` (chunk-CTHP2I62.js) and
`@minimax-ai/code@0.3.11` (chunk-P2ZQPHDU.js); the hook schema, the
`Ava` dispatch wrapper, the `Fwe` allowlist, and the `Uwe` parser are
byte-identical between the two releases.**

| event | pill state | script | 0.3.10 / 0.3.11 dispatch |
| ----------------- | ----------- | ------------------------------- | ----------------------- |
| `SessionStart` | `idle` | `session-start.ps1` | yes (`Fwe` set) |
| `SessionEnd` | `idle` | `session-end.ps1` | yes (`Fwe` set) |
| `UserPromptSubmit`| `thinking` | `user-prompt-submit.ps1` | yes (`Fwe` set) |
| `PreToolUse` | `working` | `pre-tool-use.ps1` | yes (`Fwe` set) |
| `PostToolUse` | `done`/`error` | `post-tool-use.ps1` | yes (`Fwe` set) |
| `Stop` | `done` | `stop.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |
| `PreCompact` | `thinking` | `pre-compact.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |
| `Notification` | `idle` | `notification.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |
| `SubagentStart` | `working` (CODEX only) | `subagent-start.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |
| `SubagentStop` | `done` (CODEX only) | `subagent-stop.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |
| `PermissionRequest`| `waiting` | `permission-request.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |
| `PermissionDenied`| `error` | `permission-denied.ps1` | **forward** — not in 0.3.10 / 0.3.11 `Fwe` set |

**Forward events (7 of 12):** the spec reserves these in
`proposals/hooks-detailed-spec.md` and this plugin ships a script for
each, but the mcode 0.2.4 runtime allowlist (`Wso` set in
`@minimax-ai/code@0.2.4`) does not yet dispatch them. The 0.2.4
runtime treats unknown event names as no-op. Once a future mcode
release adds the dispatch, the same `.ps1` files start firing without
any code change here. The smoke test
each, but the mcode 0.3.10 / 0.3.11 runtime allowlist (`Fwe` set in
`@minimax-ai/code@0.3.10`, `chunk-CTHP2I62.js:1843`, byte-identical in
`@minimax-ai/code@0.3.11`'s `chunk-P2ZQPHDU.js`) does not yet dispatch
them. The 0.3.10 / 0.3.11 runtime treats unknown event names as no-op.
Once a future mcode release adds the dispatch, the same `.ps1` files
start firing without any code change here. The smoke test
(`scripts/smoke.mjs`) tags these as `WARN` rather than `FAIL` for that
reason — the **plugin is correct, the runtime is not yet ready**.

If you need any of these events on 0.2.4 today, the supported fallback
is to call `notify-island.ps1` from the agent (Mode B) at the moment
you would otherwise rely on the event firing. The wrapper
If you need any of these events on 0.3.10 / 0.3.11 today, the supported
fallback is to call `notify-island.ps1` from the agent (Mode B) at the
moment you would otherwise rely on the event firing. The wrapper
`wrap-tool.ps1` covers the `Bash` path automatically.

The agent does not need to remember to push state — the runtime fires the
Expand Down Expand Up @@ -160,23 +167,33 @@ alternative:

1. **Install** — copy this folder into your `~/.minimax/plugins/mcode-island/`
(or any directory you want; the scripts only need to live together).
2. **Start the widget**:
2. **(Mode A only) Materialise the hook document** — the 0.3.10+ runtime
reads `${MINIMAX_DATA_DIR}/hooks/hooks.json`, not the Plugin's own
`io.minimax.mcode/` path. Run once after install (and after every mcode
upgrade that changes the bundled document):
```powershell
& "%PLUGIN_DIR%\mcode-island\install-hook.ps1" # project-wide
& "%PLUGIN_DIR%\mcode-island\install-hook.ps1" -Agent mavis # per-agent
```
This is idempotent. Pass `-DataDir <path>` to override
`${MINIMAX_DATA_DIR}` when the env var is not set.
3. **Start the widget**:
```cmd
mcode-island.cmd start
```
You should see a small dark pill appear at the top center of the screen.
3. **Test a state push** from a new terminal:
4. **Test a state push** from a new terminal:
```powershell
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State working -Message "demo"
```
The pill should turn blue and pulse for as long as you don't push another state.
4. **Enable logon auto-start** (optional):
5. **Enable logon auto-start** (optional):
```powershell
& "%PLUGIN_DIR%\mcode-island\autostart.ps1" -Action Enable
```
This writes to `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`. No
admin rights required.
5. **Stop when done**:
6. **Stop when done**:
```cmd
mcode-island.cmd stop
```
Expand All @@ -185,7 +202,7 @@ alternative:

```
mcode-island/
├── plugin.json # plugin manifest (official 1.0 schema)
├── plugin.json # plugin manifest (v0.4.0, official 1.0 schema)
├── README.md # this file
├── LICENSE # Apache-2.0
├── mcode-island.ps1 # WPF widget main loop
Expand All @@ -196,6 +213,7 @@ mcode-island/
├── show-island.ps1 # re-raise hidden widget
├── pin-island.ps1 # lock click-to-focus target
├── autostart.ps1 # register / unregister Windows logon
├── install-hook.ps1 # Mode A: copy hooks.json into ${MINIMAX_DATA_DIR}
├── notify-island.ps1 # state-push helper (Mode B)
├── wrap-tool.ps1 # all-in-one bash wrapper
├── mcode-status-detect.ps1 # runtime-state detector (Mode B fallback)
Expand Down Expand Up @@ -230,7 +248,7 @@ binary, no symlink, no `node_modules`.
| Windows | 10 1809+ or 11 (uses WPF, `user32` `kernel32`) |
| PowerShell | 5.1 (ships with Windows 10/11) or PowerShell 7 |
| .NET WPF runtime | 4.x (ships with Windows 10/11) |
| mcode | any version (Mode B works everywhere); 0.2.4+ activates Mode A |
| mcode | any version (Mode B works everywhere); 0.3.10+ (verified on 0.3.10 and 0.3.11) activates Mode A (with the Windows caveat below) |
| execution policy | `Bypass` for this directory; not changed globally |
| network access | **optional** — see "Network access" below. The widget itself is offline. `mcode-status-detect.ps1` only contacts `https://api.minimax.io/v1/coding_plan/remains` when a token is configured (see "Accounts" + "Data use"). |
| accounts | **optional** — see "Accounts" below. No account is required to run the widget; a token is only needed if you want the optional 5-hour usage readout in the pill. |
Expand Down Expand Up @@ -363,9 +381,28 @@ a live MiniMax Code session. Empirical evidence (captured during development):
writer and never misses an event.
- Mode A (Hook-driven) requires the registry validator to accept the
`io.minimax.mcode` client-extension namespace. The companion proposal
([`MiniMax-Code-Plugins` PR #20](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/20))
is still pending merge; until then, the `io.minimax.mcode/hooks/` directory
is dormant and the widget runs in Mode B (agent-pushed + detector).
was rewritten for the 0.3.10 / 0.3.11 nested schema in
[`MiniMax-Code-Plugins` PR #36](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/36)
(follow-up to the original
[PR #20](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/20));
until the registry accepts the namespace, the `io.minimax.mcode/hooks/`
directory is dormant and the widget runs in Mode B.
- On mcode 0.3.10 / 0.3.11 only 5 / 12 events dispatch
(`SessionStart`, `SessionEnd`, `UserPromptSubmit`, `PreToolUse`,
`PostToolUse`); the other 7 are forward-only — the `.ps1` files ship
and will start firing when a future mcode release grows the `Fwe` set
(the `Fwe` allowlist is byte-identical in 0.3.10 and 0.3.11).
- On Windows 0.3.10 / 0.3.11 even the 5 dispatched events do not
actually fire out of the box, because the runtime spawns commands via
`/bin/sh -lc` which ENOENTs on a stock Windows install. The hook
document is correct and `install-hook.ps1` succeeds, but no script
will run until upstream sets `usePlatformShell: true` on Windows. The
shipped `hooks/win32-ava-patch/apply.mjs` is a local-only patch that
makes the runtime use the existing Windows-aware shell detector
(`bZ` / `YO`) and unblocks Mode A on Windows 0.3.10 / 0.3.11 today
(verified — see the patch README); it has to be re-applied after every
`npm install -g @minimax-ai/code`. Track the upstream issue and use
Mode B in the meantime if the patch is not applied.

## Roadmap

Expand Down
Loading
Loading