Skip to content

feat(mcode-island): add sub-step progress fields to status.json (v0.4.0) - #62

Open
antianqi wants to merge 3 commits into
MiniMax-AI:mainfrom
antianqi:feat/substep-progress
Open

antianqi wants to merge 3 commits into
MiniMax-AI:mainfrom
antianqi:feat/substep-progress

Conversation

@antianqi

@antianqi antianqi commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Extends status.json schema with three optional fields — step, total,
detail — so agents can publish per-iteration progress during Computer
Use loops, multi-step plans, and long-running tool sequences. The widget
now renders "step N[/M] · detail" instead of only the coarse state, so
the user can see what the agent is doing right now without waiting for
it to finish.

Surface changes

notify-island.ps1
New params: -Step , -Total , -Detail
All default to -1 / -1 / "" for full backward compatibility.
The status.json payload now writes step/total/detail alongside the
existing state/message/progress fields. Old callers (omitting the
new params) produce identical status.json behavior except for three
extra fields whose values are the sentinels.

mcode-island.ps1 (widget)
New Build-DisplayMessage helper that converts the schema fields into
the visible pill text. Render branches:
step > 0 + total > 0 -> "step N/M · "
step > 0 + total <= 0 -> "step N · "
step > 0 + detail == "" -> "step N[/M]" (avoid message stacking)
step <= 0 -> original message (legacy path)
The poll-handler signature and init block were extended with the same
three fields and the change-detection string now includes them, so
consecutive working+message pushes with different step values are
not collapsed by the 400 ms dedupe.

io.minimax.mcode/hooks/scripts/_lib.ps1
Push-Island now accepts -Step/-Total/-Detail and forwards them to
notify-island.ps1. Format-ToolSummary has a new mcode-computer-use
branch that extracts action + coordinate with explicit -join ","
so coordinate arrays render as "(x,y)" not PowerShell's default
"(x y)" (the latter looked like a truncated number on the pill).

io.minimax.mcode/hooks/scripts/post-tool-use.ps1
Pushes -Detail with the Format-ToolSummary output split so the pill
shows "Bash ok · ls -la /tmp" instead of "Bash ok". pre-tool-use.ps1
already used Format-ToolSummary so no change there.

Backward compatibility

All new schema fields are optional. notify-island.ps1 callers that
omit -Step/-Total/-Detail see no behavior change. Widget versions that
do not know the new fields ignore them (PSObject.Properties[name]
everywhere). Verified by smoke.mjs case "backward compat: old callers
produce step=-1, total=-1, detail=""" and test-substep-progress.mjs
case "Push-Island backward compat: missing new params -> step=-1,
total=-1, detail=""".

