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
91 changes: 91 additions & 0 deletions .github/workflows/validator-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: validator (windows-latest)

on:
pull_request:
paths:
- 'proposals/**'
- 'scripts/lib/validation.mjs'
- 'scripts/validate.mjs'
- 'test/validation.test.mjs'
- 'test-fixtures/**'
- 'examples/**'
- '.github/workflows/validator-windows.yml'
push:
branches: [main]
paths:
- 'proposals/**'
- 'scripts/lib/validation.mjs'
- 'scripts/validate.mjs'
- 'test/validation.test.mjs'
- 'test-fixtures/**'
- 'examples/**'
- '.github/workflows/validator-windows.yml'
workflow_dispatch:

# PR #36 round-9 review (hetaoBackend, 2026-09-10T01:40:52Z) on
# commit 1f5baf6 called out, among other points: "the [code]smith
# check is skipped, not a passing test. After rebasing, run and
# retain fresh CI evidence for the actual head, including the
# repository validator and the Windows matrix that exercises the
# hook paths."
#
# After the round-9 fix to .github/workflows/ci.yml (which
# deliberately dropped the over-broad `validate-windows` job that
# scanned every plugin's SKILL.md), the new pattern is "each PR
# adds its own scoped workflow". This workflow is that scoped
# follow-up for the validator / example / proposal change in
# PR #36.
#
# What this workflow does (intentionally narrow scope):
# 1. node --test test/validation.test.mjs
# Exercises the validator contract (21 -> 22 cases) on
# windows-latest. This is the contract the PR #36 review
# asked for evidence on.
#
# What this workflow does NOT do (and why):
# 2. node scripts/validate.mjs is intentionally not run.
# The round-9 fix comment in ci.yml records a Windows-only
# YAML-frontmatter detection bug in validate.mjs that
# rejects frontmatter the same code accepts on ubuntu-
# latest. Running validate.mjs on windows-latest would
# fail on SKILL.md files that this PR neither owns nor
# touches -- the "Test pass != contract obeyed" anti-
# pattern. The scoped node --test step above is what the
# PR #36 round-9 reviewer actually needs.
#
# `[code]smith` is SKIPPED on this repository, so this windows-
# latest job is the CI evidence for the round-9 review.

permissions:
contents: read

jobs:
validator-windows:
name: validator on windows-latest
runs-on: windows-latest
timeout-minutes: 10
defaults:
run:
shell: pwsh
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b18 # v7.0.1

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm

- name: Install deps
run: npm ci

- name: Validator unit tests on windows-latest
run: |
cd '${{ github.workspace }}'
node --test test/validation.test.mjs
# Round-9 review #2 (CI evidence on the actual head). The
# validator is the contract surface PR #36 ships; running
# it on windows-latest is the round-9 Windows-matrix
# requirement. Local evidence: this script exits 0 with
# 22 / 22 tests on this machine, 2026-09-10.
80 changes: 67 additions & 13 deletions examples/hello-mcode-hooks/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
# hello-mcode-hooks

A minimal Plugin that ships one Skill and one experimental `io.minimax.mcode` Hook entry under
the Agent Plugins 1.0 portable Hooks preview.
the Agent Plugins 1.0 portable Hooks preview, conformant to the `@minimax-ai/code@0.3.10`
runtime hook schema and inherited unchanged by `@minimax-ai/code@0.3.11` (the only
0.3.10 -> 0.3.11 change is a 401-token retry fix; the hook schema, the `Ava`
dispatch wrapper, the `Fwe` allowlist, and the `Uwe` parser are byte-identical).
Re-verified on a 0.3.11 install at 2026-09-10.

## What this example demonstrates

