Skip to content

monomind 2.15.0

Choose a tag to compare

@nokhodian nokhodian released this 21 Sep 15:47
· 847 commits to main since this release

Upgrading — the dashboard now requires a login. Its pages and human-decision
routes no longer serve an unauthenticated request, so opening
http://localhost:4242 directly is no longer enough. Run
monomind dashboard open, which issues a one-time login link (add --print
to emit the link instead of opening a browser, e.g. over SSH). Anything that
scripted the dashboard's HTTP routes needs that login.

Org roles lost write access to decision state. A role can no longer write
gate, approval, question or inbox files. A queued inbox line not written by the
daemon, CLI or dashboard now arrives marked unverified(<sender>) rather than
being trusted.

Added

  • A brain per browser profile. Captures are scoped to the profile they came from, so a work profile and a personal one no longer share one undifferentiated library.
  • CDP over the mono-agent extension bridge. CdpClient owned its WebSocket outright, which made "CDP" and "a socket to a Chrome we launched" the same thing. The transport is now pluggable: the local path is the original code moved wholesale and connect(wsUrl) is unchanged for every caller, while a second transport drives the user's real, logged-in Chrome through the extension bridge. Console capture, network, HAR, vitals, traces, CPU profiles and the AX tree all work against it without porting a single instrument. Documented limits rather than worked around: MV3's chrome.debugger exposes no HeapProfiler domain (heap snapshots do not work over the bridge) and no Browser domain; attaching shows Chrome's debugging banner; DevTools and the debugger are mutually exclusive on a tab.
  • monomind doc lookup <url> — answers "is this already saved, and what was noted about it" on capture identity (canonical URL with the fragment stripped, the same rule ingest dedupes by), returning the note written at save time, the version count and the envelope path. The older substring filter is now reachable as --text on doc list and doc search; it matches any longer URL merely containing the string and carries no note, which is the whole difference between a lookup and a bookmark.
  • monomind dashboard open — see the upgrade note above.
  • Org: an opt-in notice to a task's creator when it completes.

Changed

  • Two files that had outgrown themselves were split, as a standalone commit so it can be reviewed or reverted without touching the features. document-pipeline.ts 1399 → 48 lines (a barrel over 7 focused modules), exported surface verified identical at 22 names before and after, so all 40+ import sites are untouched. monobrowse's cli/commands.ts 4228 → 380 lines over 12 command-group modules. The second could not be split until a hidden coupling was fixed: six module-level let bindings written from 82 call sites. ESM import bindings are read-only for importers, so nothing could move out while that state lived in the module; it now sits behind one object, and that rename alone was verified behaviour-neutral before anything moved.

