Edition Log · One Sentence Per Fix

# Changelog

Every Taprun release, written as if for a typesetter. For full technical notes on each version, see the GitHub releases page.

## v0.16.0 – v0.16.3 May 13, 2026

Extension transport: WebSocket → Native Messaging. The extension no longer reaches the CLI via `ws://127.0.0.1:9333`; it speaks to a Chrome-managed native-messaging host over OS-supervised stdio. No localhost port is opened; the host is launched on demand and dies when the extension disconnects. PoC validated for 19m30s sustained port, 2.1ms graceful EOF, and clean shutdown across three failure modes. First-run flow gains a one-time `tap bridge setup --extension-id <id>` step that writes the native-host manifest into Chrome's profile directory; the popup walks new users through it.

### Changes

- New permission: `nativeMessaging`. Replaces the previous reliance on a localhost WebSocket. No data leaves your machine — see the updated Privacy Policy.
- Popup gains failure-mode CTAs. Distinct messaging for "manifest missing" (run setup), "host crashed" (check `host.log`), and "Chrome blocklist active" (restart Chrome) — instead of one generic "bridge down".
- Service-worker keepalive consolidated. 25-second alarm plus four wake hooks (`onStartup`, `onInstalled`, `tabs.onActivated`, `windows.onFocusChanged`) — Chrome MV3's 30s idle sleep is no longer user-visible.

## v0.15.8 May 9, 2026

Release pipeline is fully hands-off. First end-to-end run with zero human intervention: `git push v0.15.8` → 12 CI jobs → npm + PyPI + GitHub binaries + Homebrew formula + MCP Registry all auto-update. The MCP Registry leg uses GitHub Actions OIDC (no shared secret) with a direct curl POST to bypass a confirmed CLI false-negative bug.

### Fixes

- `release.yml mcp-registry-publish` uses curl directly instead of `mcp-publisher publish`. The CLI v1.7.8 returns HTTP 400 "duplicate version" even when the underlying POST returns 200 — verified against live Registry on 0.15.6 and 0.15.7. Workaround extracts the OIDC-issued JWT from `~/.config/mcp-publisher/token.json` and posts to `/v0/publish` with curl.
- `mcp-publisher` install URL fix. The `install.sh` path on the registry repo doesn't exist; replaced with direct release-tarball download (`mcp-publisher_linux_amd64.tar.gz`, pinned to v1.7.8).

## v0.15.7 May 9, 2026

### Features

- MCP Registry auto-publish lands in `release.yml`. A new `mcp-registry-publish` job authenticates via GitHub OIDC and pushes the canonical `server.json` on every tag push. No more 13-day stalenesses where the Registry pinned `@taprun/cli@0.14.0` while npm shipped 0.15.4. Bumps version pins from canonical `npm/cli/package.json` via `jq`; no generator script, no diff gate, no SHIPPED_* table — just one ~30-line CI job.

## v0.15.6 May 9, 2026

Userspace via standards. ADR-driven: bare `tap` defaults to stdio MCP when stdin is a pipe — MCP host config simplifies to `{"command":"tap"}`, no Tap-specific argv string required. Plan files now self-declare `$schema` URL; loader supports every URL ever shipped, returning typed `kind:"unknown_schema"` for forward-incompatible plans.

### Features

- E1 — argv dissolution. `tap` with empty argv routes by stdin TTY: pipe/redirect → stdio MCP server; TTY → help. All previously-published argv forms (`mcp start`, `mcp stdio`, `serve`, `serve stdio`) continue to work via alias dispatch (Tier 0 userspace loyalty).
- INV-2 — plan `$schema` field. Optional URL on every Plan; `persistence.ts:loadPlan` dispatches by URL, supports every shipped schema. Adding a new schema = appending one reader function. Drift class structurally impossible.
- `tap daemon <sub>` forwards to `tap bridge <sub>`. Previously, `tap daemon status` silently started a daemon (sub-verb argv was dropped). Now grammar and behavior align: bare `tap daemon` still starts (preserves systemd convention); `tap daemon <anything>` mirrors `tap bridge <anything>`.
- `tap --version` works in compiled binary. Old `readVersion()` tried to read `CHANGELOG.md` via `import.meta.url` — the file isn't on disk inside `deno compile`'d binaries. Now JSON-imports from `npm/cli/package.json` at bundle time so the version literal is welded into the binary.

### Fixes

- Homebrew orphan formula deleted. `Formula/tap.rb` at v0.4.0 was stuck since the `tap → taprun` rename in 2026-05-03; auto-bump only touched `taprun.rb`. Anyone running `brew install LeonTing1010/tap/tap` got a years-old binary. Fixed via `oldname "tap"` alias on the canonical `taprun.rb`; existing install commands keep working.

