Skip to content

Commit 834544c

Browse files
committed
feat(mcode-island): add sub-step progress fields to status.json (v0.4.0)
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/#5/#18/#20/#21 round-4 convention.)
1 parent 6481e4a commit 834544c

9 files changed

Lines changed: 580 additions & 17 deletions

File tree

‎plugins/antianqi/mcode-island/README.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,28 @@ alternative:
156156
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State error -Message "npm test failed"
157157
```
158158

159+
For sub-step progress (Computer Use iterative loops, multi-step plans),
160+
pass `-Step` / `-Total` / `-Detail` so the pill shows what the agent is
161+
doing *right now*:
162+
163+
```powershell
164+
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State working -Message "Computer Use" `
165+
-Step 3 -Total 12 -Detail "fill username field"
166+
# → pill renders: "step 3/12 · fill username field"
167+
168+
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State working -Message "Bash" `
169+
-Step 5 -Detail "npm install"
170+
# → pill renders: "step 5 · npm install"
171+
172+
& "%PLUGIN_DIR%\mcode-island\notify-island.ps1" -State done -Message "Bash ok"
173+
# → pill renders: "Bash ok" (no step → legacy behavior, backward compat)
174+
```
175+
176+
All three params are optional and backward compatible. The detail field
177+
replaces the message in the rendered pill when present (avoids stacking
178+
"Bash ok · fill username"). See `skills/mcode-island/SKILL.md` for the
179+
full semantics and the contract with the widget renderer.
180+
159181
## Quick start
160182

161183
1. **Install** — copy this folder into your `~/.minimax/plugins/mcode-island/`

‎plugins/antianqi/mcode-island/io.minimax.mcode/hooks/scripts/_lib.ps1‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ function Push-Island {
4343
[ValidateSet('idle','thinking','working','waiting','done','error')]
4444
[string]$State,
4545

46-
[string]$Message = ''
46+
[string]$Message = '',
47+
[int]$Step = -1,
48+
[int]$Total = -1,
49+
[string]$Detail = ''
4750
)
4851
if (-not (Test-Path -LiteralPath $script:NotifyIsland)) {
4952
# Widget is not installed yet — silent no-op. The plugin's
@@ -52,7 +55,7 @@ function Push-Island {
5255
return
5356
}
5457
try {
55-
& $script:NotifyIsland -State $State -Message $Message 2>$null | Out-Null
58+
& $script:NotifyIsland -State $State -Message $Message -Step $Step -Total $Total -Detail $Detail 2>$null | Out-Null
5659
} catch {
5760
# Hook must never block the agent on a notification failure.
5861
}
@@ -97,6 +100,26 @@ function Format-ToolSummary {
97100
'WebSearch' { $detail = [string]$Event.tool_input.query }
98101
'Task' { $detail = [string]$Event.tool_input.description }
99102
'NotebookEdit' { $detail = [string]$Event.tool_input.notebook_path }
103+
# mcode-internal: Computer Use 抽 action + coordinate/text
104+
# 例: "mcode-computer-use : click at (1024,768)"
105+
# "mcode-computer-use : type 'hello'"
106+
# 注意:coordinate 是 array,PowerShell 默认 $OFS=' ' 会让
107+
# "$coord" 渲染成 "(1024 768)" 不是 "(1024,768)"。必须
108+
# 显式 -join ','。' ' 在 pill 上看起来像数字被截断,
109+
# 影响用户判断坐标。
110+
'mcode-computer-use' {
111+
$act = if ($Event.tool_input.action) { [string]$Event.tool_input.action } else { '' }
112+
if ($Event.tool_input.coordinate) {
113+
$coord = $Event.tool_input.coordinate
114+
$coordStr = "($($coord -join ','))"
115+
$detail = "$act at $coordStr"
116+
} elseif ($Event.tool_input.text) {
117+
$txt = [string]$Event.tool_input.text
118+
$detail = "$act '$txt'"
119+
} else {
120+
$detail = $act
121+
}
122+
}
100123
default { $detail = '' }
101124
}
102125
}

‎plugins/antianqi/mcode-island/io.minimax.mcode/hooks/scripts/post-tool-use.ps1‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# Note: Fires after every tool call returns. Heuristic: if the
55
# tool_result is empty or matches an error pattern, push
66
# error; otherwise push done. Self-push calls are filtered.
7+
# Per-tool summary (Format-ToolSummary) is split into
8+
# Message="<tool> ok|failed" and Detail=<the rest>, so the pill
9+
# renders "Bash ok · ls -la /tmp" instead of just "Bash ok".
710
. "$PSScriptRoot\_lib.ps1"
811
$evt = Read-HookStdin
912
if (Test-IsSelfPush $evt) { exit 0 }
@@ -20,9 +23,18 @@ if ($null -eq $result) {
2023
elseif ($s -match '^\s*(Error|ERROR|✕|Error:|\[ERROR\])') { $isError = $true }
2124
}
2225

26+
# Format-ToolSummary 抽 detail,但要剥掉 "tool : " 前缀,只留后半段
27+
$summary = Format-ToolSummary $evt
28+
$detail = ''
29+
if ($summary -and $summary.StartsWith("$tool : ")) {
30+
$detail = $summary.Substring($tool.Length + 3)
31+
} elseif ($summary -and $summary -ne $tool) {
32+
$detail = $summary
33+
}
34+
2335
if ($isError) {
24-
Push-Island -State error -Message "$tool failed"
36+
Push-Island -State error -Message "$tool failed" -Detail $detail
2537
} else {
26-
Push-Island -State done -Message "$tool ok"
38+
Push-Island -State done -Message "$tool ok" -Detail $detail
2739
}
2840
exit 0

‎plugins/antianqi/mcode-island/mcode-island.ps1‎

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -370,13 +370,49 @@ function Stop-IndeterminateShimmer {
370370
$script:progressShimmerTransform.X = -130 # 重置到起点
371371
}
372372

373+
# Sub-step 渲染("step N[/M] · detail"):
374+
# Step > 0 + Total > 0 → "step 3/12 · fill username"
375+
# Step > 0 + Total <= 0 → "step 3 · fill username"
376+
# Step > 0 + Detail 空 → "step 3/12"
377+
# Step <= 0 → 原 Message 字段
378+
# 这样 message 字段保持"工具名"("Bash ok"),detail 字段填具体动作,
379+
# 渲染时拼成 "step 3/12 · Bash ok · fill username" 或者更精确的
380+
# "step 3/12 · fill username"(detail 存在时优先覆盖 message)。
381+
# 注意:detail 非空时**完全替换** message,避免双重信息("Bash ok · ls -la")。
382+
function Build-DisplayMessage {
383+
param(
384+
[string]$Message,
385+
[int]$Step,
386+
[int]$Total,
387+
[string]$Detail
388+
)
389+
$base = if ($Message) { $Message } else { '' }
390+
if ($Step -le 0) { return $base }
391+
392+
$stepStr = if ($Total -gt 0) { "step $Step/$Total" } else { "step $Step" }
393+
if ($Detail) {
394+
# detail 非空时优先用 detail(agent 已经表达了"我在做什么")
395+
return "$stepStr · $Detail"
396+
}
397+
# detail 空但 step 给出 → 只显示 step,避免重复 message 造成噪声
398+
return $stepStr
399+
}
400+
373401
# 状态更新
374402
# Progress 取值约定(跟 notify-island.ps1 / detector 对齐):
375403
# -1 → 没有进度信息,进度条隐藏
376404
# 0..100 → 百分比,0=空条,100=满条;超出范围会被 clamp
377405
# Usage5h:剩余百分比(0..100);-2 = 未提供
378406
# Usage5hResetMs:距下次 5h 刷新的毫秒数;0 = 未知
379407
# TodoProgress:todowrite 列表的完成百分比(0..100);-2 = 未提供
408+
# Step/Total/Detail:sub-step 进度(agent 自报),参 Build-DisplayMessage
409+
# - 优先级:显式 Progress > TodoProgress > shimmer
410+
# - 即:agent 直接传 progress 最高;否则如果有 todo 列表就用 todo 完成度;都没就 shimmer 动画
411+
# -1 → 没有进度信息,进度条隐藏
412+
# 0..100 → 百分比,0=空条,100=满条;超出范围会被 clamp
413+
# Usage5h:剩余百分比(0..100);-2 = 未提供
414+
# Usage5hResetMs:距下次 5h 刷新的毫秒数;0 = 未知
415+
# TodoProgress:todowrite 列表的完成百分比(0..100);-2 = 未提供
380416
# - 优先级:显式 Progress > TodoProgress > shimmer
381417
# - 即:agent 直接传 progress 最高;否则如果有 todo 列表就用 todo 完成度;都没就 shimmer 动画
382418
function Update-State {
@@ -386,14 +422,17 @@ function Update-State {
386422
[int]$Progress = -1,
387423
[int]$Usage5h = -2,
388424
[int]$Usage5hResetMs = 0,
389-
[int]$TodoProgress = -2
425+
[int]$TodoProgress = -2,
426+
[int]$Step = -1,
427+
[int]$Total = -1,
428+
[string]$Detail = ''
390429
)
391430
$s = $script:stateMap[$State]
392431
if (!$s) { $s = $script:stateMap['idle'] }
393432
$script:statusDot.Fill = C $s.dot
394433
$script:pulseRing.Fill = C $s.ring
395434
$script:stateText.Text = $s.label
396-
$script:messageText.Text = if ($Message) { $Message } else { '' }
435+
$script:messageText.Text = Build-DisplayMessage -Message $Message -Step $Step -Total $Total -Detail $Detail
397436
$script:actionIcon.Text = $s.icon
398437

399438
if ($State -in @('thinking','working','waiting')) { Start-Pulse } else { Stop-Pulse }
@@ -689,18 +728,25 @@ $timer.Add_Tick({
689728
$script:lastStatusMtime = $mtime
690729
$data = Get-Content $statusFile -Raw -Encoding UTF8 | ConvertFrom-Json
691730
# progress 也要进 sig,否则 agent 连续推 working+相同 message+不同 progress 会被去重
731+
# step/total/detail 也要进 sig,否则连续推同 state 但不同 step 会被去重
692732
$prog = if ($data.PSObject.Properties['progress']) { [int]$data.progress } else { -1 }
693733
$usage = $null
694734
$resetMs = 0
695735
$todoP = -2
736+
$step = -1
737+
$total = -1
738+
$detail = ''
696739
if ($data.PSObject.Properties['usage5h'] -and $null -ne $data.usage5h) { $usage = [int]$data.usage5h }
697740
if ($data.PSObject.Properties['usage5hResetMs'] -and $null -ne $data.usage5hResetMs) { $resetMs = [int]$data.usage5hResetMs }
698741
if ($data.PSObject.Properties['todoProgress'] -and $null -ne $data.todoProgress) { $todoP = [int]$data.todoProgress }
699-
$sig = "$($data.state)|$($data.message)|$prog|$usage|$resetMs|$todoP|$($data.ts)"
742+
if ($data.PSObject.Properties['step'] -and $null -ne $data.step) { $step = [int]$data.step }
743+
if ($data.PSObject.Properties['total'] -and $null -ne $data.total) { $total = [int]$data.total }
744+
if ($data.PSObject.Properties['detail'] -and $null -ne $data.detail) { $detail = [string]$data.detail }
745+
$sig = "$($data.state)|$($data.message)|$prog|$usage|$resetMs|$todoP|$step|$total|$detail|$($data.ts)"
700746
if ($sig -eq $script:lastStatusSig) { return }
701747
$script:lastStatusSig = $sig
702-
Dbg "POLL: $($data.state) :: $($data.message) (progress=$prog usage5h=$usage resetMs=$resetMs todoProgress=$todoP)"
703-
Update-State -State $data.state -Message $data.message -Progress $prog -Usage5h $usage -Usage5hResetMs $resetMs -TodoProgress $todoP
748+
Dbg "POLL: $($data.state) :: $($data.message) step=$step/$total detail=$detail (progress=$prog usage5h=$usage resetMs=$resetMs todoProgress=$todoP)"
749+
Update-State -State $data.state -Message $data.message -Progress $prog -Usage5h $usage -Usage5hResetMs $resetMs -TodoProgress $todoP -Step $step -Total $total -Detail $detail
704750
} catch {
705751
Dbg "POLL ERR: $($_.Exception.Message)"
706752
}
@@ -717,12 +763,18 @@ if (Test-Path $statusFile) {
717763
$initUsage = $null
718764
$initReset = 0
719765
$initTodo = -2
766+
$initStep = -1
767+
$initTotal = -1
768+
$initDetail = ''
720769
if ($init.PSObject.Properties['usage5h'] -and $null -ne $init.usage5h) { $initUsage = [int]$init.usage5h }
721770
if ($init.PSObject.Properties['usage5hResetMs'] -and $null -ne $init.usage5hResetMs) { $initReset = [int]$init.usage5hResetMs }
722771
if ($init.PSObject.Properties['todoProgress'] -and $null -ne $init.todoProgress) { $initTodo = [int]$init.todoProgress }
723-
$script:lastStatusSig = "$($init.state)|$($init.message)|$initProg|$initUsage|$initReset|$initTodo|$($init.ts)"
772+
if ($init.PSObject.Properties['step'] -and $null -ne $init.step) { $initStep = [int]$init.step }
773+
if ($init.PSObject.Properties['total'] -and $null -ne $init.total) { $initTotal = [int]$init.total }
774+
if ($init.PSObject.Properties['detail'] -and $null -ne $init.detail) { $initDetail = [string]$init.detail }
775+
$script:lastStatusSig = "$($init.state)|$($init.message)|$initProg|$initUsage|$initReset|$initTodo|$initStep|$initTotal|$initDetail|$($init.ts)"
724776
$script:lastStatusMtime = (Get-Item $statusFile).LastWriteTimeUtc.Ticks
725-
Update-State -State $init.state -Message $init.message -Progress $initProg -Usage5h $initUsage -Usage5hResetMs $initReset -TodoProgress $initTodo
777+
Update-State -State $init.state -Message $init.message -Progress $initProg -Usage5h $initUsage -Usage5hResetMs $initReset -TodoProgress $initTodo -Step $initStep -Total $initTotal -Detail $initDetail
726778
} catch {}
727779
} else {
728780
Update-State -State 'idle' -Message ''

‎plugins/antianqi/mcode-island/notify-island.ps1‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ param(
1010
[ValidateSet('idle','thinking','working','waiting','done','error')]
1111
[string]$State = 'idle',
1212
[string]$Message = '',
13-
[int]$Progress = -1
13+
[int]$Progress = -1,
14+
[int]$Step = -1,
15+
[int]$Total = -1,
16+
[string]$Detail = ''
1417
)
1518

1619
$ErrorActionPreference = 'Stop'
@@ -113,6 +116,9 @@ $payload = [PSCustomObject]@{
113116
state = $State
114117
message = $Message
115118
progress = $Progress
119+
step = $Step
120+
total = $Total
121+
detail = $Detail
116122
ts = $ts
117123
source = 'agent'
118124
} | ConvertTo-Json -Compress

‎plugins/antianqi/mcode-island/plugin.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
33
"name": "mcode-island",
4-
"version": "0.3.0",
5-
"description": "Windows 桌面灵动岛 (Dynamic Island) 状态窗口:让 mcode agent 把工作状态(idle/thinking/working/waiting/done/error)实时推送到屏幕顶部悬浮 pill,agent 自己忙的时候用户不用切回 mcode 也能看到进度。v0.3.0 增加 io.minimax.mcode 客户端扩展(Hooks 草案),与 MiniMax-Code-Plugins PR #20 的 portable Hooks 提案对齐;mcode 0.2.4+ Runtime 触发,registry 接受后零改动生效。",
4+
"version": "0.4.0",
5+
"description": "Windows 桌面灵动岛 (Dynamic Island) 状态窗口:让 mcode agent 把工作状态(idle/thinking/working/waiting/done/error)实时推送到屏幕顶部悬浮 pill,agent 自己忙的时候用户不用切回 mcode 也能看到进度。v0.4.0 扩展 status.json schema 增加 step/total/detail 三个字段并更新 widget 渲染:agent 现在可以推送 'step 3/12 · fill username field' 这类 sub-step 进度,Computer Use 多步循环、长任务分阶段展示。Backward compat:所有新字段 optional。v0.3.0 增加 io.minimax.mcode 客户端扩展(Hooks 草案),与 MiniMax-Code-Plugins PR #20 的 portable Hooks 提案对齐;mcode 0.2.4+ Runtime 触发,registry 接受后零改动生效。",
66
"author": {
77
"name": "antianqi",
88
"url": "https://github.com/antianqi"

‎plugins/antianqi/mcode-island/scripts/smoke.mjs‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ const checkEntry = async (event, entry) => {
155155
};
156156

157157
const main = async () => {
158-
console.log(`mcode-island v0.3.0 self-check`);
158+
console.log(`mcode-island v0.4.0 self-check`);
159159
console.log(`plugin root: ${PLUGIN_ROOT}`);
160160
console.log('-'.repeat(60));
161161

@@ -182,8 +182,8 @@ const main = async () => {
182182
} else {
183183
out('PASS', `plugin.json: name is "${plugin.name}"`);
184184
}
185-
if (plugin.version !== '0.3.0') {
186-
out('FAIL', `plugin.json: version is "${plugin.version}", expected "0.3.0"`);
185+
if (plugin.version !== '0.4.0') {
186+
out('FAIL', `plugin.json: version is "${plugin.version}", expected "0.4.0"`);
187187
} else {
188188
out('PASS', `plugin.json: version is "${plugin.version}"`);
189189
}
@@ -338,6 +338,58 @@ const main = async () => {
338338
}
339339
}
340340

341+
// 5c1. Sub-step progress extension (round-13 refactor).
342+
// notify-island.ps1 must accept -Step/-Total/-Detail and write them
343+
// into status.json. mcode-island.ps1 widget must define
344+
// Build-DisplayMessage and pass step/total/detail to it.
345+
// _lib.ps1 Format-ToolSummary must extract mcode-computer-use
346+
// action+coordinate; Push-Island must forward the new fields.
347+
// The detailed functional tests live in test-substep-progress.mjs;
348+
// here we lock the surface contract so a future refactor that
349+
// drops the params surfaces in smoke (fast path) before reaching
350+
// the slower pwsh-spawned tests.
351+
const substepNotifyPath = join(PLUGIN_ROOT, 'notify-island.ps1');
352+
const substepWidgetPath = join(PLUGIN_ROOT, 'mcode-island.ps1');
353+
if (!(await exists(substepNotifyPath))) {
354+
out('FAIL', 'notify-island.ps1 missing (sub-step lock skipped)');
355+
} else {
356+
const notify = await readFile(substepNotifyPath, 'utf8');
357+
if (!/\[int\]\$Step\s*=\s*-1/.test(notify) ||
358+
!/\[int\]\$Total\s*=\s*-1/.test(notify) ||
359+
!/\[string\]\$Detail\s*=\s*''/.test(notify)) {
360+
out('FAIL', 'notify-island.ps1: missing -Step/-Total/-Detail params');
361+
} else {
362+
out('PASS', 'notify-island.ps1: declares -Step -Total -Detail');
363+
}
364+
if (!/step\s*=\s*\$Step/.test(notify) ||
365+
!/total\s*=\s*\$Total/.test(notify) ||
366+
!/detail\s*=\s*\$Detail/.test(notify)) {
367+
out('FAIL', 'notify-island.ps1: status.json payload missing step/total/detail fields');
368+
} else {
369+
out('PASS', 'notify-island.ps1: writes step/total/detail to status.json');
370+
}
371+
}
372+
if (await exists(substepWidgetPath)) {
373+
const widget = await readFile(substepWidgetPath, 'utf8');
374+
if (!/function Build-DisplayMessage/.test(widget)) {
375+
out('FAIL', 'mcode-island.ps1: Build-DisplayMessage function missing');
376+
} else {
377+
out('PASS', 'mcode-island.ps1: Build-DisplayMessage function present');
378+
}
379+
if (!/\[int\]\$Step\s*=\s*-1/.test(widget) ||
380+
!/\[int\]\$Total\s*=\s*-1/.test(widget)) {
381+
out('FAIL', 'mcode-island.ps1: Update-State missing Step/Total params');
382+
} else {
383+
out('PASS', 'mcode-island.ps1: Update-State accepts Step/Total/Detail');
384+
}
385+
}
386+
const substepTestPath = join(PLUGIN_ROOT, 'scripts', 'test-substep-progress.mjs');
387+
if (!(await exists(substepTestPath))) {
388+
out('WARN', 'scripts/test-substep-progress.mjs missing (sub-step detailed tests not run)');
389+
} else {
390+
out('PASS', 'scripts/test-substep-progress.mjs exists (run separately for full suite)');
391+
}
392+
341393
// 5b. Drift lock: permission-request.ps1 must emit `{"decision":"ask"}`,
342394
// not `allow` or `deny`. The 0.2.4 Runtime default for PermissionRequest
343395
// is fail-closed; an observer Hook that returns `allow` or `deny`

0 commit comments

Comments
 (0)