From e8217e70559c23f2a756f8bde786275d7bc259ec Mon Sep 17 00:00:00 2001 From: Menglei Li <57990306+Lumos-789@users.noreply.github.com> Date: Mon, 9 Nov 2026 02:03:50 +0800 Subject: [PATCH] feat(malo-browser): add malo-browser plugin v0.1.11 --- assets/malo-browser/icon.png | Bin 0 -> 16671 bytes .../malo-browser/.codex-plugin/plugin.json | 33 + .../malo-browser/.zcode-plugin/plugin.json | 24 + external_plugins/malo-browser/CHANGELOG.md | 266 ++++++++ external_plugins/malo-browser/LICENSE | 21 + external_plugins/malo-browser/README.md | 570 ++++++++++++++++++ external_plugins/malo-browser/README_CN.md | 518 ++++++++++++++++ .../malo-browser/assets/malo-browser/icon.png | Bin 0 -> 16671 bytes .../malo-browser/assets/malo-browser/icon.svg | 14 + .../malo-browser/clients/python/README.md | 22 + .../clients/python/malo_client.py | 288 +++++++++ .../malo-browser/cordis.patch.yml | 3 + .../malo-browser/docs/accounts.md | 127 ++++ .../malo-browser/docs/malo-browser.md | 500 +++++++++++++++ .../malo-browser/docs/malo-web-compat.md | 55 ++ external_plugins/malo-browser/index.js | 26 + .../malo-browser/schemas/scene.schema.json | 542 +++++++++++++++++ .../malo-browser/skills/malo-browser/SKILL.md | 174 ++++++ marketplace.json | 88 ++- 19 files changed, 3258 insertions(+), 13 deletions(-) create mode 100644 assets/malo-browser/icon.png create mode 100644 external_plugins/malo-browser/.codex-plugin/plugin.json create mode 100644 external_plugins/malo-browser/.zcode-plugin/plugin.json create mode 100644 external_plugins/malo-browser/CHANGELOG.md create mode 100644 external_plugins/malo-browser/LICENSE create mode 100644 external_plugins/malo-browser/README.md create mode 100644 external_plugins/malo-browser/README_CN.md create mode 100644 external_plugins/malo-browser/assets/malo-browser/icon.png create mode 100644 external_plugins/malo-browser/assets/malo-browser/icon.svg create mode 100644 external_plugins/malo-browser/clients/python/README.md create mode 100644 external_plugins/malo-browser/clients/python/malo_client.py create mode 100644 external_plugins/malo-browser/cordis.patch.yml create mode 100644 external_plugins/malo-browser/docs/accounts.md create mode 100644 external_plugins/malo-browser/docs/malo-browser.md create mode 100644 external_plugins/malo-browser/docs/malo-web-compat.md create mode 100644 external_plugins/malo-browser/index.js create mode 100644 external_plugins/malo-browser/schemas/scene.schema.json create mode 100644 external_plugins/malo-browser/skills/malo-browser/SKILL.md diff --git a/assets/malo-browser/icon.png b/assets/malo-browser/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1e2f4cf002de34d14f7db99656d3db5feb81ca GIT binary patch literal 16671 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

Malo Browser icon

+ +

Malo Browser