## v0.15.5 May 5, 2026

### Fixes

- Extension cross-origin tab routing. Cross-origin navigations now update the daemon's `lastActiveTab` cache so subsequent ops route to the new tab. Previously the cache lagged behind the SW's view, causing routes to a closed/wrong tab.
- Origin-mismatch nav opens new background tab. When a saved tap navigates to a host different from the active tab, the extension now spawns a background tab instead of clobbering the user's foreground tab.
- Extension self-heal — MV3 SW keep-alive. Service worker stays alive while a long-running op is in flight (chrome.alarms heartbeat); prevents the 30s idle-kill mid-op.

## v0.15.4 May 3, 2026

v2 launch — substrate dissolved, surface vocabulary converged. Engine source contains no type representing substrate state. The 11 ops in `core/types.ts` ARE the protocol. Tool surface collapses 12→4 meta verbs (`capture` / `verify` / `mark` / `run`); saved taps appear as MCP Resources at `tap://{site}/{name}`.

### Features

- Substrate-dissolved engine. No `Substrate` type or interface anywhere in `core/`. `core/dispatch.ts` routes each op to either an inline handler or to a registered peer function; substrate state lives on the other side of a function-call boundary, structurally inaccessible to the engine. Per ADR `2026-05-04-substrate-dissolved.md`.
- 4 meta verbs + N saved-tap Resources. Tool table is constant; per-plan capability is exposed as MCP Resources, not per-plan Tools. Discovery flow: `resources/list` → `resources/read({uri})` → `run({ref, args})`. Per ADR `2026-05-04-saved-taps-as-resources.md`.
- Plan format simplified to bare JSON. No W3C envelope, no `@context`, no `motivation`. Pure data, schema-validated against `core/types.ts`. `op:exec` deleted; `op:eval` is the only escape hatch with mandatory `returns.type`.
- Daemon model. Single-process daemon hosts the in-process queue plus two listeners (`:9333` extension long-poll, `:9334` MCP HTTP). Auto-forks on first call from CLI/stdio; idle-times out at 30 min.
- K(Δ) accounting via Run transitions. Every Run captures a transition log; healing/recovery cost is computed by projecting over `transitions[kind=="heal"]`.

## v0.15.3 May 3, 2026

### Fixes

- Release pipeline 5-bug sweep. v0.15.0 → 0.15.4 retried the binary-release flow until every latent CI bug was caught: bundle entry path, transitive-npm-import externalization, deno.json imports-map propagation, GH cross-repo PAT scope, and a stale `npm_publish_test.ts` reference. v0.15.4 was the first all-green run; v0.15.0–3 are CI-fix patches with no behavior changes.

## v0.14.5–v0.14.7 May 1, 2026

### Fixes

- Capability-strict runtime API. Plans declare `requires`; lint hard-rejects `page-session` without `requires.runtime:"extension"`. `tap.run` pre-flights `matchCapability` before any op. No silent ephemeral-fallback or string-warning machinery.
- Op-grain plan fingerprint Phase 1. `PlanFingerprint.op_hashes` + `diffPlanFingerprints` helper. Heal/doctor consumer wiring deferred to Phase 2.
- No-new-legacy on save. `lintPlan({isNewSave:true})` rejects plans with `legacy:true`; `handleSavePlan` computes the flag from filesystem instead of trusting payload. Drainage terminal state codified.

## v0.14.0–v0.14.4 April 26 – May 1, 2026

MCP Registry submission. Tap goes live in the official Anthropic MCP Registry at `io.github.LeonTing1010/tap`; PulseMCP auto-syncs within 2 days. `op:fetch` gains `credentials: "deno-host" | "page-session"` field for cross-origin auth via the user's logged-in Chrome.

### Features

- MCP Registry listing live. `mcp-publisher publish` from the canonical `server.json`. Empirical test confirmed Registry search is name-only — `name` is the discovery key, `description`/`title`/`websiteUrl` are render-only. v0.14.2/0.14.3 refresh metadata visible in client UIs.
- `op:fetch credentials: "page-session"`. Enables cross-origin requests authenticated by the user's Chrome session, replacing the `op:exec` escape hatch for 4 reddit taps blocked on free-tier auth.

## v0.13.0–v0.13.1 April 20–22, 2026

### Features

- Plan-lint — static author-time checks. Catches schema errors, dangling references, unsafe ops at save time instead of run time. CLI reads `.tap.json` via `findTap` + `doctor`.
- `tap.run` envelope: annotation. Run records carry W3C Annotation envelope for cross-tool interop.

