Release date: 2026-05-12
v1.11.1 is a patch release on top of v1.11.0. It contains no new runtime code beyond v1.11.0 — only a comprehensive release note that documents the full cumulative diff from v1.10.4 (the last v1.10.x npm publish) to v1.11.1, plus the version bump.
Treat this release note as the canonical reference for what changed in the v1.11 line. Anyone upgrading from v1.10.x reads this single document and gets the complete picture: every new module, every new MCP surface entry, every behavioral change, and every breaking-change flag they need to verify.
The v1.11 line is the portability-harness contract release. It
establishes a durable core / pilot tier split inside openchrome-mcp
and ships a substantial batch of harness primitives.
npm install -g openchrome-mcp@1.11.1
openchrome check # verify the installed CLI and runtime wiringFresh install:
npm install -g openchrome-mcp@latest
openchrome check
openchrome info # optional: print installed version and runtime detailsNo configuration changes are required to keep your existing v1.10.x
experience. The new pilot tier is opt-in via --pilot:
{
"mcpServers": {
"openchrome": {
"command": "openchrome",
"args": ["serve", "--pilot"]
}
}
}v1.10.4 → v1.11.1 (cumulative, this release)
138 files changed
+24,209 / -210 lines
72 first-parent commits
27 PRs merged (PR #774 → PR #818)
18 new tracking issues filed
4 standalone fixes carried forward (#767 #771 #773 #742)
Tier 1 MCP tool count: 34 → 38
New MCP resources: 0 → 1 (openchrome://skill-graph/<domain>)
New runtime deps: 0
New dev deps: 1 (dependency-cruiser, CI-only)
New native deps: 0 (argon2 remains the only one)
npm overrides added: 2 (basic-ftp, ip-address — security)No source-code-level breaking changes for tool callers. A v1.10.x
~/.claude/.mcp.json configuration works bit-identically with v1.11.1
when --pilot is unset.
The following are the only observable behavioral changes a v1.10.x user can hit. Each one has a documented migration path.
| v1.10.4 | v1.11.1 | |
|---|---|---|
| Default flags | --start-maximized + --window-size=1920,1080 |
--window-position=0,0 + --window-size=1280,900 |
| Window position | implicit (window-manager decided) | 0,0 |
| Window size | 1920×1080 |
1280×900 |
| Maximized? | yes, by default | no, unless requested |
Why: the v1.10.4 combination could place the window partly off-screen on multi-monitor setups, scaled displays, or narrower work areas (issue #770).
Migration if you depended on the old behavior:
# Keep maximize-on-launch
openchrome serve --auto-launch --start-maximized
# Keep 1920×1080 explicitly
openchrome serve --auto-launch --window-size 1920,1080
# Place on a specific spot (new in v1.11)
openchrome serve --auto-launch --window-bounds 1920,0,1280,900Equivalent env vars are honored too: OPENCHROME_WINDOW_SIZE,
OPENCHROME_WINDOW_POSITION, OPENCHROME_WINDOW_BOUNDS,
OPENCHROME_START_MAXIMIZED. Invalid values produce a clear error
message and exit code 2.
v1.11.1 adds four new tools to the default Tier 1 surface:
oc_assert, oc_evidence_bundle, oc_skill_record, oc_skill_recall.
Migration: if your MCP client (or test harness) pins the Tier 1 tool count, bump the expected value from 34 → 38. The four new tools are additive; no existing tool is removed or renamed.
The v1.11 line implements all new storage (trace, skill graph, skill
memory) on JSONL or JSON files + proper-lockfile, not SQLite.
v1.11.1 does not include the user-facing trace recorder hooks
yet — those land in a follow-up — so no existing trace-consumer code
breaks. If you have downstream tooling that reads index.sqlite from
a v1.10.x build, that file path is not produced by v1.11.x.
When --pilot is set, the server writes a one-line marker to
stderr at startup:
[harness] core+pilot enabled (trace,state_graph,contract_runtime,handoff_persist,perception_voting,skill_curator)(Or [harness] core only (--pilot not set) when off.)
Migration: if your MCP client captures stderr and parses it for specific patterns, this line is new. It is informational only — the JSON-RPC protocol on stdout is unaffected (the line goes to stderr explicitly because stdout carries the protocol).
The previously-proposed macOS Keychain + Windows Credential Manager handoff persistence (#721) is not implemented. Per the new portability-harness contract, OS keychain integration is forbidden because it breaks the "anywhere-compatible" portability promise on headless Linux environments without Secret Service.
Migration: if you were waiting on #721, use the new
OPENCHROME_HANDOFF_KEY_FILE opt-in for cross-restart handoff
persistence instead (see below).
These four landed on main before the v1.11 cleanup work. They are
part of v1.11.1 by virtue of sitting between the previous npm publish
(v1.10.4) and this one.
| PR | Title | Effect |
|---|---|---|
| #767 | test: opt-in to allowUnauthenticatedHttp in HTTP transport tests |
Fixed four inherited HTTP-auth test failures on develop without weakening the v1.10.4 fail-closed production policy. The escape hatch (allowUnauthenticatedHttp: true) is the documented test-only path; production code is untouched. |
| #771 | Fix auto-launched Chrome window bounds placement (closes #770) |
New --window-size / --window-position / --window-bounds / --start-maximized CLI flags + matching OPENCHROME_WINDOW_* env vars. Default flipped from --start-maximized + 1920×1080 to 0,0 + 1280×900. See breaking-change #1. |
| #773 | fix: improve javascript_tool diagnostics and Shadow DOM helpers for SPA scraping (closes #772) |
Adds globalThis.__openchrome.querySelectorAllDeep(selector, root?) and globalThis.__pierce(selector) page-context helpers for open shadow roots without monkey-patching document.querySelectorAll. Improved Runtime.evaluate diagnostics when CDP returns a Promise remote. Closed shadow roots remain out of reach. |
| #742 | fix(deps): bump basic-ftp and ip-address via npm overrides (refs #688) |
Top-level npm overrides for basic-ftp@^5.3.1 and ip-address@^10.1.1 to neutralize one critical and one moderate transitive advisory from the @puppeteer/browsers → proxy-agent chain (GHSA-5rq4-664w-9x2c, GHSA-6v7q-wjvx-w8wg, GHSA-rp42-5vxx-qpwr, GHSA-rpmf-866q-6p89, GHSA-v2v4-37r5-5v8g). npm audit --omit=dev now reports zero production vulnerabilities. |
docs/roadmap/portability-harness-contract.md— durable contract; every future PR must satisfy it.docs/roadmap/history/openchrome-1.11-cleanup.md— one-time application plan; archived in this release.
The contract defines five principles:
- P1. Tool server identity — OpenChrome accepts tool calls and returns results; it does not orchestrate, run continuous loops, or manage agent lifecycles. Long-lived background work is permitted only in the pilot tier and only when it serves the tool-call guarantee.
- P2. Zero-impact harness extension — when
--pilotis unset, an optional native dep fails to load, or the per-family flag is off, the v1.10.4 tool surface and behavior are preserved bit-identically. - P3. Anywhere-compatible MCP — neither tier may require outbound LLM API calls (Anthropic, OpenAI, etc.), mandatory API keys at boot, platform-specific compile toolchains, OS keychains, or network-attached secret stores. Server-side LLM-driven decisions live in separate npm packages or host-side libraries.
- P4. Facts versus decisions — the server captures, computes, stores, retrieves, normalizes, and verifies facts. LLM judgment lives outside.
- P5. Native dependency discipline —
argon2is the only mandatory native runtime dep.better-sqlite3is not adopted. Any future native dep ships inoptionalDependencieswith a documented fallback.
src/
├── contracts/ # core contract DSL, evaluators, pHash, evidence helpers
├── core/ # P1–P5 strictly enforced; no flag; backward-compatible
│ ├── trace/
│ ├── skill/
│ ├── skill-memory/
│ └── perception/
├── harness/ # opt-in/flag plumbing and harness runtime helpers
├── tools/ # MCP tool registrations, including new core surfaces
└── pilot/ # opt-in via --pilot; P1/P4 relaxed; P3 still enforced
├── runtime/
├── handoff/
├── voting/
└── curator/Import direction is enforced unidirectionally by a dependency-cruiser rule: src/core/** may not import from src/pilot/**. The check runs on every PR (CI step Lint tier boundary, npm run lint:tier).
openchrome serve # default v1.10.x experience
openchrome serve --pilot # opt into pilot
OPENCHROME_CONTRACT_RUNTIME=0 openchrome serve --pilot # one family off| Pilot family | Sub-flag (env) | Default inside --pilot |
|---|---|---|
| trace recorder | OPENCHROME_TRACE |
active |
| skill state graph | OPENCHROME_STATE_GRAPH |
active |
| contract runtime | OPENCHROME_CONTRACT_RUNTIME |
active |
| handoff token + persistence | OPENCHROME_HANDOFF_PERSIST |
active |
| voting framework | OPENCHROME_PERCEPTION_VOTING |
active |
| skill curator | OPENCHROME_SKILL_CURATOR |
active |
| skill curator structural merge (Pass 2) | OPENCHROME_SKILL_MEM_MERGE |
inactive (off-by-default; costlier) |
| handoff key file (cross-restart persistence) | OPENCHROME_HANDOFF_KEY_FILE=<path> |
unset (ephemeral default) |
When --pilot is unset, no module from src/pilot/** is loaded into the process (verified by a unit test that inspects the import resolver).
src/harness/flags.ts exposes isPilotEnabled(), six per-family getters, activeFamilies(), logActiveFlags(), and bootstrapPilot(). 32 unit tests cover argv/env parsing, cache behavior, family defaults and overrides, and the lazy-import guarantee.
Four new MCP tools and one new MCP resource template register unconditionally. Tier 1 tool count: 34 → 38.
| Subsystem | Module path | New MCP surface | Tests |
|---|---|---|---|
| Trace recorder | src/core/trace/{storage,redactor,types,index}.ts — JSONL append-only storage (no SQLite), credential redactor (covering JWT / AWS keys / Bearer-Basic schemes / hex-32+ / SSN / URL credential params / sibling-form-field key+value pairs), filesystem-scan session index, per-domain proper-lockfile |
(recorder CDP hooks land in a follow-up release) | 49 |
| Skill state graph | src/core/skill/{storage,types,index}.ts — JSON-per-domain storage with full schema (nodes, edges with to_state_distribution), per-domain lockfile coordination, IPv6-safe filename encoding, Windows reserved device-name prefix handling |
openchrome://skill-graph/<domain> MCP resource (read-only) |
33 + 13 |
| Skill memory | src/core/skill-memory/{store,stats-resolver,types,index}.ts — JSON-per-domain skill record store, gzipped frozen snapshots, audit-log-backed SkillStatsResolver (streams ~/.openchrome/audit.log in 64 KB chunks, lazy AuditIndex keyed strictly by skill_id) |
oc_skill_record, oc_skill_recall MCP tools |
28 + 24 + 11 |
| Outcome contracts | src/contracts/{evaluate,eval-context,validator,types,phash,screenshot-class,png-decode,safe-regex,index}.ts + src/contracts/evaluators/{dom-count,dom-text,logical,network,no-dialog,screenshot-class,url}.ts — DCT-II perceptual hash, screenshot-class registry, zero-dependency PNG decoder |
oc_assert (single-call verifiable state check), oc_evidence_bundle (DOM + screenshot + network + console + pHash bundle generator) MCP tools |
15 + 20 |
| Perception primitives | src/core/perception/{metadata,cache,cross-check,image-features,types,index}.ts — perceptual DOM metadata with in-process mtime cache, Sobel edge density, dominant-color (16-bin histogram), sRGB color distance |
(consumed by oc_evidence_bundle and the pilot voting framework) |
31 + 23 |
oc_assert({
contract: <Contract object>,
args?: Record<string, unknown>,
evidence?: { snapshot?: unknown },
}) -> {
verdict: 'pass' | 'fail' | 'inconclusive',
failed_assertions?: Array<{ name, expected, actual, location? }>,
evidence_handle?: string,
}Single call, single return. No retry. No escalation. (Pilot runtime provides those when --pilot is set.)
oc_evidence_bundle({
include?: ('dom' | 'screenshot' | 'network' | 'console' | 'phash')[],
network_window_ms?: number,
evidence?: { snapshot?: unknown },
}) -> { bundle_id, path, size_bytes, parts: string[] }Default include = ['dom', 'screenshot'], network_window_ms = 5000, console cap 200 entries. Output is a flat directory of named parts (no ZIP — keeps the install dependency-free).
oc_skill_record({ domain, name, steps, contract_id, frozen_snapshot? })
-> { skill_id, stored_at, snapshot_path? }
oc_skill_recall({ domain, contract_id?, limit? })
-> { skills: SkillRecord[] }Both are read-only / explicit-write over the JSON skill-memory store. No LLM scoring (the pilot curator does ranking when --pilot is set; core is deterministic last-used-at order).
Read-only JSON snapshot of the per-domain skill graph at request time. In-process mtime cache invalidates when the underlying JSON file changes on disk. URL-encoded domain segment so IPv6 and other unusual hosts round-trip safely.
All pilot modules load lazily through bootstrapPilot() only when --pilot is set. Each family is independently gated by a per-family sub-flag; the default inside pilot is active (except structural merge — off by default since it's costlier).
| Family | Module | Sub-flag | Tests |
|---|---|---|---|
Contract runtime — verdict taxonomy (pass / fail / inconclusive / validation_error / execution_error / budget_exhausted / escalated / aborted_by_hook), exponential-backoff retry, always-settles guarantee, abort handling, async audit emission with .unref()'d timers |
src/pilot/runtime/{runtime,types,index}.ts |
OPENCHROME_CONTRACT_RUNTIME |
64 |
| Idempotency cache + preemptive cancellation — SHA-256-keyed in-process cache with TTL, canonical JSON stringify so object property order doesn't matter, in-flight registry seeded for epoch-based supersession | src/pilot/runtime/idempotency.ts |
(same flag as runtime) | 25 |
beforeIrreversibleAction hook — fires only for critical: true contracts. Operator-registered hook can return { proceed: true }, { proceed: false, reason }, or { proceed: 'await-human', externalToken }. Hook throws are treated as implicit deny. Default registered hook is a no-op pass-through (preserves v1.10.4 behavior). |
src/pilot/runtime/before-irreversible.ts |
(same flag as runtime) | 16 |
Handoff token + manager — base64url-encoded 32-byte tokens (node:crypto.randomBytes), dual-index (bySession, byToken) for O(1) redeem, TTL-based expiry with setInterval+.unref() background prune. |
src/pilot/handoff/{token,banner,manager,index}.ts |
OPENCHROME_HANDOFF_PERSIST |
15 |
Handoff persistence (AES-256-GCM) — ephemeral key by default (regenerated at boot; persisted tokens invalidated on restart). Opt-in cross-restart persistence via OPENCHROME_HANDOFF_KEY_FILE=<path> to a 32-byte key file. Wrong key size on the file: graceful fallback to ephemeral. No OS keychain integration. |
src/pilot/handoff/persistence.ts |
(same flag as token) | 19 |
Multi-model voting framework — voter-agnostic Voter interface, argsEquivalence() action-arg canonicalization (whitespace, key order, semantic-equivalent selectors), vote() orchestrator with bounded concurrency, per-voter deadlines, one retry per voter. Ships with deterministic test voters. No Anthropic/OpenAI HTTP wrappers — those live in the future openchrome-perception-voters package (#775). |
src/pilot/voting/{orchestrator,args-equivalence,index}.ts |
OPENCHROME_PERCEPTION_VOTING |
57 |
Verified skill extractor — turns successful, contract-verified runs into reusable SKILL.md candidates. Idempotent on (graph_node_anchor, contract_id). Atomic writes, per-skill directory lock, sidecar JSON recovery. Prototype-pollution-hardened SKILL.md frontmatter parser. No LLM calls — pure deterministic transform. |
src/pilot/curator/{extractor,skill-md,types,index}.ts |
OPENCHROME_SKILL_CURATOR |
56 |
Skill recall ranking — read-only over the core-tier SkillMemoryStore. Rank by successCount DESC, lastUsedAt DESC, skillId ASC. Byte-cap drop policy (8 KB default) for LLM-friendly payload sizes. Frozen-snapshot memoization. |
src/pilot/curator/recall.ts |
(same flag as curator) | 22 |
Curator Pass 1 (prune) — two sub-passes: confidence-floor demote/archive (fail_rate > 30%, ≥5 runs, double-demote → archive) and TTL archival (stale 30 d, untouched 60 d). Moves to .archive/<skill_id>/ + reason.json. Never deletes; idempotent. |
src/pilot/curator/prune.ts |
(same flag as curator) | (in shared 38-test suite) |
Curator Pass 3 (promote) — async best-effort. Syncs sidecar runs.count and latest success timestamp into SkillMemoryStore.markUsed. Skips archived and store-unknown skills. |
src/pilot/curator/promote.ts |
(same flag as curator) | (in shared suite) |
Curator Pass 2 (structural sibling merge) — detects (domain, intent) pairs whose step lists are structurally near-duplicate after URL normalization, action-arg canonicalization, optional-step pruning. Aggregates success_count, unions runs.recent oldest-first. Structural-only, no LLM. Gated by OPENCHROME_SKILL_MEM_MERGE=1 (separate from main curator flag because merge is costlier). |
src/pilot/curator/merge.ts + src/pilot/curator/stop-words.ts |
OPENCHROME_SKILL_CURATOR + OPENCHROME_SKILL_MEM_MERGE |
27 |
PID-file lock — atomic temp+link write, TTL-based stale reclamation (1 h default), process.kill(pid, 0) liveness probe. Prevents two curators against the same domain even across two openchrome serve processes. |
src/pilot/curator/lock.ts |
(same flag as curator) | (in shared suite) |
Background runner — setInterval with .unref() so it doesn't pin the event loop. Acquires CuratorLock per cycle (skips silently if contested), runs Pass 1 → Pass 3 (Pass 2 if enabled). |
src/pilot/curator/runner.ts |
(same flag as curator) | (in shared suite) |
Two pieces from the original epic plans were redirected to separate packages rather than landing here, because their server-side LLM API calls would violate the "anywhere-compatible" portability promise:
- LLM voting providers (Anthropic + OpenAI HTTP wrappers) — tracked in #775 as the future
openchrome-perception-voterspackage. - LLM merge requester for the skill curator — tracked in #776 as the future
openchrome-skill-curator-llmpackage.
The corresponding Voter and MergeRequester interfaces remain in openchrome-mcp so those separate packages can plug in cleanly when they arrive.
Three boundary-enforcement PRs landed before any tier-routed work:
- #787 —
dependency-cruiserconfig (.dependency-cruiser.cjs) with thecore-must-not-import-pilotrule (severity:error).src/core/index.tsandsrc/pilot/index.tsplaceholders with tier documentation. Newnpm run lint:tierscript. - #788 —
lint:tierwired into.github/workflows/ci.ymlafter the existing eslint step.tests/core/andtests/pilot/scaffolding with READMEs. - #789 —
--pilotCLI flag plumbing onserve;src/harness/flags.tswith 32 unit tests covering all activation scenarios.
better-sqlite3 is not adopted. All storage layers added in the v1.11 line use JSONL or JSON files plus proper-lockfile via the existing src/utils/atomic-file.ts helpers (writeFileAtomicSafe, readFileSafe, acquireLock).
The only mandatory native runtime dependency in openchrome-mcp is argon2. One new devDependency added for CI lint: dependency-cruiser@^16. Runtime dependency list is unchanged from v1.10.4:
argon2 ^0.43.0
commander ^12.0.0
jose ^5.10.0
proper-lockfile ^4.1.2
puppeteer-core (rebrowser-puppeteer-core 23.10.3)
uuid ^9.0.0
write-file-atomic ^5.0.1Top-level npm overrides carried forward from v1.10.4 (PR #742):
"overrides": {
"basic-ftp": "^5.3.1",
"ip-address": "^10.1.1"
}These zero the npm audit --omit=dev production vulnerability count inherited from the @puppeteer/browsers → proxy-agent chain.
The v1.10.4 hardening continues unchanged in v1.11.1:
- HTTP transport fail-closed policy (unauthenticated boots rejected unless
allowUnauthenticatedHttp: trueis explicitly set) - Dashboard REST authz with tenant ownership checks
- HTTP JSON-RPC batch concurrency bound + RequestQueue timeout clearing
- File upload root restriction (no arbitrary local paths)
- Screenshot + vision capture payload guards
- Same-origin CORS bypass scheme matching + DNS-rebinding defense
New in the v1.11 line:
- Handoff persistence uses AES-256-GCM with random 12-byte IV per record and 16-byte auth tag. Token filenames are SHA-256 hashed so original tokens never leak via the filesystem.
- Audit logging is unchanged;
SkillStatsResolverwalks the audit log in 64 KB chunks with aStringDecoder-backed iterator and builds a lazyAuditIndexkeyed strictly byskill_id(prevents sibling contamination).
The v1.11 cycle burned through a 35-PR backlog accumulated against four in-flight epics:
- 35 originally-open PRs all reached terminal state.
- 27 new PRs merged to
developandmainduring the cleanup (PR #774 through PR #817 plus this release PR #818). - 18 new tracking issues filed. 14 closed by their replacement PRs; 4 remain open for follow-on work (#775 perception voters package, #776 skill curator LLM package, #778 native-deps.md trigger deferred, #779 stale
pr/*branch cleanup chore). - Three SQLite-backed PRs were rewritten as JSONL/JSON +
proper-lockfileper principle P5. - Two PRs were redirected to separate-package extraction (#760 → #775, #765 → #776).
- Zero abandoned work: every closed PR's branch is preserved on the remote (chore #779 will clean up post-release).
Detailed per-PR execution log lives at docs/roadmap/history/openchrome-1.11-cleanup.md. The durable contract remains at docs/roadmap/portability-harness-contract.md.
npm run build: clean (bothtsconfig.cli.jsonandtsconfig.json)npm run lint: cleannpm run lint:tier: 0 violations (~310 modules, ~625 dependencies cruised)- Full Jest suite: passes on
main - CI matrix on the release-merge PR: 18/18 green (9 OS×Node combos × 2 base targets)
- Manual:
openchrome serve(no--pilot) returns the v1.10.4 surface plus the 4 new core tools;openchrome serve --pilotregisters pilot tools and prints[harness] core+pilot enabledline on stderr
- Contract:
docs/roadmap/portability-harness-contract.md(PR #774) - Application plan (archived):
docs/roadmap/history/openchrome-1.11-cleanup.md - v1.11.0 release page: https://github.com/shaun0927/openchrome/releases/tag/v1.11.0
- Restructure-proposal hybrid resolution: comment thread on issue #768
- Meta tracker (closed): #780
- Release plan (closed): #786