Skip to content

Add plugin document-skills - #6

Open
Hylouis233 wants to merge 38 commits into
MiniMax-AI:mainfrom
Hylouis233:plugin/document-skills
Open

Add plugin document-skills#6
Hylouis233 wants to merge 38 commits into
MiniMax-AI:mainfrom
Hylouis233:plugin/document-skills

Conversation

@Hylouis233

@Hylouis233 Hylouis233 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Migrated from hetaoBackend/MiniMax-Code-Plugins#12; earlier review history remains there. Current head: 2b9e253.


What changes

Adds plugins/Hylouis233/document-skills, a Skill-only portable Agent Plugin for producing and
verifying DOCX, PDF, PPTX, and XLSX artifacts with standard open-source Python libraries and public
file-format knowledge.

The four hosted Skill names are deliberately namespaced to avoid colliding with built-in Skills:

  • document-skills-docx
  • document-skills-pdf
  • document-skills-pptx
  • document-skills-xlsx

Each Skill routes work to the appropriate document library, defines safe edit boundaries, and
requires reopening/verification before an artifact is handed back. The package contains no content
from Anthropic or another proprietary skill pack.

User value

The guidance prevents common silent artifact failures: corrupt hand-edited OOXML, spreadsheet
values where formulas belong, PDF pages without extractable text, and presentation text that
overflows its shapes.

Example:

Use the document-skills plugin: open sales-2024.xlsx, add a Summary sheet with per-region formulas,
a native bar chart of the top five products, currency formatting, and then reopen the workbook and
report the verified ranges.

Integration and scope

  • Plugin files live under plugins/Hylouis233/document-skills.
  • The isolated .github/workflows/document-skills.yml installs only fixture dependencies plus
    LibreOffice Writer, validates the repository, and runs fixtures.integration.mjs.
  • The workflow does not change the root test command or global test concurrency.
  • test/hosted-plugins.test.mjs accepts both POSIX and Windows path separators, fixing the portable
    scaffold assertion that previously failed on Windows.
  • The workflow checkout pin now uses the canonical 40-character actions/checkout@v7.0.1 SHA.

Requirements and disclosure

  • Python 3.9+ for the documented Skills; fixture validation uses a pinned Python dependency set.
  • LibreOffice is required only for the DOCX render smoke tests in CI. Pandoc and LibreOffice remain
    optional for end-user workflows when the relevant validation path is not requested.
  • Windows, macOS, and Linux are supported.
  • The plugin itself performs no network access and contains no credentials, telemetry, native
    binaries, installers, or private endpoints. It operates only on files selected by the user and
    writes temporary validation artifacts under a temporary directory.

Current-head verification

Verified locally on macOS against head 2b9e253:

  • npm run check: 27 tests passed, 0 failed, 0 skipped.
  • npm run validate: all 9 hosted Plugins and both examples validated.
  • Document fixture integration: 6 passed, 0 failed, 0 skipped.
    • XLSX fixture passed.
    • PPTX fixture passed.
    • PDF fixture passed.
    • DOCX fixture passed, including real soffice conversion.
    • The PDF Skill accessibility-claim guard passed.
  • Fixture environment: Python 3.14.7 with the exact pinned requirements; LibreOffice available on
    PATH.
  • Workflow YAML parsing and git diff --check: clean.

The upstream PR currently has no GitHub-hosted run for this newly added workflow. The workflow and
fixtures are locally validated; no hosted Ubuntu result is currently available. A skipped
[code]smith result is not being presented as CI evidence.

Submission checklist

  • Namespaced Skill directories avoid built-in-name collisions.
  • Plugin manifest, README, Apache-2.0 license, requirements, platforms, and data behavior are disclosed.
  • Repository validation, root tests, and all four document fixtures pass locally on the current head.
  • Fixture CI is isolated to a dedicated workflow.
  • GitHub-hosted Document Skills workflow passes on the current head.

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

Hylouis233 and others added 30 commits August 15, 2026 03:43
Four original workbench Skills (docx, pdf, pptx, xlsx) for creating, reading,
editing, and verifying Office and PDF documents with standard open-source
Python tooling.

