Add plugin document-skills - #6
Conversation
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.
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
left a comment
There was a problem hiding this comment.
Please resolve the integration/scope issues before merge:
- The plugin exports generic Skill names
docx,pdf,pptx, andxlsx, 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. - This plugin also changes the repository-wide
.github/workflows/ci.ymlto 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.
…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
left a comment
There was a problem hiding this comment.
当前 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。
|
DOCX 若缺少 |
|
Current head is Current-head evidence:
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 |
What changes
Adds
plugins/Hylouis233/document-skills, a Skill-only portable Agent Plugin for producing andverifying 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-docxdocument-skills-pdfdocument-skills-pptxdocument-skills-xlsxEach 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:
Integration and scope
plugins/Hylouis233/document-skills..github/workflows/document-skills.ymlinstalls only fixture dependencies plusLibreOffice Writer, validates the repository, and runs
fixtures.integration.mjs.test/hosted-plugins.test.mjsaccepts both POSIX and Windows path separators, fixing the portablescaffold assertion that previously failed on Windows.
actions/checkout@v7.0.1SHA.Requirements and disclosure
optional for end-user workflows when the relevant validation path is not requested.
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.sofficeconversion.PATH.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]smithresult is not being presented as CI evidence.Submission checklist
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.