+ +A browser for scripted demos. It is built on Electron + Chromium and splits a web demo +into a Stage (what the audience sees) and a Recorder (fixed-spec capture) without +forking the browser engine; demo flows are written as scene scripts, with a Director +console and a loopback control API on top. + +This repository is a working prototype (0.1.x): hidden recording, physical CDP input, +an overlay, 1080p30 output, evidence files and a minimal `.app`. It is also the +`malo-browser` agent runtime: a background daemon hands out port leases so several +agents can drive isolated browser sessions at the same time. + +## Install + +### Requirements + +- Node 22.12+ on the 22 line, 24.x, or 26+ (`engines` is + `^22.12.0 || ^24.0.0 || >=26.0.0`; the 23 and 25 lines are excluded). The range is + declared in `package.json`, so a mismatch surfaces during install (yarn fails; + npm/pnpm print an EBADENGINE warning and fail under `engine-strict`) instead of deep + inside vite/vitest. +- ffmpeg and ffprobe on `PATH` (resolved from `PATH` first, then the Homebrew/local + fallback directories); missing binaries exit with code 3. +- macOS for the verified path: window modes and recording are tested there, and hidden + mode uses the accessory activation policy. +- Python 3 only for the bundled fixture static server (`python3 -m http.server`). +- git. + +### From source + +```bash +git clone https://github.com/Lumos-789/malo-browser.git +cd malo-browser +npm install +npm run build +``` + +`npm run build` compiles the main process with tsc and bundles the fixture and the +director console with vite. `npm run typecheck` type-checks the main and renderer +projects without emitting, and `npm test` runs the vitest unit suite. + +### Plugin packaging + +The repository is both the app and the plugin carrier; there is no second copy of the +source. + +- ZCode plugin: `.zcode-plugin/plugin.json` (name `malo-browser`, version kept in sync + with the repo), skill `skills/malo-browser/SKILL.md` (install / configure / + troubleshoot; it does not reimplement the product), icon + `assets/malo-browser/icon.png` (512x512). With the `dev-oss-projects` marketplace + registered: `zcode plugins install malo-browser@dev-oss-projects -s user`. +- DSH bundle: `package.json` declares `dsh.bundle` (patch -> repo-root + `cordis.patch.yml`) and `index.js` mounts `skills/` through `bundledSkillDir`. + `engines.dsh` and the optional `@deepseek-ai/*` peerDependencies are in + `package.json`; because the peer is optional, it does not affect `npm install` for the + Electron app. + +## Run + +Serve the fixture page first (the default demo target), in another terminal: + +```bash +python3 -m http.server 4788 --bind 127.0.0.1 --directory out/fixture +``` + +Default: hidden background recording for 10 seconds. + +```bash +npx electron . --mode=record --seconds=10 --out=evidence --label=hidden +``` + +The default recording shows no window, takes no focus and claims no Dock icon on macOS +(`setActivationPolicy('accessory')`), so you can keep using the machine while it records. + +| Command | What it does | +|---|---| +| `npm run build` | main process (tsc), fixture and director (vite) | +| `npm run typecheck` | type-check main + renderer projects, no emit | +| `npm test` | vitest unit suite | +| `npm start` | Director console + Stage; the Stage shows as `inactive` and does not steal focus | +| `npm run start:control` | same, plus the loopback control API on `http://127.0.0.1:7317` | +| `npx electron . --mode=record --seconds=10` | hidden recording (default) | +| `npx electron . --mode=record --window-mode=inactive --seconds=10` | visible but unfocused recording | +| `npx electron . --mode=record --plain --seconds=10` | no interaction, static page only (control run) | +| `npx electron . --mode=app --autorun=report-review --scenes=fixtures/scenes` | run a whole scene headless and produce a take | +| `npx electron . --mode=app --director-check` | invisible check of the Director and the preload bridge | +| `npx electron . --mode=app --headless=true --control-api=true` | windowless background instance with only the control API and recording | +| `npx electron . --mode=stage --window-mode=inactive` | visible Stage for manual browsing | +| `npm run daemon` | malo-browser broker daemon handing out leases to agents | +| `npm run smoke:malo -- ` | real multi-agent / multi-session path | +| `npm run smoke:toolbar` | hidden check of the handoff toolbar (preload, Enter navigation, address sync, back, page viewport) | +| `npm run smoke:control -- report-review` | against a running control API: load scene -> record -> verify evidence | +| `npm run package` | package an unpacked app under `dist/` (Apple Silicon: `dist/mac-arm64/Malo Browser.app`) | +| `./scripts/run-phase0.sh` | Phase 0 primary evidence run (hidden + inactive) | + +`--mode=stage` follows `--window-mode`, and its default is `hidden`; pass +`--window-mode=inactive` (or `focused`) to actually see the Stage. + +Common flags: `--url`, `--width`, `--height`, `--out`, `--label`, `--keep-frames`, +`--window-mode=hidden|inactive|offscreen|ghost|focused`, `--focus-emulation=false`, +`--control-api=true`, `--control-port`, `--headless=true`, `--scenes`, `--autorun`, +`--plain`. A misspelled flag fails fast; `--help` prints the full reference and +`--version` prints the version. + +The app control API binds only to loopback, generates a random token at startup and +writes it to `/control.json` (0600). Routes and curl examples are in the +[App control API](#app-control-api) section below. + +### Window and focus modes + +Measured in Phase 0: + +| Mode | Behaviour | Recordable | Interactive | +|---|---|---|---| +| `hidden` (default) | window is never shown | yes | yes with focus emulation | +| `hidden` + `--focus-emulation=false` | window is never shown | yes | no, clicks are dropped | +| `inactive` | window is shown without taking focus | yes | yes | +| `offscreen` | window is shown at (-12000, -12000) | yes | yes | +| `ghost` | window is shown with opacity 0 | yes | yes | +| `focused` | window is shown and takes focus | yes | yes | + +Headless mode requires `Emulation.setFocusEmulationEnabled` (on by default), otherwise +CDP physical clicks do not take effect. + +### malo-browser agent runtime + +`malo-browser` is the background web-execution layer for agents: one long-lived daemon +manages port leases, every agent gets a stable port, and one port can host several +isolated sessions. There is no visible window by default; it does not take focus, does +not cover the screen and does not switch Spaces. Screenshots are produced only on +explicit request or as failure evidence. + +```bash +npm run daemon +# in another terminal +curl -s http://127.0.0.1:7410/health +cat ~/.malo-browser/broker.json +``` + +Defaults: broker `127.0.0.1:7410`, lease port pool `7411-7499`, data directory +`~/.malo-browser`. A successful start prints one JSON line and writes +`~/.malo-browser/broker.json` (0600) with `{endpoint, token, pid}`. Only one daemon may +use a data directory: a healthy running instance makes the new process fail loudly, and +a stale `broker.json` left by a stopped instance is overwritten. + +Daemon flags: `--daemon-port=7410`, `--port-min=7411`, `--port-max=7499`, +`--lease-ttl-seconds=300`, `--session-idle-seconds=300`, `--max-sessions=4`, +`--data-dir=~/.malo-browser`. + +A new session needs both the lease token and its own session token; a session token from +session A cannot read session B (401). `width` / `height` are the page viewport, and the +handoff toolbar does not consume them. Sessions without a `profile` are anonymous and +are wiped on close; sessions created with `{"profile":"work"}` share that profile's +cookies and storage across sessions and daemon restarts. Profile names allow lowercase +letters, digits, dot, underscore and hyphen (max 64 chars). Data lives under +`/leases//sessions//{downloads,artifacts}`, profile data +under `/chromium`. + +`delivery-video` is only a visual-output adapter; it does not define the malo-browser +kernel boundary. + +### Verification scripts + +- `npm run smoke:malo -- ~/.malo-browser/broker.json http://127.0.0.1:4788` runs the + real daemon chain (see also [docs/malo-browser.md](docs/malo-browser.md)). +- `npm run smoke:toolbar` builds and drives the handoff toolbar in a hidden window. +- `npm run smoke:control -- report-review` talks to an already running control API; set + `DEMO_CONTROL_URL` (default `http://127.0.0.1:7317`) and `DEMO_CONTROL_TOKEN` from + `/control.json`. + +## Scene scripts + +Scenes are YAML files at `fixtures/scenes//scene.yaml`, loaded through the +`demo://scene//...` protocol, which only serves files inside the scene directory. +The field contract is [schemas/scene.schema.json](schemas/scene.schema.json). + +### Bundled scenes + +- `report-review`: the currently usable demo, 7 steps + `wait_for -> highlight -> click -> fill -> assert -> spotlight -> annotate`. The page + loads as `demo://scene/report-review/index.html`. +- `multi-a -> multi-b`: verifies multi-scene switching: 1280x720 switches to 1600x900 + through one continuous screencast, and evidence records `run.scene_ids`. +- `login-demo`: the full account-login path (see below). + +Scene actions: `goto`, `login`, `wait`, `wait_for`, `wait_for_presenter`, `click`, +`fill`, `press`, `scroll`, `highlight`, `spotlight`, `annotate`, `mask`, `blackout`, +`assert`, `scene`, `set`, `note`, `screenshot` (19 actions). + +### Target resolution + +Targeted actions resolve in a fixed order, first match wins: + +| Priority | Form | Resolved to | +|---|---|---| +| 1 | `data_demo_id: increment` | `[data-demo-id="increment"]` | +| 2 | `selector: ".btn-primary"` | raw CSS selector | +| 3 | `text: 金额异常` | text match (rules below) | +| 4 | `x: 640` + `y: 360` | viewport point (no selector, last resort) | + +Text matching picks the smallest **visible** element (not hidden, has size) whose text +**contains** the target string, and marks it with a runtime attribute for later commands +in the same step. No match, or no target at all, reports `E_SELECTOR_NOT_FOUND` (the +message distinguishes "no element contains this text" from "the step has no resolvable +click or visual target"). + +Three special cases do not use `text` as the target: + +- `fill`: `text` is the input content; the target is `selector` / `data_demo_id` / + coordinates, or `target_text` names the element to click. +- `annotate`: `text` is the annotation copy; the target must be `selector` / + `data_demo_id` / coordinates. +- `wait_for`: `text` is the wait condition (wait for text to appear), not a click target. + +### Account login + +The `login` action uses a `0600` account file outside the repository; a scene stores only +`account_id`, the password is read from `password_env` at runtime, and the action opens +the login page, fills, submits and waits for a success condition. +`fixtures/scenes/login-demo` verifies the whole path. The default account file is +`~/.config/malo-browser/accounts.json`, overridable with `MALO_BROWSER_ACCOUNTS`. The file +must live outside a git worktree, be a regular `0600` file, keep `origin` and `login_url` +same-origin, and define `success_selector` or `success_url`; a plaintext `password` field +is rejected. Format, security boundary, examples and failure troubleshooting: +[docs/accounts.md](docs/accounts.md). + +### Scene variables + +`vars` holds scene-local strings; `${name}` interpolates them into action fields, and +`${env:SCENE_NAME}` reads an environment variable from the `SCENE_` namespace only — any +other name is rejected, so a third-party scene cannot pull cloud keys or tokens into a +URL or form field. + +### Validation + +Scene validation reports every error at once; unknown fields, out-of-range times and +missing targets fail before the scene loads. + +## Recording and evidence + +### Output files + +A `--mode=record` run writes, per take: + +```text +evidence/ + hidden.mp4 + hidden.evidence.json # version, viewport, DPR, frames, duration, SHA256, steps, self-checks + hidden.overlay-frame.png # overlay check frame (not written with --plain) + hidden.overlay-before.png # same moment before the overlay (not written with --plain) +``` + +An app-mode take (autorun or control API) writes +`/recordings/_/take-01.mp4` plus `take-01.evidence.json`; a failed +`assert` step also writes `/errors//step-.png` (other failing actions +carry the error, not a screenshot). + +App-mode evidence and the encoded mp4 are written with mode 0600. The document records `status`, +`app`, `scene`, `run` (including `run.scene_ids` for multi-scene takes), `capture`, +`video`, `steps`, `checks`, `warnings` and `error` when the take failed. + +### Self-checks + +A `--mode=record` take exits 0 only when every self-check passes: no Node in the window, +frames arrived, duration within ±0.5s, 30fps, 1920x1080, black-frame ratio <50%, and the +overlay really ended up in the video (skipped for `--plain`). + +App-mode takes check `frames_arrived`, `steps_completed` and `steps_within_take`; a +failed step or a failed check marks the evidence `failed` and the take carries +`video.partial`. Failed self-checks raise `E_EVIDENCE_MISMATCH` with the failing check +ids. In the autorun path a failed step still stops the recorder first, so the take keeps +its video and its failed evidence. + +### Failed takes + +"Failure also has evidence" holds for every entry point: + +- `--mode=record` writes `/