Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ca395b6
feat(mcode-island): add sub-step progress fields to status.json (v0.4.0)
antianqi Sep 23, 2026
e2f0dd3
feat(mcode-island): pill click toggles show / hide CLI window (round-14)
antianqi Sep 23, 2026
3b59cd2
fix(mcode-island): toggle show uses SW_MAXIMIZE to fix 480x84 strip b…
antianqi Sep 23, 2026
b922125
fix(mcode-island): toggle show uses MonitorFromWindow + SetWindowPos …
antianqi Sep 23, 2026
b73752b
fix(mcode-island): z-order SetWindowPos carries SWP_NOSIZE (round-17 …
antianqi Sep 24, 2026
ab363d2
feat(mcode-island): tool-verb messages + family tinting (round-18)
antianqi Sep 27, 2026
862f5a1
fix(mcode-island): separate plan/search hues, lock family color distance
antianqi Sep 27, 2026
012628f
feat(mcode-island): ask_user reports waiting, not working
antianqi Sep 27, 2026
1daee4b
fix(mcode-island): redact typed text in tool summaries (round-19 #1)
antianqi Sep 27, 2026
6fe1665
fix(mcode-island): detector preserves sub-step fields across a rewrit…
antianqi Sep 27, 2026
975fc86
fix(mcode-island): restore path needs SWP_NOMOVE, and drops SWP_NOZOR…
antianqi Sep 27, 2026
682f999
fix(mcode-island): post-tool-use passes Step/Total so detail renders …
antianqi Sep 27, 2026
5fbda03
ci(mcode-island): run test-substep-progress on windows-latest (round-…
antianqi Sep 27, 2026
ea96d75
ci(mcode-island): resolve the suite path from the repo root (round-19…
antianqi Sep 27, 2026
12b70cb
fix(mcode-island): round-20 - close a false disclosure claim and two …
antianqi Sep 29, 2026
df3c14d
fix(mcode-island): every non-ASCII .ps1 needs a UTF-8 BOM for PS 5.1 …
antianqi Sep 29, 2026
4876ef1
fix(mcode-island): a detector state write clears a stale sub-step (ro…
antianqi Sep 29, 2026
5e09518
fix(mcode-island): the pill never renders raw tool JSON (round-20 #6)
antianqi Sep 29, 2026
13b90ab
fix(mcode-island): a metadata refresh no longer steals state ownershi…
antianqi Sep 29, 2026
a6a17f3
fix(mcode-island): skip the assignment preamble in shell summaries (r…
antianqi Sep 29, 2026
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
53 changes: 53 additions & 0 deletions .github/workflows/mcode-island-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,56 @@ 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) Behavioral sub-step + redaction suite (round-19 review #5).
#
# scripts/test-substep-progress.mjs existed but no workflow ran it, so
# its 20 assertions never gated anything. It is the only suite that
# executes the PowerShell under test rather than grepping for
# substrings, and it carries the secret canary for review item #1, so
# leaving it unwired meant a redaction regression could land silently.
#
# The suite resolves its own PowerShell: it uses $env:PS_BIN if set,
# otherwise the value if it is an existing path, otherwise it probes
# pwsh / powershell.exe / powershell on PATH and SKIPS the behavioral
# checks loudly if none is found. It never assumes a
# user-specific absolute path.
#
# Negative-injection: restore the `$detail = "$act '$txt'"` line in
# _lib.ps1, or drop SWP_NOMOVE from Toggle-CallerWindow, and this step
# fails on the canary / the flag assertion respectively.
- name: Behavioral sub-step + redaction suite (round-19 requirement #5)
run: |
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
try { chcp 65001 | Out-Null } catch {}

# Report which PowerShell the suite will use. Not a hard requirement:
# the suite does its own resolution and falls back to PATH.
$candidates = @('pwsh', 'powershell.exe', 'powershell')
$chosen = $null
foreach ($c in $candidates) {
if (Get-Command $c -ErrorAction SilentlyContinue) { $chosen = $c; break }
}
if ($chosen) {
Write-Host "PowerShell on PATH: $chosen ($((& $chosen -NoProfile -Command '$PSVersionTable.PSVersion.ToString()')))"
} else {
Write-Host "No PowerShell found on PATH; the suite will fail loudly rather than skip"
}

# Resolve the suite path against the repo root instead of relying on
# the working directory. GitHub Actions runs every `run:` block from
# $GITHUB_WORKSPACE, not from the plugin directory, so a bare
# `scripts/...` path resolves to the wrong place and node exits
# MODULE_NOT_FOUND. It passed locally only because the local shell
# happened to be sitting in the plugin directory.
$suite = 'plugins/antianqi/mcode-island/scripts/test-substep-progress.mjs'
if (-not (Test-Path $suite)) {
throw "$suite not found (cwd: $((Get-Location).Path))"
}
Write-Host "Suite: $((Resolve-Path $suite).Path)"

node $suite
if ($LASTEXITCODE -ne 0) {
throw "test-substep-progress.mjs failed with exit code $LASTEXITCODE"
}
26 changes: 22 additions & 4 deletions plugins/antianqi/mcode-island/.gitattributes
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
# Force LF for all source files in this plugin. PowerShell 5.1 reads
# CRLF fine, but a cross-platform smoke (e.g. Linux CI) sees LF and
# the pre-existing CRLF-handling bug in scripts/validate.mjs trips
# on Windows-checked-out CRLF. LF avoids both failure modes.
# Force LF for all source files in this plugin, so a cross-platform
# smoke (e.g. Linux CI) does not trip the pre-existing CRLF-handling bug
# in scripts/validate.mjs on a Windows-checked-out tree.
#
# IMPORTANT: this policy applies to .mjs / .json / .md, and it is safe
# for .ps1 ONLY because every .ps1 that contains non-ASCII also carries
# a UTF-8 BOM (enforced by smoke check 11).
#
# Round-20 correction: the comment here used to say "PowerShell 5.1 reads
# CRLF fine, so LF avoids both failure modes". That is backwards, and it
# is what hid a real outage. Windows PowerShell 5.1 decodes a BOM-less
# script using the system ANSI codepage, so an LF .ps1 containing
# non-ASCII comments is mis-decoded and the C# here-string in
# mcode-status-detect.ps1 stops being a here-string -- `using System;`
# gets parsed as PowerShell and the detector dies before its first
# statement. Verified against 5.1: both CRLF and a UTF-8 BOM fix the
# parse, bare LF does not. The BOM is used rather than flipping this file
# to CRLF precisely so the Linux-side validator stays protected.
#
# Do NOT "clean up" the BOMs. If you rewrite a .ps1 with an editor that
# drops them, smoke check 11 will fail and any `Copy-Item` sync into the
# install directory will install a dead detector.
#
# Override at clone time: `git config core.autocrlf input` for a
# one-shot pull, or set `[core] autocrlf = false` globally.
Expand Down
28 changes: 25 additions & 3 deletions plugins/antianqi/mcode-island/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,28 @@ alternative:
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State error -Message "npm test failed"
```

For sub-step progress (Computer Use iterative loops, multi-step plans),
pass `-Step` / `-Total` / `-Detail` so the pill shows what the agent is
doing *right now*:

```powershell
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State working -Message "Computer Use" `
-Step 3 -Total 12 -Detail "fill username field"
# → pill renders: "step 3/12 · fill username field"

& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State working -Message "Bash" `
-Step 5 -Detail "npm install"
# → pill renders: "step 5 · npm install"

& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State done -Message "Bash ok"
# → pill renders: "Bash ok" (no step → legacy behavior, backward compat)
```

All three params are optional and backward compatible. The detail field
replaces the message in the rendered pill when present (avoids stacking
"Bash ok · fill username"). See `skills/mcode-island/SKILL.md` for the
full semantics and the contract with the widget renderer.

## Quick start

1. **Install** — copy this folder into your `~/.minimax/plugins/mcode-island/`
Expand Down Expand Up @@ -232,7 +254,7 @@ binary, no symlink, no `node_modules`.
| .NET WPF runtime | 4.x (ships with Windows 10/11) |
| mcode | any version (Mode B works everywhere); 0.2.4+ activates Mode A |
| 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"). |
| network access | **optional** — see "Network access" below. The widget itself is offline. `mcode-status-detect.ps1` only contacts `https://api.minimaxi.com/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. |
| paid services | **none added by this plugin** — the 5h usage endpoint is part of the user's existing MiniMax account, not a separate service |

Expand Down Expand Up @@ -267,7 +289,7 @@ when ALL of the following are true:

When all three are true, the detector makes **one** GET to:

- `https://api.minimax.io/v1/coding_plan/remains` (HTTPS, no credentials in
- `https://api.minimaxi.com/v1/coding_plan/remains` (HTTPS, no credentials in
the URL, no fragment, body is a small JSON object)

The response is parsed and only two numbers are written to
Expand All @@ -292,7 +314,7 @@ the 5-hour usage readout in the pill.
| `config.json:planApiToken` | `set-token.ps1 <token>` | `%APPDATA%\mcode-island\config.json` (plaintext) | `set-token.ps1 -Clear` or edit the file |

The token is **never logged, never written to any other file, and never
sent to a host other than `api.minimax.io`**. `set-token.ps1` only writes
sent to a host other than `api.minimaxi.com`**. `set-token.ps1` only writes
to `config.json`; it makes no network call. The detector only reads the
token to attach as an `Authorization: Bearer ...` header on the single
GET documented above.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# mcode-island: shared library for io.minimax.mcode Hooks scripts.
# mcode-island: shared library for io.minimax.mcode Hooks scripts.
# Loaded via dot-source at the top of each event script:
# . "$PSScriptRoot\_lib.ps1"
# All event scripts under this directory MUST exit 0 (or 2 with a stderr
Expand All @@ -12,6 +12,10 @@ $ErrorActionPreference = 'Stop'
$script:PluginRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path
$script:NotifyIsland = Join-Path $script:PluginRoot 'notify-island.ps1'

# Shared redaction helper (round-20). Lives in a lib because the detector
# is a second, independent producer of the same text and must redact too.
. (Join-Path $script:PluginRoot 'scripts\lib\Protect-Text.ps1')

function Set-ConsoleUtf8 {
# Force UTF-8 so the PowerShell child that mcode spawns reads the
# stdin JSON cleanly. notify-island.ps1 also does this internally,
Expand Down Expand Up @@ -43,7 +47,10 @@ function Push-Island {
[ValidateSet('idle','thinking','working','waiting','done','error')]
[string]$State,

[string]$Message = ''
[string]$Message = '',
[int]$Step = -1,
[int]$Total = -1,
[string]$Detail = ''
)
if (-not (Test-Path -LiteralPath $script:NotifyIsland)) {
# Widget is not installed yet — silent no-op. The plugin's
Expand All @@ -52,7 +59,7 @@ function Push-Island {
return
}
try {
& $script:NotifyIsland -State $State -Message $Message 2>$null | Out-Null
& $script:NotifyIsland -State $State -Message $Message -Step $Step -Total $Total -Detail $Detail 2>$null | Out-Null
} catch {
# Hook must never block the agent on a notification failure.
}
Expand Down Expand Up @@ -97,10 +104,47 @@ function Format-ToolSummary {
'WebSearch' { $detail = [string]$Event.tool_input.query }
'Task' { $detail = [string]$Event.tool_input.description }
'NotebookEdit' { $detail = [string]$Event.tool_input.notebook_path }
# mcode-internal: Computer Use 抽 action + coordinate。
# 例: "mcode-computer-use : click at (1024,768)"
# 注意:coordinate 是 array,PowerShell 默认 $OFS=' ' 会让
# "$coord" 渲染成 "(1024 768)" 不是 "(1024,768)"。必须
# 显式 -join ','。' ' 在 pill 上看起来像数字被截断,
# 影响用户判断坐标。
#
# SECURITY: `tool_input.text` is whatever the user typed. It
# reaches this function verbatim, and its output is written to
# status.json, the append-only island.log, and rendered on an
# always-on-top pill. That surface is shared-screen visible, so
# any password / token / verification code typed through Computer
# Use ends up in a screenshot, a screen share, or a screen
# recording. Never echo the raw text. Report the action and a
# length only, so the pill still answers "is the agent typing?"
# without carrying the secret.
'mcode-computer-use' {
$act = if ($Event.tool_input.action) { [string]$Event.tool_input.action } else { '' }
if ($Event.tool_input.coordinate) {
$coord = $Event.tool_input.coordinate
$coordStr = "($($coord -join ','))"
$detail = "$act at $coordStr"
} elseif ($Event.tool_input.text) {
# Length only. No substring, no length bucketing that
# could leak content shape, no echo of the value.
$len = ([string]$Event.tool_input.text).Length
$detail = "$act <$len chars, redacted>"
} else {
$detail = $act
}
}
default { $detail = '' }
}
}
if ([string]::IsNullOrEmpty($detail)) { return $tool }
# Redact before collapsing/truncating (round-20). This is the single
# choke point for every tool branch above: the result is written to
# status.json, appended to island.log, AND rendered on the pill, so
# redacting here covers all three sinks at once. Without it a Bash
# command like `export API_KEY=sk-...` reached all three verbatim.
$detail = Protect-SecretText $detail
# Collapse newlines, take first 80 chars.
$detail = ($detail -replace "[\r\n]+", ' ').Trim()
if ($detail.Length -gt 80) { $detail = $detail.Substring(0, 77) + '...' }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Hook: PostToolUse
# Hook: PostToolUse
# Event: io.minimax.mcode / PostToolUse
# State: done / error
# Note: Fires after every tool call returns. Heuristic: if the
# tool_result is empty or matches an error pattern, push
# error; otherwise push done. Self-push calls are filtered.
# Per-tool summary (Format-ToolSummary) is split into
# Message="<tool> ok|failed" and Detail=<the rest>, so the pill
# renders "Bash ok · ls -la /tmp" instead of just "Bash ok".
. "$PSScriptRoot\_lib.ps1"
$evt = Read-HookStdin
if (Test-IsSelfPush $evt) { exit 0 }
Expand All @@ -20,9 +23,27 @@ if ($null -eq $result) {
elseif ($s -match '^\s*(Error|ERROR|✕|Error:|\[ERROR\])') { $isError = $true }
}

# Format-ToolSummary 抽 detail,但要剥掉 "tool : " 前缀,只留后半段
$summary = Format-ToolSummary $evt
$detail = ''
if ($summary -and $summary.StartsWith("$tool : ")) {
$detail = $summary.Substring($tool.Length + 3)
} elseif ($summary -and $summary -ne $tool) {
$detail = $summary
}

# Build-DisplayMessage 只在 Step > 0 时才用 detail(Step<=0 直接返回
# Message),所以只传 -Detail 的话 detail 永远不显示
#(round-19 review hetaoBackend #4)。这里给一个确定的 Step/Total,
# 渲染成 "step 1/1 · <detail>"。
# PostToolUse 是单次工具结果,不参与多步序列,所以用 1/1 而不是
# 猜一个更大的分母——猜错会让 pill 显示 "step 1/0"。
$step = 1
$total = 1

if ($isError) {
Push-Island -State error -Message "$tool failed"
Push-Island -State error -Message "$tool failed" -Detail $detail -Step $step -Total $total
} else {
Push-Island -State done -Message "$tool ok"
Push-Island -State done -Message "$tool ok" -Detail $detail -Step $step -Total $total
}
exit 0
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Hook: PreCompact
# Hook: PreCompact
# Event: io.minimax.mcode / PreCompact
# State: thinking
# Note: Fires before the runtime compresses context. We push
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Hook: SessionEnd
# Hook: SessionEnd
# Event: io.minimax.mcode / SessionEnd
# State: idle
# Note: Fires when the runtime terminates a session. We push idle
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Hook: Stop
# Hook: Stop
# Event: io.minimax.mcode / Stop
# State: done
# Note: Fires when the agent finishes a turn (one model response,
Expand Down
Loading
Loading