Commit a8ecc57
authored
Add mcode-island plugin: Windows Dynamic Island status pill for MiniMax Code agents
* Add mcode-island plugin: Windows Dynamic Island status pill for MiniMax Code agents
Adds a Skill-first plugin that surfaces the agent working state in a 320x60 WPF pill anchored to the top center of the primary display, so the user can leave the terminal in the background and still watch progress.
States: idle / thinking / working / waiting / done / error.
Includes wrap-tool.ps1, a thin bash wrapper that pushes working / done / error / waiting based on $LASTEXITCODE, so the user does not have to remember to call notify-island.ps1 for every shell command.
* Add mcode-status-detect v0.2.0: state inference from mcode session log
Adds a 1-second-polling daemon that reads the active mcode session messages.jsonl and infers the agent state (idle/thinking/working/done/error) without requiring the agent to call notify-island.ps1.
State mapping:
role=user -> idle
role=assistant + toolCall -> working "<tool>: <args>"
role=assistant + thinking -> thinking
role=assistant + text -> idle (just replied)
role=toolResult + !isError -> done "<tool> 完成"
role=toolResult + isError -> error "<tool> 失败"
mcode 进程不在 -> error "mcode 进程已退出"
60s 无新事件 -> idle 兑底
Priority logic: agent-pushed states (with Message) are preserved; detector takes over only for settle states (idle / error).
Tested on Windows 11 24H2 + PowerShell 5.1 against a live mcode session. All 6 state transitions verified, including mcode exit and recovery.
* fix: address review feedback on PR #17 (v0.2.1)
Fixes for review comments from hetaoBackend (commit fce7c5f):
#1 detector hard-coded path: resolve the [userprofile]/.minimax-code
directory at runtime via the mcode node process cmdline (regex on
@minimax-ai/code/cli.js), with fallbacks to $env:USERPROFILE/.minimax-code,
$env:APPDATA/minimax-code, and the current working directory.
Override with -Root [path].
#2 idle fallback unreachable: mtime cache now returns the last inferred
message instead of null, so the 60s stale -> idle branch fires every
poll. Verified locally: idle :: already idle 195s after 65s of inactivity.
#2b session log: prefer ledger.jsonl (mcode v2 event stream) and fall
back to messages.jsonl when ledger is missing. Both formats are handled
in Infer-State (kind/phase for ledger, message.role for messages).
#3 PID reuse safety: start/stop-{island,detect-island}.ps1 now verify
the target PID command line contains the expected script path before
acting. Stale PIDs and PID-reused processes are refused with a
REFUSED log line instead of being killed.
#4 wrap-tool.ps1 shell-injection: removed Invoke-Expression entirely.
The wrapper is now status-only; the agent runs the command via mcode's
own bash tool and passes -ExitCode to publish the outcome.
Documented in README + SKILL.md.
#5 README: -Enable -> -Action Enable to match autostart.ps1 parameter set.
#6 start-island.ps1 readiness: dropped the 'about to ShowDialog' log wait
(which was never emitted). Now polls MainWindowHandle != 0 every 500ms
for up to 8s.
Tests: validator reports OK plugin antianqi/mcode-island. wrap-tool
6-state matrix verified locally (working / done / waiting / error).
* fix(mcode-island): pick most-recently-touched session file (ledger vs messages)
Get-LatestSessionFile always preferred ledger.jsonl when present, regardless
of which file was more recently written. On systems where mcode v0.2.x left
behind a stale ledger.jsonl from a previous session, the detector would
read the old ledger every poll, the 60s idle-fallback would fire against
an ancient mtime, and the widget would stay stuck on "已静默 NNNNNs"
forever (verified: 49549s = 13.76h against a ledger that was actually
{"action":"test ledger 1"} test residue).
Fix: compare mtimes and pick whichever is newer. Fall back to ledger if
messages is absent (original fallback contract), but never let a stale
ledger shadow a live messages.jsonl.
Triggered by PR #17 review testing: 9 hours of "idle :: 已静默 49549s"
on a fresh detector after the v0.2.1 fixes were deployed.
* fix(mcode-island): tag notify-island status writes with source='agent'
notify-island.ps1 was writing status.json with only {state, message,
progress, ts} and no source field. The detector's takeover logic keys
off `cur.source -eq 'detector'` to decide whether the live entry is its
own or an externally-pushed one. With no source field on agent-pushed
states, the detector treated every agent push as "no current status" and
immediately overwrote it with whatever it had just inferred — most often
idle (60s fallback), even when the agent had just pushed `working` or
`thinking`.
Concretely: pushing `notify-island.ps1 -State working` would survive for
roughly 1 second before the detector's next poll clobbered it back to
idle. This made the manual notify tool useless for any state the detector
cares about, and made the `wrap-tool.ps1 -State working` wrap pattern
invisible on the pill.
Fix: add `source = 'agent'` to the payload. With it set, the detector's
existing precedence rules work as documented:
- agent push of working/thinking/done → preserved (not overwritten by
the same-state detector inference, since detector-inferred
working/thinking/done is not "settled" and does not trigger the
takeover branch when the current entry is not the detector's own);
- agent push of idle/error → can be taken over by detector's
idle/error inference, matching the original "detector settles agent"
contract.
Verified live: `notify-island.ps1 -State thinking` now persists across
multiple detector polls (ts unchanged after 3.5s, message intact,
source field present).
Pushed on top of 6e99c0b on add-mcode-island.
* fix(mcode-island): kill pipeline-thread leak in detector hot loop
The detector polled once per second, and every poll walked ~15 pipeline
cmdlets: Get-ChildItem -Recurse | Where-Object | Sort-Object |
Select-Object (×2), Get-Content -Raw | ConvertFrom-Json (×3-4),
$collection | Where-Object (×3), Get-Process (×1-2), etc. PS 5.1 hidden
window has a known issue where completed pipeline tasks aren't
immediately released back to the Runspace thread pool — the pool backs
up over multi-hour runs. After ~9 hours of polling, the process was
holding ~30k threads and Get-ChildItem was effectively starved:
status.json stopped updating, island.log stopped appending, the
process looked alive but the loop was no longer advancing. Only a
restart recovered it.
Fix in three layers:
1. Replace the most expensive pipeline calls with direct .NET method
calls so no Runspace hop is incurred:
- Get-LatestSessionFile: Get-ChildItem -Recurse | Where-Object |
Sort-Object | Select-Object → a single
[System.IO.Directory]::EnumerateFiles + manual mtime scan
- Get-McodePid: Get-ChildItem | foreach { Get-Content |
ConvertFrom-Json | Get-Process } → EnumerateFiles + File.ReadAllText
+ Process.GetProcessById
- Read-LastMessage: Get-Item → [System.IO.FileInfo]::new(...)
- Read-StatusObj: Get-Content -Raw → File.ReadAllText
- Infer-State (assistant branch): $m.content | Where-Object ×3 →
one foreach loop with early exit (toolCall wins, no need to scan
the rest)
2. Add a 5s TTL cache for both `mcodePid` and `latestSessionFilePath`
in the main loop. mcode doesn't churn sub-second, and a fresh
session log only shows up when mcode itself starts a new session,
which is also a sub-5s event in practice. 5s is a comfortable
upper bound that cuts the heavy directory enumeration to once per
5s without losing visible state fidelity (the existing mtime gate
in Read-LastMessage already gates re-parse on real content
changes, so cache staleness is invisible to the user).
3. Verified live: after the fix, restarting the detector and running
for 30s reports 18-28 threads (was previously climbing into the
thousands within minutes). State transitions (working → done →
working) still fire correctly. The 60s-idle fallback still fires
correctly.
Side benefit: the refactor also fixes a tiny correctness wart in
Get-McodePid — when multiple .json files happen to coexist in
.mcode-active (e.g. during a restart overlap), the previous code
returned the first hit; the new code picks the most-recently-touched
one, which matches what Get-LatestSessionFile does on the messages
side.
Pushed on top of db73c11 on add-mcode-island.
---------
Co-authored-by: antianqi <antianqi@users.noreply.github.com>1 parent 31e3e4c commit a8ecc57
25 files changed
Lines changed: 2167 additions & 0 deletions
File tree
- plugins/antianqi/mcode-island
- assets
- skills/mcode-island
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
0 commit comments