Fixed

  • A publish with npm instead of pnpm could ship an uninstallable package, and did. @monoes/monodesign@1.2.12 went out with a literal "@monoes/monobrowse": "workspace:*" in its manifest, because npm copies package.json verbatim where pnpm rewrites the protocol. Every consumer install then failed with EUNSUPPORTEDPROTOCOL, and because the releases current at the time resolved monodesign by ^1.2.x range, 2.13.0 and 2.14.0 both became uninstallable — releases that had been fine for days, broken by a sibling publish that touched none of their code. Fixed at the source: scripts/check-workspace-publish.mjs blocks prepublishOnly for any package with workspace: dependencies unless the publish is via pnpm, now wired into monodesign and hooks (the CLI has had this guard since #130 and was never affected). @monoes/monodesign@1.2.13 is published correctly and 1.2.12 is deprecated; 2.13.0 and 2.14.0 install again with no action needed.
  • Dashboard: Human Input drafts survive a list rebuild; a concurrently resolved approval is distinguished from an ended one; a stale approval reports as ended rather than 404.
  • Org: a relative MONOMIND_ORGRT_OPERATOR_DIR is masked from roles.

Security

  • An authority mask for every role outside the SDK sandbox. Push-capable roles and non-Claude CLIs now run under a bubblewrap mask rather than inheriting the operator's authority.
  • Signed inbox entries, and decision gates held in memory for the duration of a run.

Action needed if you use the dashboard. It now requires a logged-in browser. Run
monomind dashboard open (or --print over SSH) to get a one-time login link; the
session then lasts 30 days in that browser. Opening http://localhost:4242 directly
shows how to log in instead of the dashboard.

Changed

  • The dashboard only acts for a browser you logged in yourself. Its pages used to hand the dashboard token to any local program that asked, and that token could approve tool calls, resolve gates, answer questions, message roles and edit org config, so anything running as you, org roles included, could act as you. The pages now need a session cookie, which a browser gets from a one-time, ten-minute login link (monomind dashboard open, or the tab the dashboard opens when it starts). Approvals, gates, answers, chat and config edits are refused without it, even with the token. The token keeps its machine uses (hooks, the CLI, event forwarding), so nothing else changes. The session-start hook that pairs a project with a dashboard already running for another project no longer scrapes the page; it reads the new GET /api/identity (pid, project dir, token file path, never the token).
  • Removed dashboard code for the v1 org model: 22 org tabs that could no longer be shown, and 10 GET /api/org/:name/… routes that only read v1 files nothing writes (projects, members, issues, environments, workspaces, invites, my-issues, secrets, join-requests, goals). No live view used them.

Fixed

  • Approving a tool call for an org that is not running looked like it worked, but did nothing. 2.14.0 recorded the decision in the org's approvals.json, but nothing reads that file back: an approval request lives in the run that asked for it, and a new run asks again. The dashboard now refuses the decision (409) and says why. It does the same when a daemon started since then (for example a --resume in a new process) no longer holds the request, instead of reporting a bare 404. While no daemon hosts the org, its pending approvals show as expired instead of counting as waiting on you. Gates and answers are unchanged, because the next run does read those.
  • Human-in-the-loop decisions and the Runtime tab could reach a same-named org in another project. The daemon registry is machine-wide and keyed by org name, so an approval, answer or chat message for project B's dev went to project A's running dev, and B's Runtime tab showed A's run. A daemon now counts only when it is registered for the same project root.
  • A malformed approval or gate id, or a client that disconnected mid-request, could crash the dashboard process. Those requests now get 400; a body over 64 KB gets 413 instead of a reset connection. A multi-byte character split across network chunks is no longer corrupted.
  • The Runtime tab left out budget exhaustion, agent-fatal, loadout mismatches, session and coordinator crashes, and context-limit restarts. The runtime emits the first three as status events, not audit events, and the rest were not on the list.
  • /health counted a stopped-then-resumed run twice. It now counts each run once, by how it last ended.
  • A chat message the daemon refused now shows the daemon's reason. A network failure when sending chat now says so, and an org event no longer wipes an answer you are halfway through typing in Human Input. One unreadable questions.json no longer hides the same org's approvals and gates. The Config tab refuses a non-string goal instead of saving "null".

Security

  • An org role could redirect the dashboard's file writes to any file the user owns. When the dashboard recorded a decision for an org that was not running, or saved its config, it wrote through a predictable temp file (<file>.<pid>.tmp) in a directory roles can write to. A link planted there redirected the write, with agent-chosen content, to a file such as ~/.bashrc. Temp files are now created exclusively (O_EXCL) with a random name.
  • Values from runtime files an agent can write reached the Runtime tab's HTML unescaped. They came from the run checkpoint, the run history and the settings panel. They are now escaped.
  • An org role could approve its own decision gate. A running org re-read gates.json for every pending-gate check, and that file sits in a directory the org's roles can write to. A role that rewrote it, or swapped the directory holding it, unblocked itself. A running org now holds its gates in memory and writes the file only as a record, restoring it when the run stops.
  • An org role could pose as you in the next run. The org inbox (inbox.jsonl) is delivered when an org starts, as whichever sender each line names, human included. Entries are now signed with a key the roles cannot read. A line that does not verify arrives as unverified(<sender>) with a marked subject. Messages queued by a version before this one also arrive unverified.
  • Roles at policy.git: push, roles with the sandbox off, and every non-Claude runtime could read the operator credentials and write the decision files. Only Claude roles below push ran sandboxed. Every role is now kept from human authority: the operator-credential and dashboard-auth directories are hidden, the dashboard token files are unreadable, and gates.json, approvals.json, questions.json and inbox.jsonl are read-only. Roles outside the SDK sandbox get this from a minimal bubblewrap layer that adds no other restriction. Where bubblewrap cannot run, the role starts anyway and an authority-mask-unavailable audit event says so.
  • A relative MONOMIND_ORGRT_OPERATOR_DIR was not masked from roles. The broker uses a relative value as-is, but the role deny rules only accepted absolute paths. They now resolve it the same way.