### Fixes

- Sandbox fully retired. The sandbox layer that isolated tap code from the host's Deno APIs is removed; security boundary is now purely the runtime+extension trust model.
- Linux UPX fix. v0.13.0 `tap-linux-x64` was UPX-packed and broke at startup ("Could not find standalone binary section"). v0.13.1 removes UPX from the build pipeline.
- Runtime vision op removed. Violated the zero-AI-at-runtime invariant; vision-related compilation now happens at authoring time only.

## v0.11.3 April 11, 2026

Chrome MV3 "many empty tabs" root fix. Four related bugs, one root cause: MV3 service workers wipe in-memory state every ~30s of idle, but Chrome tabs persist. The sessions Map in the extension was losing track of its own tabs across SW restarts, and every follow-on symptom traced back to that one mismatch. Full post-mortem: Debugging Chrome MV3's "Many Empty Tabs" Bug.

### Fixes

- Session persistence across service-worker restarts. Extension's sessions Map is now written through to `chrome.storage.session` and rehydrated on SW wake. Previously, any session created before an SW idle became an orphan: `session.destroy` silently no-op'd on lookup miss, and the tab leaked forever.
- nav no longer leaks `chrome://newtab/` placeholder tabs. The daemon-path nav handler used to create a replacement tab whenever the current URL was `chrome://`, updating session.tabId but never closing the original. Now uses `chrome.tabs.update` in place.
- MCP main session is lazy. `mcp-stdio.ts` / `mcp-http.ts` used to eagerly `sessionCreate` on the first non-local tool call, wasting a browser tab for pure `tap.run` workflows (which create their own temp session). Main session now materializes only on first use of the interactive API.
- Compiled-binary detection uses `Deno.execPath()`. The prior `Deno.mainModule.startsWith("file://")` check silently regressed in recent Deno — both source and compiled contexts now return `file://`-prefixed main modules, so the Playwright→extension fallback never fired and every compiled binary errored with "Playwright is not available" on the first MCP call.

## v0.11.2 April 11, 2026

### Fixes

- `npm-publish --sync-only` runs sync step only instead of triggering the full build+publish pipeline. Used by `release.sh` step 1 to keep npm package.json versions aligned with `src/cli.ts` VERSION without rebuilding dist/.

## v0.11.1 April 10, 2026

### Fixes

- Daemon pollWaiters drops dead waiters on timeout. Stale waiters accumulated on every client timeout and caused subsequent `notifyPoll` calls to shift resolved no-ops instead of the live extension waiter — every client command was paying the full poll timeout (~20s) before dispatch. Unblocked typical RPC latency back to single-digit milliseconds.

## v0.11.0 April 10, 2026

MCP goes Playwright-first. Zero-friction onboarding: `tap mcp` now defaults to Playwright runtime (no extension, no daemon needed). Users can switch to Chrome mid-session via the new `tap.connect(runtime:'chrome')` tool when they hit an auth wall.

### Features

- `tap.connect` — switch runtime mid-session in MCP. Hit a login wall on a headless run? Call `tap.connect(runtime:'chrome')` and the next tool call operates on your real Chrome browser with login cookies. No server restart.
- `tap doctor --env` — environment health check. One command surfaces daemon status, extension connectivity, Playwright availability, Chrome profile state, and MCP auth — the first place to look when something misbehaves.
- SessionManager refactor — extension owns all tab lifecycle. Sessions are created, routed, and destroyed inside the extension process; Deno side just holds sessionIds. Cleaner boundary, fewer cross-process races.

### Fixes

- MCP detects Playwright availability at startup and falls back to extension runtime if the npm package isn't loadable. Hint messages guide users to either install Playwright or call `tap.connect(runtime:'chrome')`.
- `tap.run` reuses caller's runtime context. Previously, a tap run from a Playwright MCP session would spawn a fresh extension runtime, losing cookies/session. Now `tap.run` honors the caller's runtime when they match.
- Stable daemon token + forge captures page-load API traffic. Daemon restarts no longer invalidate MCP auth; forge.inspect now captures network requests that happen during the initial page load (previously missed the first batch).
- Sandbox allows all `tap.*` RPC methods, blocks Deno APIs. Clarified the sandbox's threat model: it isolates untrusted tap code from the host's filesystem/network/env, not from the browser runtime (which has its own security layer).
- `codesign --deep` for macOS binaries. Nested frameworks in the compiled binary were not being signed, causing Gatekeeper to kill the process on launch.

For releases older than v0.11.0, see the full GitHub releases archive.

← Taprun Blog GitHub Taprun Library