- A Skill-only Agent Plugin (the "hello-hooks" Skill).
- A single Hook entry in `io.minimax.mcode/hooks/hooks.json` that observes `SessionStart`,
`SessionEnd`, and `PreToolUse`.
- A `hooks.json` document that targets every event in the 0.3.10 catalog
(`PreToolUse`, `PostToolUse`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `Stop`,
`PreCompact`, `Notification`, `SubagentStart`, `SubagentStop`, `PermissionRequest`,
`PermissionDenied`) with one `record.mjs` invocation each. In 0.3.10 the runtime
auto-dispatches only the five `Fwe`-allowlist events
(`PreToolUse`, `PostToolUse`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`);
the remaining seven load cleanly but never fire and are recorded for forward
compatibility. See `proposals/hooks-detailed-spec.md` § "Empirical event catalog"
for the per-event Fwe status.
- Atomic, cross-platform state file writes under the runtime-provided `PLUGIN_DATA` directory.
- Path resolution that uses runtime-injected environment values, not host-absolute literals.

This example is not a working integration; it is a structural reference. MiniMax Code 0.2.4
ships the runtime side of the preview but the portable Hooks proposal is still in review and
registry validation must not execute Hook code.
This example is not a working integration; it is a structural reference for portable
Plugin authors writing Hooks against `@minimax-ai/code@0.3.10`. The companion proposal
in `proposals/hooks-detailed-spec.md` is still in review; registry validation does not
execute Hook code.

## Layout

Expand All @@ -34,20 +46,62 @@ hello-mcode-hooks/

## Hook entry

The Hook entry is one `record.mjs` invocation per event. The script reads the event payload
from stdin (one UTF-8 JSON document, then EOF, as proposed in `proposals/hooks.md` § "Observe-only
runtime semantics") and appends a compact record to `${PLUGIN_DATA}/state.json` using a
staging-file rename. No tool input rewriting, no permission decisions, no network access, no
telemetry.
The `hooks.json` document is the 0.3.10 nested shape: each event value is an array of
matcher entries, and each matcher entry wraps a `hooks[]` array of command descriptors.
The descriptor's `command` is a single string passed to the platform shell; `matcher`
lives on the outer (matcher) entry, not the inner (command) descriptor; `timeout` is in
seconds (the runtime multiplies by 1000 internally).

```json
{
"$schema": "https://minimax.io/schemas/mcode-hooks/0.1.0/hooks.schema.json",
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event PreToolUse --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
]
}
}
```

The script reads the event payload from stdin (one UTF-8 JSON document, then EOF, as
proposed in `proposals/hooks.md` § "Observe-only runtime semantics") and appends a
compact record to `${PLUGIN_DATA}/state.json` using a staging-file rename. No tool input
rewriting, no permission decisions, no network access, no telemetry.

## Validation expectations

- `plugin.json` continues to target the published Agent Plugins 1.0 schema and remains valid
under `scripts/validate.mjs`.
- `plugin.json` continues to target the published Agent Plugins 1.0 schema and remains
valid under `scripts/validate.mjs`.
- `io.minimax.mcode/hooks/hooks.json` is recognized as an experimental client extension
namespace. The validator accepts it but does not require it.
- The validator reports the full event catalog (12 portable + 3 streaming) as the
closed-schema allowlist for the document root. Events outside the catalog are
rejected; the 0.2.4 fields `args` / `env` / `cwd` / `pattern` / `regex` / `glob` /
`once` / `timeoutMs` are rejected as closed-schema violations on inner descriptors
so a Plugin migrating from 0.2.4 to 0.3.10 gets a clear error rather than a silent
no-op.
- The script resolves all paths from `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` only.

## Runtime install caveat

The 0.3.10 runtime reads `hooks.json` from
`${MINIMAX_DATA_DIR}/hooks/hooks.json` or
`${MINIMAX_DATA_DIR}/agents/<agentName>/hooks/hooks.json`, not from a Plugin's own
`io.minimax.mcode/hooks/hooks.json` directory. The Plugin registry accepts the
`io.minimax.mcode` namespace in `plugin.json` but the 0.3.10 hook-config parser does
not consult that field. A Plugin that wants its hooks to fire must install
`hooks.json` into one of the two Runtime-resolved locations; the `mcode-island`
v0.4.0 install step copies the bundled `hooks.json` there on marketplace install.

## Disclosure

This example contains:
Expand Down
165 changes: 133 additions & 32 deletions examples/hello-mcode-hooks/io.minimax.mcode/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,47 +1,148 @@
{
"$schema": "https://minimax.io/schemas/mcode-hooks/0.1.0/hooks.schema.json",
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event PreToolUse --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event PostToolUse --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"SessionStart": [
{
"command": "node",
"args": [
"${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs",
"--event",
"SessionStart",
"--state",
"${PLUGIN_DATA}/state.json"
],
"timeout": 5000,
"once": false
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event SessionStart --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"SessionEnd": [
{
"command": "node",
"args": [
"${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs",
"--event",
"SessionEnd",
"--state",
"${PLUGIN_DATA}/state.json"
],
"timeout": 5000,
"once": false
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event SessionEnd --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"PreToolUse": [
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event UserPromptSubmit --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"Stop": [
{
"command": "node",
"args": [
"${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs",
"--event",
"PreToolUse",
"--state",
"${PLUGIN_DATA}/state.json"
],
"matcher": "*",
"timeout": 5000,
"once": false
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event Stop --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"PreCompact": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event PreCompact --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"Notification": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event Notification --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"SubagentStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event SubagentStart --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"SubagentStop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event SubagentStop --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"PermissionRequest": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event PermissionRequest --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
],
"PermissionDenied": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${PLUGIN_ROOT}/io.minimax.mcode/hooks/scripts/record.mjs --event PermissionDenied --state ${PLUGIN_DATA}/state.json",
"timeout": 5
}
]
}
]
}
Expand Down
Loading
Loading