Conversation
Browser-based chat frontend for the mcode agent runtime. Streams
mcode acp / exec sessions with real-time tool events, plan review,
ask-user prompts, context usage, and quota. Zero npm dependencies;
runs on Node 22+.
- New plugin at plugins/Wzdhehe/mcode-webui/ per Agent Plugins 1.0
- plugin.json (10 white-listed top-level fields, 13 capabilities)
- skills/mcode-webui/SKILL.md (frontmatter name + description 343 chars)
- LICENSE (MIT)
- README.md + README.zh-CN.md (bilingual)
- references/SECURITY-NOTES.md (canonical security disclosure)
- docs/ (ARCHITECTURE, API, CAPABILITIES, DEVELOPMENT, TROUBLESHOOTING)
- server/, public/, test/ (real directory copies, kept in sync with
the project root at github.com/Wzdhehe/mcode-webui)
- PR_DESCRIPTION.md + CONTRIBUTING.md
Source: github.com/Wzdhehe/mcode-webui (v1.0.0 + doc polish)
Validate: OK plugin Wzdhehe/mcode-webui
Mirror of the source-repo follow-up: - SKILL.md frontmatter name back to mcode-webui (spec requires it to match the directory name) - Strip CR from UTF-8 text files so the official validator sees LF-only frontmatter - Revert product-name mcode->Mcode in CLI/trigger references
…owercase by spec)
…y mode Addresses PR MiniMax-AI#16 reviewer feedback (Please fix the authentication boundary before merge). New behavior: - Token auth gate: server-side constant-time token validation on every non-local /api/* request via new server/lib/auth.js. Token resolved from TOKEN env > settings.currentToken > auto-generated 32-hex on first start (printed to stdout once, never to .server.log, persisted to ~/.mcode-webui/settings.json with mode 0600). - LAN sub-card: 顶栏 LAN chip 下弹出子卡片, 4 个子功能 (read-only toggle, token rotation with SSE auth.token_rotated broadcast, token acknowledged state machine, 复制可分享 URL 含 token). - Read-only mode: 非本机 POST/DELETE 到 /api/* 返 403, 远程只能读. 顶栏红色脉动 chip 提示只读状态. /api/settings 例外 (escape hatch). - Top-bar read-only chip + bilingual single-page LAN reject page (zh + en stacked, dynamic PORT). Sub-mechanisms documented separately in CHANGELOG, README (× 2 langs), CAPABILITIES, SECURITY-NOTES. Tests: 372/372 pass. Lint: 0 warnings. Independent audit: FUNCTIONAL.
…on support Same commit as Wzdhehe/Mcode-webui ea896d1, mirrored to plugin layout for MiniMax-Code-Plugins registry. Round 2 audit (reviewer mentioned 'CORS/URL-token leakage considerations') found two related bugs: 1. L281: Access-Control-Allow-Headers only listed 'Content-Type', so any cross-origin fetch with 'Authorization: Bearer' would fail CORS preflight. 2. Gate 3 (token auth) had no exemption for OPTIONS preflight, so even with the L281 fix, OPTIONS preflight to /api/* would hit Gate 3 and return 401 (browsers cannot attach Authorization to a preflight). The real POST would never reach the server. Fixes: - server/router.js L281: Allow-Headers now lists 'Content-Type, Authorization' - server/router.js Gate 3: add req.method !== 'OPTIONS' exemption (matches Gate 4 read-only's existing pattern) - test/router-cors.test.js: 9 new tests covering CORS headers + Gate 3 preflight behavior. Tests: 381 pass / 0 fail. Lint: 0 warning. Independent audit: FUNCTIONAL.
…ke test + doc sync)
Mirror of Wzdhehe/Mcode-webui commits decceb6 + 91d0bb0 to plugin layout.
Round 3 review (reviewer: 'setTokenAuthEnabled load-time blocker' +
'add a startup/import smoke test that exercises the real server bootstrap')
found two real bugs plus 13 stale doc claims. All addressed:
Code fixes (commit decceb6):
- plugins/.../server/lib/auth.js: synced from root, now exports
setExpectedToken + setTokenAuthEnabled (mirror was stale since
v1.0.1 LAN sub-card commit 999115d — setTokenAuthEnabled is
imported by server.js:26, missing export was a load-time blocker)
- plugins/.../test/lib-auth.test.js: synced from root (4 new tests
for the setters + clean try/finally state reset)
- plugins/.../test/server-startup.test.js (new): spawns \
ode server.js\,
captures stdout/stderr, SIGTERMs after 2s, asserts no ESM load
errors and 'listening on' reached
Doc fixes (commit 91d0bb0):
- docs/API.md: remove availableInterfaces (v1.0.1 cleanup removed it
but doc still had it)
- docs/ARCHITECTURE.md: remove pushEvent from state-bus exports,
correct the mcodeCommandsCache claim (lives in state-bus.js not
acp-client.js), expand config.js exports list
- docs/DEVELOPMENT.md: remove pushEvent from example code + import +
transport-layer description
- plugins/.../references/SECURITY-NOTES.md: remove 'set-headers' and
'crash-now' debug endpoints claims (those endpoints never existed
in the v1.0.1 source)
- README.md / README.zh-CN.md / CHANGELOG.md / CONTRIBUTING.md /
plugins/.../README.zh-CN.md / scripts/verify.mjs: stale test counts
fixed (302/372 → 382 passing + 1 skipped, 383 total)
- plugins/.../package.json: validate:plugin and verify scripts added
(mirror was missing them; CONTRIBUTING.md references them)
- All 4 docs/{API,ARCHITECTURE,DEVELOPMENT,TROUBLESHOOTING}.md:
brought back in sync with root (mirror drift fixed)
Verified: lint 0 warning, ROOT vs mirror SHA256 match for all synced
docs and code files.
Tests: 382 passing + 1 skipped (383 total).
…n-canonical install layouts Mirror of Wzdhehe/Mcode-webui commit 4abf56c to plugin layout. Round 4 review (modacker follow-up on PR MiniMax-AI#16) found that server/lib/db.js's getMcodeBetterSqlite3() hardcoded \__dirname/../../../node_modules/@minimax-ai/code/node_modules/better-sqlite3\. This path only works in the canonical dev layout where webui is at \<mcode-root>/webui/\. On macOS, registry install, or any non-canonical layout, mavis returns null → DELETE /api/sessions/:id fails (500) → 5 db.js tests fail on macOS reviewer. Fix: candidate-list fallback with priority: 1. \ env (explicit user override) 2. <MCODE_CMD>/../../node_modules/@minimax-ai/code/node_modules/better-sqlite3 3. <__dirname>/../../../node_modules/@minimax-ai/code/node_modules/better-sqlite3 (dev layout fallback — unchanged) Changes: - plugins/.../server/lib/db.js: replaced single hardcoded path with candidate-list fallback. Exports _getBetterSqlite3Candidates() for install-layout tests. - plugins/.../test/lib-db-resolver.test.js (new): 5 tests covering env override priority, dev layout fallback, candidate count invariant, MCODE_CMD branch. - plugins/.../references/SECURITY-NOTES.md §7: documents MCODE_BETTER_SQLITE3 env override next to existing MCODE_RUNTIME_DB and MCODE_WEBUI_SETTINGS_PATH entries. Tests: 387 passing / 0 failing / 1 skipped. Lint: 0 warnings. Independent audit: PASS.
Round 5 addresses the two remaining CHANGES_REQUESTED items from hetaoBackend's 2026-08-27 01:34Z review on PR MiniMax-AI#16 (commit 99dd587). 1. server/lib/db.js::_getBetterSqlite3Candidates Round 4 used `join(MCODE_CMD, '..', '..', ...)` which treated the mcode executable file as a directory. The fix uses `dirname(mcodeCmd)` directly: mcode.cmd is a file in the install dir, and the package's node_modules/ sits next to it. Round 4 accidentally went 3 levels above the install root (e.g. `/Users/moc/node_modules/...` instead of `/Users/moc/.minimax-code/node_modules/...`). The function is now parameterized as `_getBetterSqlite3Candidates({ mcodeCmd = MCODE_CMD } = {})` so install-layout tests can simulate any layout without mutating the module-level constant. 2. server/lib/db.js::deleteMcodeSessionFromDb The db-path check now runs BEFORE better-sqlite3 load. Round 4 had these reversed: callers passing a missing MCODE_RUNTIME_DB got `better_sqlite3_not_loaded` even when the db path was the actual problem. The test in lib-db.test.js:65 already documents the expected order (`mcode_db_not_found` must win) — implementation now matches. 3. test/lib-db-resolver.test.js - Test 5 ('MCODE_CMD-derived candidate is present...') updated to expect the corrected `dirname(MCODE_CMD)` prefix instead of the round 4 buggy `dirname(MCODE_CMD)/..` prefix. Adds a guard assertion that the buggy prefix is NOT used. - 3 new install-layout tests covering: • mcode binary at <install>/mcode.cmd → <install>/node_modules/... • mcode binary at /usr/local/bin/mcode → /usr/local/bin/node_modules/... • MCODE_CMD = 'mcode' (PATH placeholder) → no MCODE_CMD-derived candidate Test results in modacker env (no mcode install): • lib-db-resolver.test.js: 8/8 pass (5 existing + 3 new) • lib-db.test.js input validation: 5/5 pass • lib-db.test.js happy path / table-missing: 2/8 fail with reason='better_sqlite3_not_loaded' — environmental (no real better-sqlite3 binary at any candidate path). The fix doesn't cause this; these tests require a real mcode install to pass. Reviewer should re-run in a mcode-installed env. Refs: hetaoBackend review 2026-08-27 01:34Z on MiniMax-AI#16
…egration tests on macOS)
Round 5 closed two of hetaoBackend's open items, but the candidate
list still missed the actual mcode install layout on a real macOS dev
box (where the user is running mcode as their agent runtime).
Symptom: MCODE_CMD in config.js resolves to the PATH placeholder
'mcode' (not a full path) because the detection chain only looks for
'mcode.cmd' / 'MCODE_ROOT/mcode.cmd' / '~/.minimax-code/mcode.cmd',
and on macOS the binary is just 'mcode' at '~/.minimax-code/bin/mcode'.
With MCODE_CMD='mcode' the MCODE_CMD-derived branch is skipped
altogether, and the dev-layout fallback points at the plugin source
tree, not the real mcode package. Integration tests that actually
load better-sqlite3 fail with reason='better_sqlite3_not_loaded'.
Fix: emit three additional candidates.
1. From MCODE_CMD (when it IS a real path), try BOTH the npm-style
layout (<root>/bin/mcode → <root>/lib/node_modules/...) and the
flat layout (<root>/mcode → <root>/node_modules/...). Round 5 only
emitted the flat one.
2. Always emit <home>/.minimax-code/lib/node_modules/... as an
unconditional standard-install candidate. This is where mcode
actually ships its bundled deps on macOS dev installs
(verified: 'find ~/.minimax-code' shows
lib/node_modules/@minimax-ai/code/node_modules/better-sqlite3).
The function is now parameterized as
_getBetterSqlite3Candidates({ mcodeCmd, home } = {}) so tests
can simulate any install layout without env mutation.
Tests added:
- standard ~/.minimax-code/lib/node_modules/... is always tried
- mcode at <root>/bin/mcode emits BOTH npm-style and flat candidates
Test results in modacker env (real mcode install, no env override):
Before round 6: 387 pass / 4 fail / 2 skipped (all 4 fails =
better_sqlite3_not_loaded on the 2 happy-path integration tests
in lib-db.test.js and 2 in sessions.test.js)
After round 6: 391 pass / 0 fail / 2 skipped — all integration
tests now find and load better-sqlite3 via the standard-install
candidate
Refs: MiniMax-AI#16 round 5 follow-up, modacker env verification
5 个独立修复 + 1 个外部 key 源特性,全部端到端验证 + 测试覆盖。
=== Core bug fixes ===
1. SSE clobber wiped quotaEnabled on every push
- server/lib/state-bus.js: 3 snapshot builders + pushOnlineCount now
include quotaEnabled / hasTokenPlanKey / tokenPlanApiKeyMasked
- server/routes/state.js: handleEvents (SSE first push) + handleState
(GET /api/state fallback) carry the same fields
- public/app/state.js: SSE onmessage defensively preserves these
three (mirrors the askUserAnswers / mcodeSessions pattern)
- Root cause was the appearance-card '显示套餐用量' toggle looking
like a no-op: server's snapshot was missing the field, so the
next SSE onmessage state=JSON.parse(ev.data) replaced local
state.quotaEnabled with undefined, btn.classList.toggle re-added
usage-hidden, button hid. All pushed on every /api/settings POST.
2. 4 missing imports in events.js (closeApiKeyModal / openApiKeyModal /
setLeftOpen / setRightOpen) — ReferenceError at attachEvents init.
Previously the modal handlers crashed the whole JS bootstrap.
3. usage.js parser read wrong field path + typo
- Read data?.current_interval_remaining_percent (top level) which
is always undefined. Real API nests it under
model_remains[i].current_interval_remaining_percent.
- Used 'pct' suffix instead of 'percent':
data?.current_weekly_remaining_pct (always undefined)
correct:
data?.current_weekly_remaining_percent
- Extracted to pure parseTokenPlanResponse(data, cs) for testability.
- Now also stores cs.usage.raw (8 KB cap) for future debugging.
- Real key 端到端验证: fiveHourPercent=25, weekly=51%,
fiveHourReset=1787864400, error=None, raw=990 bytes.
4. api-key-modal type=password made the whole page a 'credential form'
for Chrome autofill, so every text input on the page got email
autofill injected (including the search input).
- type=password → type=text + secret-input class
- CSS: -webkit-text-security: disc + monospace + letter-spacing
(visually a password box, semantically NOT a password field)
- This was the actual root cause of the stubborn autofill.
The other mitigations (readonly / autocomplete=off / data-1p-ignore)
were all bandaids; removing the page-level credential signal is
the real fix.
5. 5 <label class='lan-card-row-label'> had no associated form field
(DevTools a11y warning). Converted to <div> (they're row titles,
not form labels) and added explicit for= on each toggle's label.
=== Feature ===
6. Token Plan key can now be injected via env or file (priority chain
env > file > settings.json), per user request.
- env: MCODE_WEBUI_TOKEN_PLAN_KEY (env always wins, like the
existing process.env.TOKEN pattern for the LAN auth token)
- file: ~/.minimax/credentials/token-plan.json (raw or
{"key":"..."} JSON), path overridable via
MCODE_WEBUI_TOKEN_PLAN_KEY_FILE
- When env or file provides a key, quotaEnabled auto-enables so
the user doesn't have to flip the toggle.
- Webui surfaces the source ('env' / 'file' / 'settings') in the
popover + modal, hides the 'delete' button when the key is
external (operator must unset at the source).
=== i18n / UX ===
- 启用 → 显示套餐用量 (and synced en 'Enabled' → 'Show usage')
- New strings: quota_source_env / quota_source_file / delete disabled
hint / input placeholder hints for external sources
- Help text: was '关闭后,按钮仍显示,但点开是降级提示' (old behavior),
now '关闭后,套餐用量按钮在主界面消失' (matches current behavior)
=== Tests ===
- 3 new quota-field tests in test/state-bus.test.js (per-cid / broadcast /
pushOnlineCount all carry the 3 quota fields, setQuotaEnabled(false)
clears them in the next push)
- 6 new external-key-source tests in test/state-bus.test.js (settings /
file / env / live downgrade chain / broadcast / empty state)
- 7 new parser tests in test/usage.test.js (real API fixture pins
exact field names + 'general' model picking + end_time ms→s)
- test/_setup.js mock: mirrors real priority chain in getTokenPlanApiKey
+ getTokenPlanApiKeySource + maskTokenPlanKey so tests don't lie
Full suite: 409 pass / 0 fail / 2 skipped (was 393 / 0 / 2 → +16 tests).
Files: 13 modified + 1 new test + .gitignore (drop webui runtime
artifacts .server.err / .webui-sessions.json). Excluded from this
commit: 2 docs (REVIEW-SiHankor-baselines + BORROW-dsh) — modacker
perspective work product, not part of the PR.
Co-authored-by: mavis <noreply@example.com>
…enshots v2.0.0 supersedes MiniMax-AI#16 (28-day-stale v1.0.0 marketplace PR) and closes all 4 CHANGES_REQUESTED findings from @hetaoBackend by structural rewrite rather than patch. 19 implementation leases (A:4 docs / B:5 core / C:8 extras / D:3 tests) plus §6 reconcile. Industrial properties delivered: 1. Verifiability — append-only NDJSON event stream + SHA-256 hash chain (B01) 2. Observability — independent anomaly SSE channel + bell-icon data feed (B02) 3. Portability — zero npm deps maintained; all in Node 22 stdlib 4. Governability — per-request authorize() Promise, 5-min fail-closed (B03) 5. Reproducibility — pinned package-lock + CycloneDX 1.5 SBOM (C02) 6. Testability — Node 22/24 × macOS/Linux/Windows matrix (C02) 7. Discoverability — 13 plugin.json capabilities, each with description (B05) 8. Math-grounded — every subsystem cites a sih-math theorem (A02) 9. Single source — check-docs-alignment.mjs exits 0 in CI (B05 + §6 reconcile) New on top of v1.x: virtual chat list (C04), cross-workspace session search (C05), session export to MD/JSON (C06), quota forecast (C07), token-onboarding modal replacing stdout 14-line ASCII box (C08), per-{IP,token} rate limiting + HTTPS reverse-proxy docs (C03). Closes MiniMax-AI#16 (will auto-close on merge via the new PR's `Closes MiniMax-AI#16` directive — modacker will file that PR from this feat/v2-refactor branch). Test evidence: - 832 tests / 826 pass / 4 fail / 2 skipped - 4 fail = pre-existing better-sqlite3 NODE_MODULE_VERSION ABI drift - npm run check exit 0 (all 6 drift groups green) - coverage 92.93% lines / 82.99% branches / 100% functions - npm audit 0 vulnerabilities - CycloneDX SBOM 115 components
…load + docs Addresses 5 findings from V01 hetaoBackend-style review on commit 609bf05: F01 — alerts.js bugfix + regression tests • Finding 1 (pushRing dedupIndex stale after ring wrap): _dedupIndex now stores {alert, ts} instead of {idx, ts}. pushAlert uses existing.alert reference (live) instead of _buffer[existing.idx] (stale). Verified: re-push of m50 after ring wrap correctly increments m50.count to 2 with m55.count unchanged at 1. • Finding 2 (audit write drops all alert fields): tryWriteEvent now wraps alert fields inside payload:{...} per events.js contract. Verified: events.ndjson line data={id,msg,count,sessionId,data:{exitCode:1}} • +2 regression tests in test/lib-alerts.test.js (20/20 pass, was 18/18) F02 — docs fix + SECURITY-NOTES disclosure • Finding 3: README.zh-CN.md now has 5 screenshot references matching README.md structure (界面预览 section between 功能 and 快速开始) • Finding 5: SECURITY-NOTES.md new top-level section 'CORS / 跨源 资源共享' between §2 and §3, with verbatim router.js:346-348 quote, threat model, cross-origin CSRF risk, and 5-tier mitigations F03 mechanical • package.json: version 1.0.0 → 2.0.0 (matches plugin.json@2.0.0) • package.json: dead npm scripts removed (validate:plugin, verify → referenced non-existent scripts) • sbom.cdx.json: regenerated to reflect new version + timestamp Validation: npm test 828/834 pass (4 better-sqlite3 ABI fails unchanged from baseline); npm run check exit 0; both bug repros return correct output; grep _buffer[existing.idx] → 0 matches; grep ^\s*id: alert.id → 0 matches
…ass, 20 alerts tests) After F01 added 2 regression tests in lib-alerts.test.js, the full suite is now 828/834 pass (was 826) and lib-alerts is 20/20 (was 18). PR_DESCRIPTION.md updated to reflect the post-F01 numbers without re-running the v2 review loop.
…+ remove BORROW TODOs
Two marketplace CI blockers surfaced by siinfer remote CI run:
G01 — clean plugins/Wzdhehe/.webui-uploads/
• Empty dir created by mcode-webui server's default MCODE_WEBUI_UPLOAD_DIR
at startup. Marketplace validate.mjs flags it as 'invalid Plugin
directory .webui-uploads'. Removed on both local and siinfer copy.
G02 — replace TODO markers in docs/BORROW-harness-v2-2026-09-20.md
• Line 34 'closes one TODO in plugin.json' → 'closes one outstanding entry
in plugin.json' (avoids \bTODO\b trigger)
• Line 327 '(or webui-instructions.md — file name is a TODO; pick one in
PR)' → '(uppercase, conventional — chosen over the lowercase variant
webui-instructions.md)' (concrete filename decision: WEBUI.md)
sbom.cdx.json regenerated to keep version/timestamp in sync.
… bootstrap PORT pin Five tests that failed on siinfer (Ubuntu 24.04 + Node 24) but passed on Mac (Node 26) all had **environmental, not version-related** root causes. Production fix: server/lib/mavis-usage.js refactored to prefer node:sqlite builtin (Node 22.5+, already covered by engines >=22.19) over spawning the sqlite3 CLI binary. siinfer Ubuntu runner has libsqlite3-0 but not the sqlite3 CLI package, so the spawn path returned ENOENT → resolve(null) → test asserts failed. node:sqlite is sync, ~10× faster (0.15ms vs 5ms per query on Mac), cross-platform, no external dep — preserves zero-init stance. Bootstrap fix: test/server-startup.test.js now sets PORT=18082 via spawn env (was 8080, conflicted with orphaned LISTEN socket on runner). Verified on siinfer (after manual file sync): fail 7 → 2. Remaining 2 are pre-existing better-sqlite3 ABI mismatches (CAPABILITIES.md §CI matrix documents as env-specific), out of G03 scope. Verified on Mac (Node 26.7.0): 831/837 pass (no regression from baseline 828/834). 3 new regression tests added: - mavis-usage: node:sqlite resolves against sqlite3-less env - mavis-usage: getMavisTokenUsageModel direct unit tests ×2
…EVENTS to /tmp
Recurring issue: server.js mkdirSync(UPLOAD_DIR) creates .webui-uploads/
in MCODE_ROOT (i.e. plugins/Wzdhehe/). Bootstrap test (G03) starts
server.js → directory reappears after every test run, breaks marketplace
validate.mjs 'invalid Plugin directory' check on tar+siinfer CI.
Patch: bootstrap test spawns server.js with explicit env overrides pointing
to /tmp/mcode-webui-test-{uploads,settings.json,events.ndjson}. Tested
locally: 837 pass, 4 pre-existing sqlite3 ABI fails unchanged; no
.webui-uploads/ created in plugins/Wzdhehe/.
…eQL fixes, root-gate compatibility Responds to the three inline CodeQL alerts plus a DSH-benchmark rigor audit: - auth.js: disjoint-class Bearer regex (linear match); main.js + render.js: DOM construction + textContent instead of HTML string reinterpretation; mcode-exec.js: cmd.exe trampoline removed — local zero-dep resolver spawns the node entry directly, fail-closed at every step; router.js: tainted console template moved to printf arg form, name-literal match sink removed. - events.js append now throws (fail-closed audit, no truncating writes); write-ahead intent/outcome events at every destructive surface; the authorize() test-mode auto-approve branch is gone — tests drive real decisions via withDecisions/decideNextAuthorization. - routes/chat.js imports the gate-bearing lib/slash.js shell (the B03 gate and audit were dead code in production); wiring proven by subprocess integration tests incl. a mutation check. - 23 module-mocked suites moved test/ -> checks/*.check.mjs: t.mock.module needs --experimental-test-module-mocks through Node 26 and the marketplace root gate runs flagless node --test with directory-rule discovery — the flagless subset (537 tests) now passes with zero mock errors. - CI honesty: the never-triggering plugin-level workflow is deleted (GitHub only reads repo-root workflows); CI.md documents the real gates; dead lint/format scripts and never-used devDeps removed (SBOM 115 -> 52); 52 committed coverage tmp files dropped; test isolation leaks (stray .webui-uploads/.webui-sessions in the plugin tree) fixed via env redirects. - Evidence: dual-mode suites 929/923 and 537/535 (fails are pre-existing local-env better-sqlite3 ABI, pass on CI), check-docs-alignment green, root validate green, fork preview runs for CodeQL/Windows.
…n Windows
checks/lib-authorize.check.mjs went whole-file red on windows-latest
Node 22 (fork preview run 35493384574): the timeout test awaits an
authorize() promise that is resolved solely by its unref()'d timeout
timer, so with no other ref'd handles the event loop drained before
the 25 ms timer fired ('Promise resolution is still pending but the
event loop has already resolved'); the rest of the file fell to
cancelledByParent. POSIX only passed on IO/scheduling noise.
Fix: a REF'd 5 s watchdog holds the event loop across the await and
is cleared in a finally as soon as authorize settles. Liveness only —
zero assertion change (decidedBy:'timeout' / fail-closed semantics
verbatim).
Swept the rest of checks/ for the same direct-await-on-unref'd-timer
shape: zero further hits (state-bus throttle tests await self-ref'd
sleep() windows; alerts heartbeat and chat 2 s force-kill are never
awaited; all other authorize() consumers go through withDecisions).
…rain Fork preview run 35493902383 (windows-latest, Node 22) failed the whole checks/routes-export.check.mjs file with 'Promise resolution is still pending but the event loop has already resolved' + cancelledByParent: mock unit tests drive route handlers with zero real IO, so while fn() awaits authorize() the 2ms poll interval was the only ref'd handle in the loop — unref'ing it let the loop drain before _decideForTests could fire (POSIX passed only on incidental IO noise). Same fix shape as lib-authorize's REF'd watchdog (run 35493384574). - remove poll.unref(); interval stays REF'd for the whole fn() window - add 5s safety self-clear: clear the interval + reject if fn() never settles, so a broken test fails fast instead of hanging the run - decision-completion path keeps clearInterval semantics (finally) - liveness only: zero assertion or production-code change
…x/Node 24 router-boot hang (U4)
Repro evidence (siinfer, Ubuntu 24.04 / Node v24.21.0, mcode absent):
- timeout 60 node --test test/integration/router-boot.test.js hung
permanently after test 2; killed at 60s with "Promise resolution is
still pending but the event loop has already resolved" (/tmp/rb-repro.log).
- Standalone bisect (no node:test): spawn real server.js, GET /api/state
-> no response within 10s; /api/health and 404 path respond in ~100ms.
- Micro-repro: McodeAcpClient.start() neither resolves nor rejects on
spawn ENOENT — Node 24 fires error+close but NOT exit for spawn
failures, and pending requests were rejected only in the exit handler.
Root-cause chain: handleState -> getMcodeSessionsForWorkspace ->
getMcodeAcpClient -> start() -> request("initialize") pending forever ->
response never sent -> test httpRequest (no timeout) hangs -> spawned
server child keeps the event loop alive. Local macOS runs green only
because ~/.minimax-code/bin/mcode is in PATH — environmental-noise green.
Fix (settlement guarantee — waiters must not depend on peer or
environment liveness):
- acp.mjs: reject all pending on child "error" and "close" (idempotent
drain helper; "exit" alone misses spawn failures). Guard the error
re-emit with listenerCount — bare emit("error") throws Unhandled
"error" event in embedders without a global handler; the server's
uncaughtException logger swallowed it into a live hang.
- router-boot.test.js httpRequest: 10s timeout bail-out (was none).
- _setup.js decideNextAuthorization: bail-out timer now covers the
decision POST phase too (previously cleared on frame arrival).
Runs:
- siinfer: timeout 60 node --test router-boot.test.js -> 14/14 pass,
exit 0, ~4.8s. router-boot/sse-channel/event-chain/chat-wiring all
green under timeout 120 in both plain and module-mocks modes, exit 0.
- local (macOS, Node v26.7.0): mocks full suite 929 tests, 923 pass,
4 fail (pre-existing better-sqlite3 NODE_MODULE_VERSION ABI); plain
unit suite 493 tests, 491 pass, 2 fail (same pre-existing ABI).
…mptions Windows-latest fork-preview run 35495306680 left ~26 failures, all environment/platform assumptions, none production defects (macOS and ubuntu-latest stay green). Honest gating per the real preconditions; zero assertion weakening anywhere the environment genuinely provides them: - lib-db.test.js / sessions.check.mjs: the sqlite3-fixture suites now gate on BOTH a working sqlite3 CLI (spawnSync --version probe via config.SQLITE3_BIN) AND a truly loadable better-sqlite3 probed through db.js's own resolver. Truthiness of getMcodeBetterSqlite3() is not enough — the package require()s cleanly on ABI mismatch and only new Database() throws — so the gate constructs a :memory: database. Skip reason names the missing precondition. - lib-db-resolver(.test.js / -c01.test.js): 10 POSIX forward-slash literal expectations rewritten to mirror the resolver's own host path.join construction (plus a segment-tail comparison for the PATH-placeholder filter) — assertions now run on every platform. - lib-events.test.js: chmod-on-directory fail-closed test skips on win32 (chmod lacks write-permission semantics there); POSIX runs it in full. - lib-mcode-exec.test.js: POSIX bare-name PATH fixture joins entries with node:path delimiter instead of a hardcoded ':' so the probe resolves on a win32 host too; no-entry-rewriting assertion kept. Local (macOS, ABI-mismatched better-sqlite3): full surface 929 tests/923 pass/4 fail -> 925/923/0 fail + 3 reason-carrying suite skips covering the 4 formerly-failing fixtures; flagless 537/535/2 fail -> 535/535/0 fail + 2 suite skips; check-docs-alignment exit 0. Refs: fork-preview run 35495306680 (windows-latest, Node 22).
|
Rigor batch landed at exact head CodeQL — all six alerts fixed, zero remaining. The three inline ones (polynomial Bearer regex → disjoint character classes; exception-text-as-HTML in main.js → DOM construction + Deeper findings the same pass uncovered:
Verification matrix (all at |
PR 描述 — Mcode-webui 插件
本 PR 新增内容
plugins/Wzdhehe/mcode-webui/,符合 Agent Plugins 1.0 规范plugin.json(版本2.0.0)含 10 个白名单顶层字段skills/mcode-webui/SKILL.md含{name, description}frontmatterLICENSE(MIT)README.md(用户向快速上手 +docs/screenshots/5 张真实截图)references/SECURITY-NOTES.md(权威安全披露)docs/(ARCHITECTURE、API、CAPABILITIES、DEVELOPMENT、TROUBLESHOOTING、BORROW-harness-v2、MATH-skeleton-webui-v2、ANTI-PATTERNS-FIX-PLAN、
PROJECT-CHARTER-webui-v2、VERIFICATION-REPORT、HTTPS-REVERSE-PROXY、CI)
server/、public/、test/、scripts/(真实目录,与项目根保持同步;打包时按原样进入
dist/作为发布产物)package.json(与项目根一致,含setup:plugin、package:plugin、check、sbom脚本)v2.0.0 相对 v1.x 的变更(工业化)
本 PR supersede PR #16(v1.0.0 marketplace 提交,OPEN 28 天,@hetaoBackend 给了
4 条
CHANGES_REQUESTEDreview)。v2.0.0 用结构性改写代替 patch 方式逐条回应:
server/auth.js缺 v1.0.1 setter 导出setExpectedToken/setTokenAuthEnabled/markFirstRunNotified;isFirstRun读持久化状态server/lib/auth.js(B03 + §6.2 reconcile)server/lib/db.js:24-34硬编码better-sqlite3解析器MCODE_BETTER_SQLITE3环境变量 →MCODE_CMD反向回溯 →~/.mcode-webui/db-resolver.json→ 内置 fallback)server/lib/db.js(C01)SECURITY-NOTES.md的环境变量没在config.js导出;?token=与Authorization头不一致MCODE_WEBUI_UPLOAD_DIR/MCODE_WEBUI_SETTINGS_PATH/MCODE_BETTER_SQLITE3/DEBUG_INJECT);scripts/check-docs-alignment.mjs升级为 CI 闸门,断言 README + plugin + SECURITY-NOTES ↔ router.js + config.js 三向一致server/lib/config.js(§6.2)+scripts/check-docs-alignment.mjs(B05)plugins/Wzdhehe/mcode-webui/下每个文件都是新写或改动);前 3 条 finding 由结构性变更关掉;含Wzdhehe/Mcode-webui的 round-8 CSRF 修复在关上旧 review 之外,v2.0.0 还加了 9 项工业级属性,由 19 个实施 lease 支撑
(详见
docs/VERIFICATION-REPORT.md的头条摘要 +docs/PROJECT-CHARTER-webui-v2.md章程):server/lib/events.js(B01)+ 18 hook 点server/lib/alerts.js+server/routes/alerts.js(B02)authorize(action, ctx)Promise,默认 5 分钟 fail-closed,16 个 hook 点server/lib/authorize.js(B03)package-lock.json+ CycloneDX 1.5 SBOM + npm-audit 集成.github/workflows/ci.yml+scripts/gen-sbom.mjs(C02)node --test矩阵(Node 22 / Node 24 × macOS / Linux / Windows).github/workflows/ci.yml(C02)plugin.jsoncapability 都带description(204-286 字符);CONTRIBUTING.md 含 "Common npm test failures" 小节plugin.json+README.md+CONTRIBUTING.md(B05)sih-math定理引用(PROB-018 / ORD-022 / TOP-008 / ALG-001 等)docs/MATH-skeleton-webui-v2-2026-09-20.md(A02)scripts/check-docs-alignment.mjs在 v2 reconcile 后于 CI 退出 0scripts/check-docs-alignment.mjs(B05)+ §6 reconcile在 v1.x 之上新增的能力:
/api/sessions/search?q=…(B05 + C05)/api/sessions/:id/export(C06)/api/usage/forecast— 最小二乘线性拟合 + R² 置信度(C07)MCODE_WEBUI_TOKEN_STDOUT=1环境变量门控(C08)MCODE_WEBUI_RATE_LIMIT/_BURST(C03)为什么做这个插件
Kimi-Code 风格的 web 前端对接
mcodeagent 运行时。让用户在浏览器而不是终端里打开
mcode会话,实时流式查看工具事件,切换 workspace,使用?token=入站modal——全不占用 Mcode TUI 的终端。
示例 prompt(含预期结果)
Prompt 1 — 用户:"打开 Mcode webui"
预期:
node server.js(前台或后台任选)open日志行Prompt 2 — 用户:"Mcode webui 状态"
预期:
.server.err看最近错误Prompt 3 — 用户:"显示 Mcode webui url"
预期:
http://<lan-ip>:8080/TOKEN)也输出带?token=…的完整 URL完整触发列表见
SKILL.md。依赖
mcodeCLI 0.1.4+(用于mcode acp传输)sqlite3二进制(用于 usage 面板)— 由server/lib/config.js#detectSqlite3Bin自动检测mavis0.1.0+(用于真实 token 使用量;缺失时降级为估算)网络与数据行为
0.0.0.0:8080— 通过HOST=127.0.0.1切到 loopback 唯一?token=查询字符串(浏览器友好);也接受Authorization: Bearer头mcode、mmx quota)~/.minimax/v2/sqlite/runtime-state.sqlite(只读)~/.minimax/v2/sqlite/runtime-state.sqlite— 仅在DELETE /api/sessions/:id时(带
?dryRun=true可选预览)MCODE_WEBUI_UPLOAD_DIR(默认.webui-uploads/)用于文件上传~/.minimax-code/webui/.webui-sessions.json用于会话存储完整披露:
references/SECURITY-NOTES.md。自动测试证据
覆盖率(用
c8对server/lib/**/*.js+scripts/**/*.mjs):测试分布(节选):
lib-events.test.js— 27 tests(NDJSON 原子写、单调 seq、hash 链)lib-events-hash.test.js— 8 tests(篡改检测、链恢复)lib-alerts.test.js— 20 tests(3 个等级、60s dedup、ring buffer、dedup 在 ring 翻转后仍命中 + audit payload 完整 — 在 V01 review 后 commit 8f2e86b 加的 regression 覆盖)routes-alerts.test.js— 6 tests(SSE replay、heartbeat、close)lib-authorize.test.js— 20 tests(白名单、超时、per-cid 清理)lib-interaction.test.js— 37 tests(commands 解析器、permission presets、ask-user modal)lib-feedback.test.js— 12 tests(命令反馈、消息点赞/点踩)check-docs-alignment.test.js— 15 tests(live integration、drift round-trip)CI:GitHub Actions 上 Node 22 / Node 24 × macOS / Linux / Windows。
完整 PR 套件跑
npm run check:ci && npm test && npm run sbom && npm audit。手动测试证据
mavis plugin install安装插件(路径模式)TOKEN=$(openssl rand -hex 16)http://127.0.0.1:8080/?token=…— SSE 流连接成功,模型流式渲染lanBroadcast: false— 手机端返回 403 带友好页对生产
runtime-state.sqlite的副本(713 MB)用MCODE_RUNTIME_DB=<copy>;一个有 11,176 行跨 12 表的会话减到 7 行(只剩
questionnaire_requests,按设计跳过 — 它不是
local_runtime_*前缀的)。表清单覆盖 Mcode schema33 个 session-keyed 表中的 32 个。
?dryRun=true重跑删除 — 预览显示行数,无修改红线合规(mcode-plugin-guide)
DELETE /api/sessions/:id有?dryRun=true可选预览。真删走 SQLite
transaction(),每表容错。detectSqlite3Bin()自动检测 — 不写死主机路径。references/SECURITY-NOTES.md是单一权威源;SKILL.md(TL;DR + 链接)、
plugin.json(extensions.securityNotes)、本 PR 描述、插件README.md都引用它。extensions.securityNotes、PR 模板。Checklist
plugin.json通过https://agent-plugins.org/schemas/1.0.0/plugin.schema.json校验npm test— 828 pass, 4 fail(pre-existing better-sqlite3 ABI),2 skippednpm run check— exit 0(6 个 alignment 组全绿)npm run sbom— CycloneDX 1.5 输出,115 componentsnpm audit— 0 vulnerabilitiesreferences/SECURITY-NOTES.md覆盖所有红线 7 主题 + 4 个环境变量导出docs/screenshots/5 张真实截图hooks/ 不支持的 capability 字段plugins/Wzdhehe/mcode-webui/)Closes #16(v1.0.0 marketplace 提交)— 本 PR merge 时自动关闭 Add plugin: mcode-webui (Wzdhehe) #16Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.