- lives at plugins/Hylouis233/document-skills
- plugin.json + README + Apache-2.0 LICENSE + four Skills with references
- original guidance written for this plugin; no proprietary content reused
- no network, no credentials, no MCP server; scripts referenced are the
  user's own installed Python libraries (python-docx, python-pptx, openpyxl,
  pypdf, reportlab, pymupdf), not bundled
- npm run validate passes; documented patterns smoke-tested end to end
Review fixes (all fixture-verified in tests/):
- PDF: AcroForm route now clones pages + /AcroForm into the writer via
  append() and fills fields on writer pages; mediabox postcheck reduced to
  width/height pairs; CMYK/ICC pixmaps converted to RGB before PNG save
- PPTX: table cells edited at run level (cell.text rebuild proven lossy);
  inventory walker recurses into group shapes; font triage resolves
  inherited fonts with theme major/minor fallback
- XLSX: sniffed CSV dialect passed to DictReader; round_trip_losses()
  detects parts an openpyxl round trip drops before saving
- DOCX: numbering restart via cloned <w:num> + startOverride, rendered
  proof in fixture (restarts at 1 vs style-reuse continuing at 4)

Depth references (original content, no proprietary material):
- docx cjk.md: east-asian font slot, 字号 table, char-based indent,
  fixed line spacing, GB/T 9704 page geometry, font availability risks
- docx scenes.md: academic paper / resume / official document / contract
  skeletons with scene-specific verification
- xlsx formatting.md: conditional formatting rules, structured tables,
  honest pivot aggregation routes (openpyxl cannot create pivots)

tests/: one runnable fixture per format, 30 assertions, all passing
locally (pdf 9, pptx 10, xlsx 6, docx 5 incl. soffice-rendered proof)
- xlsx edit: round_trip_changes() now also detects extensions stripped from
  retained parts (x14/extLst markers), not just dropped archive members
- docx scenes: signature blocks get row-level cantSplit + keep_with_next on
  all rows (rendered fixture proves the table stays on one page)
- docx cjk: tofu postcheck switched to glyph-coverage (fontTools cmap) since
  text extraction cannot detect missing-glyph boxes; style snippet now
  defines font names before use (NameError fix)
- pptx edit: locator candidate collection recurses into groups with a
  stable nested path for the uniqueness assertion
- xlsx formatting: aggregation formulas build sheet refs from ws.title
  (quoting when needed) instead of hard-coded Data!
- xlsx read: profiles every sheet by default, not just sheetnames[0]
- pdf create: escape() rule for plain text into Paragraph; fixture shows
  unescaped markup silently swallows <...> runs

Fixtures extended to 44 assertions, all passing locally
- pdf extract: real table detection via page.find_tables() with span fallback
- pdf postcheck: interactive-only pages (AcroForm widgets) exempt from text gate
- docx read: unified block walker yields tables inside w:sdtContent
- README: drop the untagged-ReportLab accessibility claim, state the limitation
- xlsx: fullCalcOnLoad contract so manual-calc workbooks recalculate on open
- fixtures: table detection, widget exemption, sdt table walker, calc flags (all green)
- pptx analyze: sparse XY/bubble cache points keep their idx so x/y/bubble
  values pair correctly across blank points
- docx cjk: Hangul (jamo, compatibility jamo, extended-A, syllables) routes
  through the eastAsia font slot
- xlsx edit: extension detection matches namespace URIs and the local name
  extLst instead of arbitrary XML prefixes
- xlsx formatting: header-only sheets skip conditional formatting instead of
  building inverted ranges openpyxl rejects
- docx read: table extraction walks real w:tc elements and annotates
  gridSpan/vMerge instead of the merge-expanded row.cells view
- pdf transform: stamps are scaled and centered per destination page via
  merge_transformed_page; rotated pages flagged for visual verification
- fixtures extended for all six (with negative controls)
- pdf postcheck: real overflow check - text blocks extracted through an
  enlarged clip rectangle and compared to the page box (plain extraction
  silently drops fully off-page text)
- docx read: table cells rebuilt per paragraph with visible tab/break
  markers instead of concatenated w:t text
- docx/pptx health checks: explicit require() raises so every archive
  limit survives python -O
- pptx analyze: run fonts resolve a:latin/a:ea/a:cs by the run's scripts
  before falling back to the Latin-only font.name; triage walks table
  cells (graphic frames have no text frame)