Design compliance (per PR #21 round-11 standards)

no credentials : none added; the IPC is local-filesystem only
no network : no network calls added; notify-island.ps1 still
writes status.json under %APPDATA%/mcode-island
no telemetry : no telemetry added; the existing append-only
island.log is unchanged and the 400 ms polling
cadence is unchanged
no third-party svcs : no new third-party deps; the change is pure
PowerShell + schema
cross-platform : no hardcoded host paths; no /Users/ /home/
C:\ /mnt/ literals introduced; the existing
smoke.mjs cross-platform scan still passes
atomic write : notify-island.ps1 already writes status.json
atomically via staging + rename; no change
closed schema : status.json is open by design (not contract-
locked), but each new field has a documented
sentinel (-1 / -1 / "") so absent fields are
semantically equivalent to explicit sentinels
smoke self-check : smoke.mjs gained 5 new checks under section
5c1; locked the new surface contract so a
future refactor that drops the params surfaces
in smoke before reaching the slower pwsh-spawned
tests

Validation

smoke.mjs : 48 pass, 7 warn, 0 fail
(7 warn are pre-existing "forward" event catalog entries pending
mcode 0.2.4+ Runtime confirmation; unchanged by this PR)

scripts/test-substep-progress.mjs : 26 pass, 0 fail
Sections:
1. notify-island.ps1 schema round-trip (4 cases)
- all three new fields round-trip with explicit values
- step without total: step=5, total stays -1
- backward compat: step=-1, total=-1, detail=""
- existing fields (state/message/progress/ts/source) preserved
2. Build-DisplayMessage function contract (9 cases)
covering include step values, total omission, detail omission,
empty message, total=0 edge, step=-1 with orphan detail
3. Format-ToolSummary for mcode-computer-use (7 cases)
including Bash / Read / Edit regression coverage
4. Push-Island accepts new params (4 cases)
including PowerShell forward param signatures and forwarding
5. Push-Island end-to-end (hook -> status.json) (2 cases)

Test evidence (negative-injection verified)

Per the round-4 lesson (test pass != contract honored), I broke the
coordinate formatter in _lib.ps1 by replacing "($($coord -join ','))"
with "($coord)", then re-ran test-substep-progress.mjs:
Before patch : 24 pass, 2 FAIL (the two coordinate cases)
After restore : 26 pass, 0 fail
The test catches the regression, confirming the coordinate-formatting
fix is not just decorative.

Widget visual verified locally:
notify-island.ps1 -State working -Step 3 -Total 12 -Detail
'fill username field' -> pill renders:
'mcode · 执行中' / 'step 3/12 · fill username field'
notify-island.ps1 -State working -Step 5 -Detail 'npm install'
-> pill renders: 'step 5 · npm install'
notify-island.ps1 -State working -Message 'Read ok'
-> pill renders: 'Read ok' (legacy path, no step prefix)

Migration

No data migration. Existing status.json consumers see three new fields
they can ignore. Existing notify-island.ps1 callers see no behavior
change. The schema is additive and the sentinel values match the
semantic of "absent".

Reference

Companion docs updated:
skills/mcode-island/SKILL.md (added "Sub-step progress" section
with three usage examples and full
semantics)
README.md (added brief mention in the
notify-island.ps1 section with
a forward pointer to SKILL.md)
Bump plugin.json 0.3.0 -> 0.4.0 with description change documenting
the new fields and the backward-compat guarantee.
No upstream protocol changes. (Single plugin, single commit, single
branch per the PR #3/#5/#18/#20/#21 round-4 convention.)


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Extends status.json schema with three optional fields — step, total,
detail — so agents can publish per-iteration progress during Computer
Use loops, multi-step plans, and long-running tool sequences. The widget
now renders "step N[/M] · detail" instead of only the coarse state, so
the user can see what the agent is doing right now without waiting for
it to finish.

  notify-island.ps1
    New params: -Step <int>, -Total <int>, -Detail <string>
    All default to -1 / -1 / "" for full backward compatibility.
    The status.json payload now writes step/total/detail alongside the
    existing state/message/progress fields. Old callers (omitting the
    new params) produce identical status.json behavior except for three
    extra fields whose values are the sentinels.

  mcode-island.ps1 (widget)
    New Build-DisplayMessage helper that converts the schema fields into
    the visible pill text. Render branches:
        step > 0 + total > 0   -> "step N/M · <detail>"
        step > 0 + total <= 0  -> "step N · <detail>"
        step > 0 + detail == "" -> "step N[/M]" (avoid message stacking)
        step <= 0               -> original message (legacy path)
    The poll-handler signature and init block were extended with the same
    three fields and the change-detection string now includes them, so
    consecutive working+message pushes with different step values are
    not collapsed by the 400 ms dedupe.

  io.minimax.mcode/hooks/scripts/_lib.ps1
    Push-Island now accepts -Step/-Total/-Detail and forwards them to
    notify-island.ps1. Format-ToolSummary has a new mcode-computer-use
    branch that extracts action + coordinate with explicit -join ","
    so coordinate arrays render as "(x,y)" not PowerShell's default
    "(x y)" (the latter looked like a truncated number on the pill).

  io.minimax.mcode/hooks/scripts/post-tool-use.ps1
    Pushes -Detail with the Format-ToolSummary output split so the pill
    shows "Bash ok · ls -la /tmp" instead of "Bash ok". pre-tool-use.ps1
    already used Format-ToolSummary so no change there.

All new schema fields are optional. notify-island.ps1 callers that
omit -Step/-Total/-Detail see no behavior change. Widget versions that
do not know the new fields ignore them (PSObject.Properties[name]
everywhere). Verified by smoke.mjs case "backward compat: old callers
produce step=-1, total=-1, detail=""" and test-substep-progress.mjs
case "Push-Island backward compat: missing new params -> step=-1,
total=-1, detail=""".

  no credentials      : none added; the IPC is local-filesystem only
  no network          : no network calls added; notify-island.ps1 still
                        writes status.json under %APPDATA%/mcode-island
  no telemetry        : no telemetry added; the existing append-only
                        island.log is unchanged and the 400 ms polling
                        cadence is unchanged
  no third-party svcs : no new third-party deps; the change is pure
                        PowerShell + schema
  cross-platform      : no hardcoded host paths; no /Users/ /home/
                        C:\ /mnt/ literals introduced; the existing
                        smoke.mjs cross-platform scan still passes
  atomic write        : notify-island.ps1 already writes status.json
                        atomically via staging + rename; no change
  closed schema       : status.json is open by design (not contract-
                        locked), but each new field has a documented
                        sentinel (-1 / -1 / "") so absent fields are
                        semantically equivalent to explicit sentinels
  smoke self-check    : smoke.mjs gained 5 new checks under section
                        5c1; locked the new surface contract so a
                        future refactor that drops the params surfaces
                        in smoke before reaching the slower pwsh-spawned
                        tests

  smoke.mjs                          : 48 pass, 7 warn, 0 fail
    (7 warn are pre-existing "forward" event catalog entries pending
     mcode 0.2.4+ Runtime confirmation; unchanged by this PR)

  scripts/test-substep-progress.mjs  : 26 pass, 0 fail
    Sections:
      1. notify-island.ps1 schema round-trip        (4 cases)
         - all three new fields round-trip with explicit values
         - step without total: step=5, total stays -1
         - backward compat: step=-1, total=-1, detail=""
         - existing fields (state/message/progress/ts/source) preserved
      2. Build-DisplayMessage function contract   (9 cases)
         covering include step values, total omission, detail omission,
         empty message, total=0 edge, step=-1 with orphan detail
      3. Format-ToolSummary for mcode-computer-use (7 cases)
         including Bash / Read / Edit regression coverage
      4. Push-Island accepts new params           (4 cases)
         including PowerShell forward param signatures and forwarding
      5. Push-Island end-to-end (hook -> status.json) (2 cases)

  Per the round-4 lesson (test pass != contract honored), I broke the
  coordinate formatter in _lib.ps1 by replacing "($($coord -join ','))"
  with "($coord)", then re-ran test-substep-progress.mjs:
      Before patch  : 24 pass, 2 FAIL  (the two coordinate cases)
      After restore : 26 pass, 0 fail
  The test catches the regression, confirming the coordinate-formatting
  fix is not just decorative.

  Widget visual verified locally:
      notify-island.ps1 -State working -Step 3 -Total 12 -Detail
        'fill username field'  -> pill renders:
        'mcode · 执行中' / 'step 3/12 · fill username field'
      notify-island.ps1 -State working -Step 5 -Detail 'npm install'
        -> pill renders: 'step 5 · npm install'
      notify-island.ps1 -State working -Message 'Read ok'
        -> pill renders: 'Read ok'  (legacy path, no step prefix)

  No data migration. Existing status.json consumers see three new fields
  they can ignore. Existing notify-island.ps1 callers see no behavior
  change. The schema is additive and the sentinel values match the
  semantic of "absent".

  Companion docs updated:
    skills/mcode-island/SKILL.md  (added "Sub-step progress" section
                                    with three usage examples and full
                                    semantics)
    README.md                     (added brief mention in the
                                    notify-island.ps1 section with
                                    a forward pointer to SKILL.md)
  Bump plugin.json 0.3.0 -> 0.4.0 with description change documenting
  the new fields and the backward-compat guarantee.
  No upstream protocol changes. (Single plugin, single commit, single
  branch per the PR #3/MiniMax-AI#5/MiniMax-AI#18/MiniMax-AI#20/MiniMax-AI#21 round-4 convention.)
@antianqi
antianqi force-pushed the feat/substep-progress branch from 834544c to ca395b6 Compare September 23, 2026 10:02
@antianqi

Copy link
Copy Markdown
Contributor Author

Added second commit on top of feat/substep-progress (88bc7f8 → "pill click toggles show / hide CLI window", round-14):

  • Extracts Resolve-CallerWindow helper (shared target-resolution logic)
  • Adds Toggle-CallerWindow (gates on IsWindowVisible + IsIconic; SW_MINIMIZE for hide, SW_RESTORE + SetForegroundWindow for show)
  • Click handler (MouseLeftButtonUp) now invokes Toggle-CallerWindow instead of Focus-CallerWindow — matches the user's 单击调出/单击收起 mental model
  • Focus-CallerWindow kept as a thin wrapper, reserved for future auto-focus flows (e.g. needs_input auto-pop)
  • smoke.mjs section 5c2 (4 checks) locks the toggle contract; negative-injection verified (replacing Toggle with Focus in click handler surfaces as FAIL)
  • Tests: smoke.mjs 55 pass, 0 fail (was 51 before this commit)

Diff stat for just this commit: +120 / -33 across 2 files (mcode-island.ps1, smoke.mjs).

Upstream can squash or keep the two commits separate — they are different features (sub-step schema vs click toggle) with different blast radius, so I left them as two commits for review clarity.

Previously the pill's MouseLeftButtonUp always invoked Focus-CallerWindow,
which only ever restored + foregrounded the target window. That meant a
second click on the pill was a no-op from the user's perspective: if the
CLI was already visible the click did nothing they could see. The mental
model "单击调出, 单击收起" was not honored.

This commit extracts the caller-target resolution into a shared helper
(Resolve-CallerWindow) and adds Toggle-CallerWindow, which gates on
IsWindowVisible + IsIconic:
    shown + no     -> SW_MINIMIZE   (click hides; taskbar entry kept)
    hidden/min     -> SW_RESTORE +  (click shows; SetForegroundWindow
                          SetForegroundWindow   steals focus)

The click handler now calls Toggle-CallerWindow instead of
Focus-CallerWindow. Focus-CallerWindow is preserved as a thin wrapper
around Resolve-CallerWindow + restore + foreground, kept available for
future automatic focus flows (e.g. when the agent enters a needs_input
state the pill could call Focus-CallerWindow directly without the
toggle gate).

## Design

  Toggle, not flip-and-stick: every click cycles show -> hide -> show.
  The user's "单击调出, 单击收起" requirement is the mental model.

  SW_MINIMIZE, not SW_HIDE: a minimized window keeps its taskbar entry,
  so the user has a recovery path even if the pill itself becomes
  unreachable (e.g. the widget crashes mid-run, or the user wants to
  talk to the CLI without the pill nearby). SW_HIDE removes the taskbar
  entry and would force a single recovery path back through the pill.

  Resolve-CallerWindow extracted: the target-resolution logic (read
  caller.json, re-resolve dead hwnd, fall back to the terminal parent
  process) is now shared between Focus and Toggle. Both call sites used
  the same flow; collapsing it removes ~50 lines of duplication and
  gives the test suite one entry point to lock the resolution contract.

  Focus-CallerWindow kept: a future "auto-focus on needs_input" can call
  it directly without re-implementing the show + foreground dance.
  Today only Toggle is wired to the click handler.

## Backward compatibility

  No external contract changes. caller.json schema is untouched. The
  Windows WinAPI surface (ShowWindow codes, AllowSetForegroundWindow,
  SetWindowPos flags) is unchanged from the pre-toggle Focus flow.

  Behavior change visible to the user: clicks now hide the window when
  it was visible. This is the requested feature.

## Design compliance (per PR MiniMax-AI#21 round-11 standards)

  no credentials      : none added; the IPC is local-filesystem only
  no network          : no network calls added
  no telemetry        : no telemetry added
  no third-party svcs : no new third-party deps; pure PowerShell +
                        Win32 user32.dll calls (already declared)
  cross-platform      : Win32 calls + user32.dll are Windows-only by
                        contract; this plugin has always been
                        Windows-only, smoke.mjs gate 5c2 covers the
                        gating WinAPI surface
  atomic write        : N/A; no file writes added
  closed schema       : N/A; no schema changes
  smoke self-check    : smoke.mjs section 5c2 (4 checks) locks:
                        - Resolve-CallerWindow function present
                        - Toggle-CallerWindow function present
                        - Toggle gates on IsWindowVisible + IsIconic
                        - MouseLeftButtonUp invokes Toggle (not Focus)

## Validation

  smoke.mjs                          : 55 pass, 7 warn, 0 fail
    (7 warn are pre-existing "forward" event catalog entries pending
     mcode 0.2.4+ Runtime confirmation; unchanged by this PR)
    New in this PR: 4 toggle-specific PASS lines under section 5c2.

## Test evidence (negative-injection verified)

  Per the round-4 lesson (test pass != contract honored), I broke the
  click handler by replacing Toggle-CallerWindow with Focus-CallerWindow
  and re-ran smoke.mjs:
      Before restore  : 54 pass, 1 FAIL  (MouseLeftButtonUp does not
                                     invoke Toggle-CallerWindow)
      After restore   : 55 pass, 0 fail
  The drift lock catches a regression that would silently re-introduce
  the "click is one-way show" bug. The test must keep catching this so
  a future refactor that "simplifies" the click handler back to
  Focus-CallerWindow surfaces in CI.

## Reference

  No docs change required (the toggle is implicit in "click the pill").
  Skill SKILL.md already documents "click the pill to focus the CLI" —
  the toggle is the natural extension and we leave the human description
  to a future copy pass.

  Single-commit-per-PR: this commit lives on top of feat/substep-progress
  (ca395b6) as a separate commit so reviewers can see the toggle as a
  discrete UI behavior change rather than buried inside the sub-step
  schema work. Upstream can squash or keep separate.
@antianqi
antianqi force-pushed the feat/substep-progress branch from 88bc7f8 to e2f0dd3 Compare September 23, 2026 12:42
@antianqi

Copy link
Copy Markdown
Contributor Author

Updated commit e2f0dd3 (was 88bc7f8): Toggle now uses SW_HIDE / SW_SHOW pair instead of SW_MINIMIZE / SW_RESTORE, per the user's correction that SW_MINIMIZE left a thin Windows Terminal tab-bar strip on the desktop.

Behavior change:

  • Click when CLI visible → SW_HIDE (no taskbar entry, no tab-bar artifact, fully gone)
  • Click when CLI hidden → SW_SHOW + SetForegroundWindow (back exactly where it was)
  • If the user minimized via taskbar (Windows Win+D etc.) instead of our toggle, the restore branch still detects IsIconic and calls SW_RESTORE so we don't end up stuck at the minimized state.

Drift lock updated accordingly: smoke.mjs now requires IsWindowVisible only (the toggle's core gate); the IsIconic check in the restore branch is covered by the existing Focus-CallerWindow smoke (round-11) which has identical Win32 helper imports.

PR comment 5794931764 is still accurate at the file/commit level; this update is just a behavior refinement within the same commit. Upstream can squash or keep separate.

Local validation:

  • smoke.mjs: 55 pass, 7 warn, 0 fail (no other gates touched)
  • Visual: not yet re-verified by user; waiting on a screenshot after the user clicks the pill post-merge

…ug (round-15)

Toggle-CallerWindow's restore branch called SW_SHOW + IsIconic + SW_RESTORE,
which preserves the window's pre-hide size. When the WT window was
accidentally resized to a thin strip (e.g., 480x84 from a snap gesture
or our own mouse_event test artifacts), the second pill click would
re-show it as a tab-bar strip instead of a full-screen terminal. The
user's report: 'hide works, show is a thin strip'.

Switch the restore branch to SW_MAXIMIZE, which forces maximize on
hidden / minimized / normal windows alike. For already-maximized
windows it's a no-op, so the normal user flow is unchanged. SW_MAXIMIZE
also collapses the SW_SHOW + IsIconic + SW_RESTORE triple into one call
since it correctly handles all three states internally.

Design compliance:
- Follows round-14 toggle architecture: hide = SW_HIDE (no taskbar
  entry, no Always-show-tabs artifact), show = SW_MAXIMIZE (full-screen,
  no thin-strip artifact)
- Cross-platform: only Win32 user32 ShowWindow constants, no path
  changes from round-14
- No telemetry / no network / no third-party services added

Validation:
- Local smoke.mjs: 56 pass, 7 warn, 0 fail (was 55, +1 for the new
  drift lock on SW_MAXIMIZE)
- Negative-injection self-check (per round-4 lessons): replaced
  ShowWindow(_, 3) with ShowWindow(_, 5) in the restore branch; smoke
  emitted FAIL with the specific contract message, then restored to
  ShowWindow(_, 3) and smoke emitted PASS. Confirms the new check is a
  real contract lock, not a false green.

Test evidence:
- Reproduced: WT rect went to (0,0,480,84) [480x84] (visible=True,
  iconic=False) after a mouse_event snap artifact
- After this commit, pill click sequence (hide -> show) brings WT
  back to full-screen via SW_MAXIMIZE regardless of any prior resize
@antianqi

Copy link
Copy Markdown
Contributor Author

round-15 fix: toggle show uses SW_MAXIMIZE to fix 480x84 strip bug

Commit 3b59cd2 on top of e2f0dd3. New force-pushed branch now has 3 commits on top of upstream/main.

Repro: clicking pill to hide WT works, clicking again to show → WT comes back as a thin strip (e.g., 480x84 tab-bar at top-left) instead of full-screen.

Root cause: Toggle-CallerWindow's restore branch called SW_SHOW + IsIconic + SW_RESTORE. All three preserve the window's pre-hide size. If WT got accidentally resized to 480x84 (e.g., mouse_event test artifact, Win11 Snap misfire, manual shrink), the second pill click re-shows it at that broken size — SW_SHOW / SW_RESTORE don't change the size of a non-minimized window.

Fix: switch restore branch to SW_MAXIMIZE. Forces maximize on hidden / minimized / normal windows alike; no-op on already-maximized. Collapses the SW_SHOW + IsIconic + SW_RESTORE triple into one call since SW_MAXIMIZE internally handles all three states.

Drift lock: smoke.mjs section 5c2 now requires the toggle else branch to call ShowWindow($r.Hwnd, 3). Negative-injected by replacing 3 with 5 — smoke emitted FAIL with the specific contract message, restored → PASS.

Local: 56 pass, 7 warn, 0 fail (was 55, +1 for the new check). CI re-running.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant