DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
M

malo-browser

Malo Browser

脚本化浏览器演示工具,支持 1080p30 证据录制;另含 malo-browser:具有端口租约和隔离会话的后台代理浏览器运行时

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:Lumos-789/malo-browser#a466a12354b0d130883b8decca9e9cbd50ab44bb
README兼容性版本

兼容性与来源证明

Malo Browser 以 malo-browser 发布,当前版本为 0.1.5。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
any
发布来源
github
Registry 更新时间
2026/11/4

版本

0.1.5stable
2026/11/4
0.1.1stable
2026/11/4

相关插件

正在加载相关插件…

最新版
0.1.5
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
any
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/11/4
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 developer-tools 分类下经过校验的插件。

Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Sdk Minimal@deepseek-ai/dsh-sdk-minimal独立的最小 SDK 配置包:JSON-RPC、一个 DeepSeek 适配器、持久化 Shell 和 JSONL 会话Sdk App@deepseek-ai/dsh-sdk-appdsh SDK 配置包:基于 dsh-base 提供 stdio JSON-RPC 服务和进程生命周期管理Experimental Inspector@deepseek-ai/dsh-experimental-inspector用于 Host 调试和 Client Runtime 检查的实验性跨 realm CDP hub

README

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

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:

python3 -m http.server 4788 --bind 127.0.0.1 --directory out/fixture

Default: hidden background recording for 10 seconds.

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.

CommandWhat it does
npm run buildmain process (tsc), fixture and director (vite)
npm run typechecktype-check main + renderer projects, no emit
npm testvitest unit suite
npm startDirector console + Stage; the Stage shows as inactive and does not steal focus
npm run start:controlsame, plus the loopback control API on http://127.0.0.1:7317
npx electron . --mode=record --seconds=10hidden recording (default)
npx electron . --mode=record --window-mode=inactive --seconds=10visible but unfocused recording
npx electron . --mode=record --plain --seconds=10no interaction, static page only (control run)
npx electron . --mode=app --autorun=report-review --scenes=fixtures/scenesrun a whole scene headless and produce a take
npx electron . --mode=app --director-checkinvisible check of the Director and the preload bridge
npx electron . --mode=app --headless=true --control-api=truewindowless background instance with only the control API and recording
npx electron . --mode=stage --window-mode=inactivevisible Stage for manual browsing
npm run daemonmalo-browser broker daemon handing out leases to agents
npm run smoke:malo -- <broker.json> <fixture-url>real multi-agent / multi-session path
npm run smoke:toolbarhidden check of the handoff toolbar (preload, Enter navigation, address sync, back, page viewport)
npm run smoke:control -- report-reviewagainst a running control API: load scene -> record -> verify evidence
npm run packagepackage an unpacked app under dist/ (Apple Silicon: dist/mac-arm64/Malo Browser.app)
./scripts/run-phase0.shPhase 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 <out>/control.json (0600). Routes and curl examples are in the App control API section below.

Window and focus modes

Measured in Phase 0:

ModeBehaviourRecordableInteractive
hidden (default)window is never shownyesyes with focus emulation
hidden + --focus-emulation=falsewindow is never shownyesno, clicks are dropped
inactivewindow is shown without taking focusyesyes
offscreenwindow is shown at (-12000, -12000)yesyes
ghostwindow is shown with opacity 0yesyes
focusedwindow is shown and takes focusyesyes

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.

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 <data-dir>/leases/<lease-id>/sessions/<session-id>/{downloads,artifacts}, profile data under <data-dir>/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).
  • 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 <out>/control.json.

Scene scripts

Scenes are YAML files at fixtures/scenes/<id>/scene.yaml, loaded through the demo://scene/<id>/... protocol, which only serves files inside the scene directory. The field contract is 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:

PriorityFormResolved to
1data_demo_id: increment[data-demo-id="increment"]
2selector: ".btn-primary"raw CSS selector
3text: 金额异常text match (rules below)
4x: 640 + y: 360viewport 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.

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:

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 <out>/recordings/<timestamp>_<scene>/take-01.mp4 plus take-01.evidence.json; failed step screenshots go to <out>/errors/<scene>/step-<index>.png.

App-mode evidence files 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 <out>/<label>.evidence.json with status: "failed", video: null, error.code, error.message and the failing error.step whenever the run throws before the video is encoded — opening the stage (for example an unreachable page, reported as ERR_CONNECTION_REFUSED), capture, step or encoding failure. artifacts lists any partial files that were written before the failure.
  • app mode (--autorun, control API, IPC) writes the take's take-01.evidence.json with status: "failed", video: null, error.code and error.message when the recorder fails before encoding — including the zero-frame case E_RECORDER_NO_FRAMES — and then rethrows the original error, so the exit code / control-API error is unchanged.
  • A take whose recorder succeeded but whose run had a failed step keeps the video (video.partial: true) and records the step error.
  • --keep-frames additionally keeps the raw frames of a failed take.

Exit codes

Exit codeMeaning
0success: every self-check passed (an --mode=app --autorun success also prints one JSON line on stdout)
2argument error or general failure (page load failure, E_SELECTOR_NOT_FOUND, assertion failure, CDP attach failure, scene validation failure, port busy, evidence self-check failure)
3ffmpeg/ffprobe missing or failed (E_FFMPEG_MISSING / E_FFMPEG_FAILED; install ffmpeg)
4recorder failure (E_RECORDER_NO_FRAMES and other capture/timeline errors)

Control API

App control API

Started with --control-api=true (or npm run start:control), it binds 127.0.0.1:7317 by default and drives the same dispatchCommand path as the Director:

curl http://127.0.0.1:$PORT/health
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:$PORT/state
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:$PORT/scenes
curl -X POST -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"cmd":"run.start","args":{"mode":"record"}}' http://127.0.0.1:$PORT/command
curl -N -H "Authorization: Bearer $TOKEN" http://127.0.0.1:$PORT/events

x-demo-token: $TOKEN is accepted as the equivalent of the bearer header. POST /command returns 200 with {"ok":true,"result":...}; DemoError maps to 400, E_PROFILE_LOCKED / E_STEP_TIMEOUT / E_RECORDER_NO_FRAMES to 409, and anything else to 500. /events is SSE: a : connected comment, then a : ping comment every 15 seconds, and data: lines carrying AppEvent JSON. The token is in <out>/control.json (0600) as {url, token, pid} and is compared with crypto.timingSafeEqual. The server sends no CORS headers, so a cross-origin browser script cannot call it; the API is for local scripts.

Agent tool API

The lease endpoint exposes agent tools. GET /v1/tools returns the list with JSON Schema, and a call is POST /v1/sessions/:id/tools/<tool> with both the lease token and the session token:

curl -X POST http://127.0.0.1:7411/v1/sessions/$SESSION_ID/tools/snapshot \
  -H "x-malo-lease-token: $LEASE_TOKEN" \
  -H "x-malo-session-token: $SESSION_TOKEN" \
  -H "content-type: application/json" \
  -d '{"max_chars":8000,"max_items":120}'

Tools: navigate, history, events, observe, snapshot, extract, click, fill, press, hover, select, focus, scroll, scroll_into_view, wait_for, sleep, assert, run_steps, network_capture, responses, watch, login, profiles, close. snapshot returns body text, title, links, buttons, inputs, forms, tables, landmarks, iframes, dialogs and selectors for interactive elements; extract does targeted reads. network_capture subscribes this session to XHR/fetch response bodies for matching URL prefixes and responses returns them by seq, in daemon memory only. run_steps runs a list of those tools in order (stop at the first failure by default), writes a run report to the session's artifacts/run-<timestamp>.json, and returns { ok, step_count, failed_step, steps, report_path }; watch, login, profiles and run_steps cannot be nested inside run_steps; network_capture and responses are session controls and are rejected there too, so captured bodies never reach a run report.

Status mapping on the lease endpoint: bad arguments, unknown tools, selector-not-found and navigation policy violations → 400; unknown session → 404; locked profile → 409; session cap → 429; navigation failure → 502; step timeout → 504; real server failures → 500. A navigation failure carries details.navigation (http_status, net_error, net_error_description, settle).

Security boundary: loopback only, Host must be 127.0.0.1:<port> or localhost:<port> (DNS-rebinding guard), JSON only with a 1MB body cap, broker / lease / session tokens are three separate layers, navigation is http:/https: only with URL-embedded credentials rejected, and window.open plus permission requests are denied by default. The old x-pagehand-* token headers are still accepted; new scripts use x-malo-*.

assert and run_steps verdicts

A failed assertion is a business verdict, not a broken service. assert answers HTTP 200 with the verdict on the result:

{
  "tool": "assert",
  "result": {
    "ok": false,
    "passed": false,
    "checks": [{ "kind": "visible", "expected": true, "actual": false, "ok": false }],
    "url": "https://example.com/done",
    "waited_ms": 5001,
    "screenshot": "<artifacts>/failure-<ms>.png",
    "error": {
      "code": "E_ASSERTION_FAILED",
      "message": "E_ASSERTION_FAILED: 断言失败:visible expected true, got false",
      "details": {
        "checks": [{ "kind": "visible", "expected": true, "actual": false, "ok": false }],
        "waited_ms": 5001,
        "screenshotPath": "<artifacts>/failure-<ms>.png"
      }
    }
  }
}

On success result.ok and result.passed are true, and result.checks holds every check. So for both assert and run_steps, read result.ok for the verdict: run_steps also answers 200 when the batch ran, with result.ok: false plus failed_step and steps[].error. Invalid arguments stay 4xx and genuine server failures stay 5xx.

Contract change in 0.1.2: an assert failure used to be HTTP 500 with {"ok":false,"error":{"code":"E_ASSERTION_FAILED",...}}. Callers that treated the 500 status as "assertion failed" must now read HTTP 200 plus result.ok. This is a 0.1.x contract change, hence the explicit note.

Human handoff

Logins, QR codes and human checks should not be brute-forced by an agent. watch and login make "human takes over once, agent gets control back" a contract: the window appears with showInactive by default (no keyboard focus, no Space switch), has a native title bar and a browser toolbar (back, forward, reload, address bar that accepts only http/https, Enter to navigate, address synced from the page). focus: true is the only way to bring it to the front. Credentials live only in that call's memory: never written to disk, events or responses; password fields read [redacted]. A successful login hides the handoff window again (unless keep_visible: true), and the agent keeps the same session; watch stays visible until it is hidden explicitly, and a timed-out handoff window is not closed automatically. The login state survives only with a profile.

Project layout

src/main/
  index.ts            # startup, exit codes, top-level failure handling
  cli.ts              # flags
  app.ts              # app mode: scene service, Director, autorun, IPC
  app-controller.ts   # state snapshot, scene loading, run, recording, evidence
  ipc.ts              # Director channel and control commands
  control-dispatch.ts # command dispatch shared by Director IPC and the control API
  control/            # loopback HTTP control API (token + SSE)
  stage-layout.ts     # scene size / policy / visit-order math
  demo-protocol.ts    # demo:// scene protocol (registered per session)
  electron-page-driver.ts  # PageDriver on Electron (CDP physical events)
  electron-overlay.ts      # OverlayPort: cursor/highlight/spotlight/annotate/mask/blackout
  malo/               # malo-browser daemon: port pool, leases, broker, sessions, HTTP
  scenes/             # Zod validation, scene loading, interpolation, script hash
  runner/             # state machine, 19 actions, target resolution
  capture/            # screencast frames, 30fps timeline, ffmpeg, evidence
  security/           # allowlist, path safety, permission and download policy
  windows/            # Stage (BaseWindow + WebContentsView), Director
  phase0.ts           # Phase 0 smoke / --mode=record path
src/renderer/director # presenter console (React + lucide)
src/preload/          # Director bridge (sandbox-safe)
src/fixture/          # React fixture: counter, search, risk rows, animation
fixtures/scenes/      # loadable scenes (report-review, login-demo, multi-a, multi-b)
scripts/
  run-phase0.sh      # primary evidence reproduction
  malo-smoke.mjs     # multi-agent / multi-session daemon smoke
  control-smoke.mjs  # control API: load -> record -> evidence
  toolbar-smoke.mjs  # handoff toolbar (hidden StageWindow)
  malo-web-probe.mjs # site compatibility probe (captures + screenshots)
  compare.cjs        # SSIM comparison between two videos

Troubleshooting

  • EBADENGINE warning/failure -> wrong Node line; use a range from engines (>=22.12 on the 22 line, 24.x, or >=26; not 23/25).
  • Exit 0 with no stdout and no files in <out> -> ELECTRON_RUN_AS_NODE is set in that shell (echo $ELECTRON_RUN_AS_NODE), so Electron ran as plain Node and never started the app; re-run as env -u ELECTRON_RUN_AS_NODE npx electron ....
  • Exit 3 E_FFMPEG_MISSING -> install ffmpeg (e.g. brew install ffmpeg) and re-run.
  • Exit 4 / recording failure -> open the take's .evidence.json: it exists even when no video was written (video: null, error.code). E_RECORDER_NO_FRAMES means the screencast delivered no frames; check that the page really rendered and that the window was not destroyed mid-take.
  • EADDRINUSE -> the message names the right surface: --control-port for the control API, --daemon-port for the broker, --port-min/--port-max for the lease pool.
  • Control API 401 -> read the token from <out>/control.json and send it as Authorization: Bearer <token> or x-demo-token: <token>.
  • Director shows a MOCK marker -> the preload bridge is missing; launch via npm start / npm run start:control, not by opening the HTML directly.
  • E_SELECTOR_NOT_FOUND -> no visible element matched; check target syntax. For fill the text is the input content, so the target needs selector / data_demo_id / coordinates (or target_text); for annotate text is the annotation copy.
  • E_ACCOUNT_NOT_FOUND -> account file or account_id missing; check MALO_BROWSER_ACCOUNTS and the ids listed in the error.
  • E_ACCOUNT_INVALID with 0600 or outside a git worktree -> move the profile outside the repo and run chmod 600 <accounts.json>.
  • E_ACCOUNT_INVALID with environment variable -> export the profile's password_env in the shell that launches Electron; never put the password in scene YAML.
  • A window steals focus during recording -> a focusing --window-mode was passed; the defaults (hidden/inactive) never take focus.

Side effects

  • Writes evidence and recordings under --out (default ./evidence): mp4, evidence JSON, overlay check frames, app-mode takes under recordings/, failed step screenshots under errors/<scene>/, and control.json (0600) when the control API is on.
  • Starts ffmpeg/ffprobe child processes while encoding and self-checking; capture itself costs CPU while the take runs.
  • Binds loopback ports only: control API 7317, broker 7410, lease pool 7411-7499 (all configurable). No listener on external interfaces.
  • Opens windows depending on mode: none by default in record / autorun / headless; a visible, unfocused stage with npm start; the handoff window for watch/login. Non-interactive macOS modes switch to the accessory activation policy and hide the Dock icon.
  • The malo daemon keeps running until stopped, writes ~/.malo-browser (broker.json 0600, Chromium profile data, per-session downloads/artifacts), and deletes anonymous session partitions when those sessions close.
  • Login state persists only for named profile sessions, until DELETE /v1/profiles/:name.
  • Permissions, window.open and downloads are denied unless the scene policy allows them.

Limitations

  • Verified on macOS only; other platforms are untested.
  • Recording caps at 1920x1080 and a 30fps timeline; evidence self-checks enforce that.
  • Not a general-purpose browser: no tabs, bookmarks or multi-tab UI; the handoff window only has an address bar plus back/forward/reload.
  • Loopback only: no remote access, no multi-user service, no approval flow or remote deployment.
  • Hidden mode needs focus emulation; with --focus-emulation=false physical clicks are dropped.
  • The agent runtime runs one daemon per data directory, defaults to 4 sessions per lease and reaps idle sessions after 300 seconds; it never streams video continuously.
  • Login handling is auto-detection plus selector overrides, not a per-site adapter layer.
  • Sites can block automated or headless browsers at the network / challenge layer; see docs/malo-web-compat.md. network_capture covers the page target only: OOPIF (cross-process iframe) and Service Worker requests are not captured.
  • 0.1.x contract change: assert failures are HTTP 200 with result.ok: false, not HTTP 500. Callers that keyed on 500 must be updated.

License

MIT (see LICENSE).

  • assert returns HTTP 200 with result.ok: false -> that is the verdict, not a broken service; read result.error.details.checks and the failure screenshot.
  • malo session 401 -> send both the lease token and the session token; a session token from another session is rejected. 404 after a long idle period -> the session was reaped, create a new one.
  • Daemon refuses to start -> another daemon owns that data directory; stop it or pass a different --data-dir.