Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
37 changes: 37 additions & 0 deletions plugins/Wzdhehe/mcode-webui/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Contributing to Mcode-webui plugin

This is the packaged plugin view of the project. The full
contribution guide lives in the **source repo**:

**[github.com/Wzdhehe/Mcode-webui → CONTRIBUTING.md](https://github.com/Wzdhehe/Mcode-webui/blob/main/CONTRIBUTING.md)**

## Quick reference

| Need to … | Read |
|-----------|------|
| Add a route, event, or UI panel | [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) |
| Update a config / env var | [server/lib/config.js](server/lib/config.js) + [docs/API.md](docs/API.md) |
| Bump the version | `package.json` (root + plugin copy) + `plugin.json` |
| Update capability list | [docs/CAPABILITIES.md](docs/CAPABILITIES.md) + `plugin.json#extensions.capabilities` |
| Change a security disclosure | [references/SECURITY-NOTES.md](references/SECURITY-NOTES.md) (the single source of truth) |

## Sync rule

The plugin tree here (`server/`, `public/`, `test/`, `docs/`) is a
**real copy** of the source-repo root. When you change a file at
the root, mirror the same change here in the same commit, or run
`npm run package:plugin` at the source repo to regenerate the
plugin tree.

## Submitting to the community registry

The official
[MiniMax-Code-Plugins](https://github.com/MiniMax-AI/MiniMax-Code-Plugins)
repo accepts plugin submissions as folders under
`plugins/<author>/<plugin-name>/`. The `plugins/Wzdhehe/mcode-webui/`
tree in this repo is the unit of submission — fork the registry,
copy this folder in, open a PR.

The official gate is `npm run check` at the registry root. This
repo ships a mirror (`npm run validate:plugin`) that runs the same
checks locally before you push.
21 changes: 21 additions & 0 deletions plugins/Wzdhehe/mcode-webui/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Wzdhehe

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
154 changes: 154 additions & 0 deletions plugins/Wzdhehe/mcode-webui/PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# PR Description — Mcode-webui plugin

> **Submission body for the upstream PR to the
> [MiniMax-Code-Plugins](https://github.com/MiniMax-AI/MiniMax-Code-Plugins)
> community registry. Use this as the PR body verbatim.**

## What this PR adds

- New plugin at `plugins/Wzdhehe/mcode-webui/` per Agent Plugins 1.0 spec
- `plugin.json` with the 10 white-listed top-level fields
- `skills/mcode-webui/SKILL.md` with `{name, description}` frontmatter (343 chars) + body (official skills/ layout)
- `LICENSE` (MIT)
- `README.md` (user-facing quick start)
- `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; packaged as-is into `dist/` for the release artifact)
- `package.json` (copy of project root, with `setup:plugin` and
`package:plugin` scripts)

## Why this plugin

A Kimi-Code-style web frontend for the `mcode` agent runtime. It lets
users open `mcode` sessions in a browser instead of the terminal,
stream real-time tool events, switch workspaces, and use the
`ask-user` modal — all without the Mcode TUI eating their terminal.

## Example prompts (with expected results)

**Prompt 1** — User: "open Mcode webui"

Expected:
1. Run `node server.js` (foreground or background, your call)
2. Wait for the SSE `open` log line on stdout
3. Tell the user: "webui running at http://127.0.0.1:8080/ (or http://<lan-ip>:8080/ for LAN)"

**Prompt 2** — User: "Mcode webui status"

Expected:
1. Check if port 8080 is in use
2. If listening: report "running" + URL; if not: report "not running"
3. Optionally read `.server.err` for last error

**Prompt 3** — User: "show Mcode webui url"

Expected:
1. Print `http://<lan-ip>:8080/`
2. (If `TOKEN` is set) also print the full URL with `?token=…`

Full trigger list in [`SKILL.md`](SKILL.md#when-to-use-this-skill).

## Dependencies

- **Runtime**: Node 22.19+ stdlib only (zero npm deps)
- **External binary**: `mcode` CLI 0.1.4+ (for `mcode acp` transport)
- **Optional**: `sqlite3` binary (for usage panel) — auto-detected via
`server/lib/config.js#detectSqlite3Bin`
- **Optional**: `mavis` 0.1.0+ (for real token usage; degrades to
estimates if missing)

## Network & data behavior

- **Binds `0.0.0.0:8080` by default** — loopback-only via `HOST=127.0.0.1`
- **`?token=` query string** supported (browser convenience);
`Authorization: Bearer` header also accepted
- **No outbound network** — only local subprocesses (`mcode`, `mmx quota`)
- **Reads**: `~/.minimax/v2/sqlite/runtime-state.sqlite` (read-only)
- **Writes**:
- `~/.minimax/v2/sqlite/runtime-state.sqlite` — only on
`DELETE /api/sessions/:id` (with `?dryRun=true` opt-in preview)
- `MCODE_WEBUI_UPLOAD_DIR` (default `.webui-uploads/`) for file uploads
- `~/.minimax-code/webui/.webui-sessions.json` for session store
- **No telemetry, no remote endpoints**

Full disclosure: [`references/SECURITY-NOTES.md`](references/SECURITY-NOTES.md).

## Automated test evidence

```
$ npm test
ℹ tests 291
ℹ suites 86
ℹ pass 290
ℹ fail 0
ℹ skipped 1
ℹ duration_ms ~550

$ npm run lint
> eslint server/ test/
(0 errors, 0 warnings)
```

Test breakdown:
- `lib-config.test.js` — 28 tests (constants, env loading, sqlite detection)
- `lib-lan.test.js` — local request detection, LAN IP detection
- `lib-db.test.js` — `deleteMcodeSessionFromDb` happy path + missing-table
tolerance, dryRun path
- `lib-state-bus.test.js` — per-cid state isolation, SSE channel mgmt
- `mavis-usage.test.js` — real sqlite3 fixture, per-turn context math
- `sessions.test.js` — `?dryRun=true` preview, route-level session
CRUD with rollback
- `chat.test.js`, `routes-*.test.js` — error path coverage

CI: GitHub Actions on Node 22 / Node 24, Windows + Linux + macOS.

## Manual test evidence

- Installed plugin via `mavis plugin install` (path mode)
- Set `TOKEN=$(openssl rand -hex 16)`
- Opened `http://127.0.0.1:8080/?token=…` in browser — SSE stream
connected, model stream rendered
- Opened same URL on phone (LAN) — token auth accepted, mobile
layout responsive
- Ran a multi-turn session with tool calls (Bash, Read, Edit) —
all events rendered, quota panel updated
- Toggled `lanBroadcast: false` — phone got 403 with friendly page
- Deleted a session — log shows rows removed from all session-keyed
tables. v1.0 E2E evidence: ran the real-delete path against a copy of
the production `runtime-state.sqlite` (713 MB) via
`MCODE_RUNTIME_DB=<copy>`; a session with 11,176 rows across 12 tables
was reduced to 7 rows (only `questionnaire_requests` remains, skipped
by design — not `local_runtime_*`-prefixed). The table list covers
32 of the 33 session-keyed tables in the Mcode schema.
- Re-ran delete with `?dryRun=true` — preview shows row count, no
modification
- Restarted server — orphan mcode acp child cleaned up via SIGTERM

## Red-line compliance (mcode-plugin-guide)

- **Red-line 1 (destructive ops)**: `DELETE /api/sessions/:id` has
`?dryRun=true` opt-in preview. Real delete runs in a SQLite
`transaction()` with per-table error tolerance.
- **Red-line 2 (cross-platform)**: sqlite3 binary is auto-detected via
`detectSqlite3Bin()` — no hardcoded host paths.
- **Red-line 3 (披露完整性)**: `references/SECURITY-NOTES.md` is the
single source of truth; `SKILL.md` (TL;DR + link), `plugin.json`
(`extensions.securityNotes`), this PR description, and the plugin
`README.md` all reference it.
- **Red-line 7 (披露完整性)**: 3-place consistency — README,
plugin.json description + `extensions.securityNotes`, PR template.

## Checklist

- [x] `plugin.json` validates against `https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`
- [x] `npm run validate-plugin` (planned batch H) passes
- [x] `npm test` — 261 pass, 0 fail, 0 lint warning
- [x] `references/SECURITY-NOTES.md` covers all red-line 7 topics
- [x] LICENSE present (MIT)
- [x] README.md present and non-empty
- [x] No symlinks (release artifact expands junctions)
- [x] No UTF-8 BOM in any text file
- [x] No placeholder markers in shipped files
- [x] No `hooks` / unsupported capability fields
- [x] One plugin per PR (this PR is only `plugins/Wzdhehe/mcode-webui/`)
139 changes: 139 additions & 0 deletions plugins/Wzdhehe/mcode-webui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Mcode Web UI Plugin

> **Browser-based chat frontend for the Mcode agent runtime.**
> Streams `mcode acp` / `mcode exec` sessions in real time. Zero npm
> dependencies; runs on Node 22.19+.

This is the mcode-plugin-guide (Agent Plugins 1.0) packaging of the
[Mcode-webui](https://github.com/Wzdhehe/Mcode-webui) web frontend.

## Quick start

```bash
# 1. Install the plugin (per mavis / MiniMax Code plugin loader)
# 2. Set TOKEN (recommended on non-loopback networks)
export TOKEN="$(openssl rand -hex 16)"
# 3. Start the plugin
node server.js
# 4. Open in browser
# http://127.0.0.1:8080/?token=$TOKEN
```

## What's in the box

| File | What |
|------|------|
| `plugin.json` | Agent Plugins 1.0 manifest (10 top-level fields, white-listed) |
| `SKILL.md` | This plugin's skill description (frontmatter + body) |
| `LICENSE` | MIT |
| `README.md` | This file |
| `references/SECURITY-NOTES.md` | **Canonical security disclosure** (read this before installing) |
| `docs/` | ARCHITECTURE, API, CAPABILITIES, DEVELOPMENT, TROUBLESHOOTING |
| `server/` | Node.js HTTP + SSE server |
| `public/` | Static frontend SPA |
| `test/` | `node:test` unit tests |
| `package.json` | Project metadata + scripts |

## Configuration

All settings are environment variables. See
[SKILL.md § Configuration](SKILL.md#configuration-environment-variables)
and [`server/lib/config.js`](server/lib/config.js) for the canonical
list. Most relevant:

| Variable | Default | Purpose |
|----------|---------|---------|
| `PORT` | `8080` | HTTP listen port (default was `7890` before v0.5) |
| `HOST` | `0.0.0.0` | Bind address (override to `127.0.0.1` for loopback-only) |
| `TOKEN` | (empty) | Required token for non-local requests. **v1.0.1**: if unset, server auto-generates a 32-hex token on first start (see "Token auth" below). |
| `MCODE_WEBUI_SETTINGS_PATH` | `~/.mcode-webui/settings.json` | **v1.0.1**: override the settings file location (tests, non-default installs). |

## Token auth (v1.0.1)

Token auth is the headline change in v1.0.1 — it addresses PR #16
reviewer feedback that `SECURITY-NOTES.md §2` documented
`?token=` / `Authorization: Bearer` but the code had no real auth
gate. v1.0.1 implements three pieces, each documented as a
separate concern below.

### Token auth: default-on

On first start with no `TOKEN` env set, the server **auto-generates
a 32-hex-char token** (`crypto.randomBytes(16).toString('hex')`),
persists it to `~/.mcode-webui/settings.json` (mode `0600` on Unix;
best-effort on Windows), and **prints it to stdout exactly once**
(never to `.server.log` — copy it from the terminal before it
scrolls off). The settings card in the bottom-left sub-card
shows the token on first open; until you click
"我已保存 / I have saved it", `GET /api/settings` and the SSE
state push keep including the `currentToken` field. Setting
`TOKEN` env still wins (the env path is unchanged — this is
purely additive).

### Token auth: reset + live broadcast

The settings card has a "重置 token / Reset token" button. Click
it → confirm → the server generates a new 32-hex value,
persists it, and **broadcasts an `auth.token_rotated` SSE
event** with the new token to every connected client. Each
client that receives the event updates its `localStorage`
(`webui_token` key) and the live `HEADERS.Authorization`
object **in place** — subsequent `fetch()` calls use the new
token automatically, no reload required. Offline clients
that missed the broadcast will get a `401` on their next
request, at which point they need to be re-sent the new URL
(with `?token=`) manually. `rotateToken` is **crash-safe**:
persists to disk first, then commits in-memory state; if
disk write fails, in-memory is rolled back and the API
returns `500`.

### Token auth: acknowledged state machine

After clicking "我已保存 / I have saved it" in the settings
card, the server records `tokenAcknowledged=true` and
**stops including `currentToken` in subsequent
`GET /api/settings` responses and SSE state pushes**. The
UI replaces the value/mask row with a `✓ 已保存 — 查看请点
"重置" / Saved — click "Reset" to view again` placeholder
(show / copy buttons disappear). To view the token again
you must hit "Reset token" (which produces a new value and
a new broadcast). The acknowledged flag prevents accidental
token disclosure in `/api/settings` responses if a stale
client or external monitor is scraping the endpoint. The
state is persisted alongside the token itself, so the
acknowledged flag survives server restarts.

## Security disclosure (READ THIS)

Full disclosure is in
[`references/SECURITY-NOTES.md`](references/SECURITY-NOTES.md). Key points:

- Default binds `0.0.0.0` — reachable from any device on the LAN. Use
`HOST=127.0.0.1` for loopback-only mode.
- `?token=` query string is supported for browser convenience. Prefer
`Authorization: Bearer` header for any non-browser caller.
- `DELETE /api/sessions/:id` writes to the user's real mavis sqlite
(`~/.minimax/v2/sqlite/runtime-state.sqlite`). Pass `?dryRun=true` to
preview before committing.
- No telemetry, no remote endpoints, no third-party subprocesses.

## Documentation

| Doc | What |
|-----|------|
| [SKILL.md](SKILL.md) | Plugin skill description + trigger examples |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Module topology, request lifecycle, SSE schema |
| [docs/API.md](docs/API.md) | Every HTTP endpoint with request/response schema |
| [docs/CAPABILITIES.md](docs/CAPABILITIES.md) | Capability matrix — what works, what doesn't |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Dev setup + how to add a route/UI panel |
| [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common errors with verified fixes |

## License

MIT — see [LICENSE](LICENSE).

## Maintainer

- **Author**: Wzdhehe
- **Repository**: https://github.com/Wzdhehe/Mcode-webui
- **Homepage**: https://github.com/Wzdhehe/Mcode-webui
Loading