- docx SKILL: pandoc-only installs can serve read/extract requests
- pdf extract: inline content-stream images enumerated via dict image
  blocks alongside the XObject loop; coordinate spans position-sorted
  before emission
- xlsx read: implausible sheet <dimension> reset before streaming
- fixtures extended for the overflow check (with negative control), cell
  paragraph boundaries, dimension corruption, script faces, table triage
Address the latest review round: add bounded XLSX/OPC package preflight with compression, member, materialization and range-token budgets; route all existing-package loads through validated snapshots; harden DOCX CJK/locator, PDF Type3 and PPTX merged-slot guidance; cover CSV formula neutralization, missing dimension scanning and internal hyperlink rewrites with real OPC fixtures.
Copilot AI lite review requested due to automatic review settings August 19, 2026 09:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 22, 2026
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].

  MiniMax-AI#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).

  MiniMax-AI#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.

  MiniMax-AI#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.

  MiniMax-AI#5 README: -Enable -> -Action Enable to match autostart.ps1 parameter set.

  MiniMax-AI#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).

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please resolve the integration/scope issues before merge:

  1. The plugin exports generic Skill names docx, pdf, pptx, and xlsx, which collide with the built-in MiniMax Code skills using those names. Please either define the intended precedence/namespace and prove that installation cannot create ambiguous selection, or rename the hosted skills and update the documentation.
  2. This plugin also changes the repository-wide .github/workflows/ci.yml to install Python packages and LibreOffice for its fixtures. Please justify that global CI change (or isolate it to plugin-specific validation) and ensure the hosted-plugin check runs with the documented dependency setup.

The current [code]smith check is SKIPPED, so the collision and CI integration need explicit validation.

hetaoBackend pushed a commit that referenced this pull request Aug 25, 2026
…ax 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>

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前 head 56eab83 仍有可复现的 fixture 阻塞:

  • plugins/Hylouis233/document-skills/tests/xlsx_fixture.py:29-33 仍读取 plugins/Hylouis233/document-skills/skills/xlsx/references/package.md,但当前实际目录是 skills/document-skills-xlsx/...;fixtures.integration.mjs 实测因此以 FileNotFoundError 失败。
  • 当前 fixture 结果是 PDF/PPTX 主要路径通过,XLSX 因上述仓库内错误路径失败;DOCX 的剩余失败是本机缺少 soffice,属于环境前置条件,不应掩盖 XLSX 的代码/fixture 错误。

请修正 xlsx fixture 的实际路径,并在依赖安装后重新运行 document-skills fixtures;确认 DOCX 的环境失败与代码失败分开记录。当前 [code]smith 为 SKIPPED。

@Hylouis233

Hylouis233 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

a132aa8 相对上一次 CHANGES_REQUESTED 所对应的 56eab83 有以下变更:

  • 四个 Skill 目录现为 document-skills-docx / document-skills-pdf / document-skills-pptx / document-skills-xlsx,不再使用会与内置 Skill 撞名的 docx / pdf / pptx / xlsx
  • tests/xlsx_fixture.py 已改为读取 skills/document-skills-xlsx/references/package.md,不再使用旧的 skills/xlsx/... 路径。

DOCX 若缺少 soffice,属于环境前置条件,与 XLSX fixture 路径问题相互独立。

@Hylouis233

Copy link
Copy Markdown
Contributor Author

Current head is 2b9e253.

Current-head evidence:

  • npm run check: 27 passed, 0 failed/skipped.
  • npm run validate: 9 hosted Plugins + both examples validated.
  • fixtures.integration.mjs: 6 passed, 0 failed/skipped; XLSX, PPTX, PDF, and DOCX all executed, with the DOCX path completing a real soffice conversion.
  • Workflow YAML and git diff --check: clean.

The four Skill directories remain namespaced, the XLSX fixture uses the corrected namespaced path, and fixture CI remains isolated from the root test command. This head also corrects the Document Skills workflow's checkout pin to the canonical 40-character v7.0.1 SHA.

The PR body now reflects the actual files, platform scope, current head, and local evidence. It explicitly leaves the GitHub-hosted workflow result unchecked rather than treating the skipped [code]smith check as evidence.

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.

3 participants