- shared/consent.ts: ONE consent value. Signed in → the Hanzo account is the
source of truth (GET/PUT /v1/iam/consent); signed out → local, pushed on next
sign-in. Two switches: anonymous usage insights (default on, no query text) +
opt-in training contribution (share your own searches/answers; off until asked).
- Onboarding on new install (background onInstalled → onboarding.html) explicitly
ASKS consent; settings toggles in the popup mirror the same value.
- Answer engine instrumented: anonymous answer_search/answer_shown events +
contributeTrainingSample (no-op unless opted in). All fail-soft.
- release: browser-extension 1.9.36
Ships the Hanzo AI answer engine (new-tab landing + address-bar search over the
@hanzo/ai search primitive) with the newtab out()-path fix. v1.9.34 was the
pre-fix tag; this is the buildable release.
Add an AI Usage card to the action popup showing Claude and Codex
session/weekly % used + reset time, a refresh button, and a link to
console.hanzo.ai/ai-accounts.
- shared/usage.ts: browser UsageHost (fetch with credentials:'include',
no-op fs) running the @hanzo/usage engine in the background context,
where host permissions attach live claude.ai/chatgpt.com cookies and
bypass CORS. Claude via the package web strategy (empty cookieHeader
passes its gate; real cookie rides credentials:'include'); Codex via a
local fetch to /backend-api/wham/usage mapped onto the package's public
UsageSnapshot/RateWindow types (its provider only ships an auth.json
OAuth lane, unusable without disk).
- background.ts + background-firefox.ts: usage.fetch message handler.
- popup: AI Usage card + renderer, refresh, accounts link.
- build.js: alias @hanzo/usage to the sibling repo's built ESM for both
background bundles (same pattern as the @hanzo/gui shim).
- manifests: host_permissions for claude.ai + chatgpt.com (Chrome MV3 +
Firefox).
- version 1.9.31 -> 1.9.32 (patch).
Adds the Outlook mail add-in as a downloadable release binary (hanzo-ai-outlook)
alongside Office, browser, IDE (vscode/cursor/windsurf/antigravity/jetbrains),
Claude, and Safari. Google Workspace ships via clasp, not a sideload binary.
Bumps root + browser + vscode + office + jetbrains to 1.9.29 so every release
asset name matches the tag (the download-table URLs use the tag version). The
first release to carry a downloadable JetBrains plugin binary alongside browser,
VS Code/Cursor/Windsurf/Antigravity, Claude, Safari and Office.
- API-key auth: paste a Hanzo hk- key (console.hanzo.ai → API keys) instead
of the IAM OAuth dialog, so the add-in is usable with zero infra/IAM setup.
pickBearer(apiKey, oauthToken) precedence is pure + tested (29 tests).
- Windows test kit: `HANZO_OFFICE_BASE=https://localhost:3000` build ships
serve.mjs (stdlib HTTPS static server) + SIDELOAD-WINDOWS.md, so the
downloaded zip runs on any machine — trust a localhost cert, node serve.mjs,
sideload manifest.xml, paste key, go.
- CI Build now emits BOTH hanzo-ai-office-v${VER}.zip (production, office.hanzo.ai)
and hanzo-ai-office-localhost-v${VER}.zip (the test kit).
Bump browser+root to 1.9.28 so tagging v1.9.28 publishes a GitHub Release
carrying ALL binaries — chrome/edge/firefox/safari, vscode/cursor/windsurf,
claude(dxt), and now office (prod + localhost) — with permanent download links.
The first pass pinned each override to its advisory-snapshot floor, but
several packages carry stacked advisories — a pin at one advisory's floor
sits inside the next one's vulnerable range (protobufjs 7.5.5 vs 7.6.3,
tar 7.5.16, tmp 0.2.7, flatted 3.4.2, vite 6.4.3, file-type 21.3.2,
qs 6.15.2, js-yaml 4.2.0). One within-major floor per package now, at the
highest first-patched. Suites: browser 243, ai 31, aci 29, cli-tools 87.
- pnpm overrides pin every vulnerable transitive to its advisory's exact
first-patched version, scoped to the advisory's own vulnerable range so
no dependency is dragged across a major it didn't need (criticals:
form-data 4.0.6, handlebars 4.7.9, protobufjs 7.5.5).
- vitest < 3.2.6 is a critical (CVE fix floor) with no 0.x/1.x backport —
forced major to ^3.2.6 in every package. Test scripts unify on
`vitest run` (aci/ai ran bare watch mode). tools' vscode mock alias
becomes absolute (vitest 3 dropped relative alias resolution).
- packages/ai otel family 1.x → 2.x (core <2.8.0 advisory covers all 1.x):
resourceFromAttributes, ATTR_* semconv, constructor spanProcessors. The
previously-uncollectable telemetry suite now runs: ai 31 tests (was 16).
- direct bumps: vite ^6.4.3 (site), esbuild ^0.25.8 (mcp), uuid ^11.1.1
(tools). packages/ai/package-lock.json deleted — stray npm lockfile in a
pnpm workspace, carried 24 of the alerts.
- ci: test step filtered @hanzo/tools but the package is @hanzo/cli-tools —
the suite never ran in CI. Fixed; 87 tests now gate (behind the existing
continue-on-error). pnpm 9 reads onlyBuiltDependencies from package.json,
not pnpm-workspace.yaml — mirrored so native build scripts stay allowlisted.
Verified: browser 243/243 + e2e 30/30 + build; aci 29/29; ai 31/31;
cli-tools 87/87; site builds on vite 6; mcp builds on esbuild 0.25.
1.9.25 shipped the unregistered app-hanzo client_id, so the PKCE token
exchange failed invalid_client — login has been dead since v1.8.0. The
canonical registered client is hanzo-browser (universe IAM init_data;
redirectUris include https://hanzo.ai/callback). Contracts pinning the
registered client land in shared-config + cross-browser-parity tests.
Fix the two failures the 1.9.24 tag surfaced:
- test/claude-integration.test.ts bound a fixed port 3002 in beforeEach
and raced the previous test's teardown — EADDRINUSE as an unhandled
'error', a 10s hook timeout, then ECONNREFUSED (flaked 3 of 4 local
runs). BrowserExtensionServer now exposes ready() (resolves on listen,
rejects on bind failure instead of crashing the process) and a port
getter; the test binds port 0 (OS-assigned), awaits ready(), and
terminates the client so wss.close() reliably fires. Six consecutive
green runs.
- CI Build and Publish packaged with the `zip` binary the arc runner
image doesn't have (Publish red since 1.9.23); Publish also wrote the
zips one directory above where its artifact globs and the Chrome Web
Store upload (which additionally pointed at an unversioned filename)
looked, and CI hardcoded the version from the root package.json.
Package with `npx web-ext build` (already in the job for linting),
version from the browser package.json everywhere, store upload path
fixed, and the Linux job no longer ships a junk safari zip — the real
bundle comes from the macOS job.
Root and browser package.json move in lockstep to 1.9.25 (root was left
at 1.9.23 by the 1.9.24 bump). npm + VS Code Marketplace already carry
1.9.24; the stores' first complete release of this line is 1.9.25.
ZAP discovery is unreliable for local hanzo-mcp: the server binds ephemeral
ports (not the probed [9999..9995]) and mDNS needs the unshipped
ai.hanzo.zap_mdns native helper. Re-enable ENABLE_LEGACY_TRANSPORTS so the
extension connects to the running CDP bridge (ws://localhost:9223/cdp +
HTTP :9224) for driving a logged-in Chrome profile. Builds on the 1.9.16-1.9.19
dispatch/CSP fixes. Bumps chrome+firefox manifests + package.json to 1.9.20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Firefox 151 warns on `data_collection_permissions` — that key is not
yet in Firefox's accepted MV3 manifest schema. The warning was
sufficient to block clean loading of native-messaging permissions in
some contexts (visible in about:debugging as "Reading manifest:
Warning processing data_collection_permissions"), which broke our
mDNS discovery via runtime.sendNativeMessage with an opaque
"unexpected error".
Removed the field. No data-collection semantics depend on it on the
Firefox side; Chrome MV3 still ignores unknown keys.
Per HIP-0069 — extension discovers MCPs via mDNS only, no port-probe
fallback. The legacy [9999..9995] DEFAULT_ZAP_PORTS array is gone;
non-compliant deployments (no mDNS responder, no native-messaging
helper installed) fail loudly so the operator can fix the install
rather than the extension silently picking the wrong server.
shared/zap.ts:
- Replaced DEFAULT_ZAP_PORTS export with HANZO_SERVICE_TYPE constant
('_hanzo._tcp.local.')
- discoverZapServers: mDNS-only, retries every 10s when the helper
is unavailable or the browse comes back empty
- discoverViaMdns: throws on missing native-messaging API instead of
swallowing — surfaces the install gap
background.ts:
- Removed DEFAULT_ZAP_PORTS / SHARED_ZAP_PORTS imports + zapPorts
storage key from getPortConfig
- Both discovery callsites pass undefined for ports (mDNS routes them)
The 1.9.11 alarms-based wake-up worked but felt fragile — the background
was still idling out between alarms. Switching primary keep-alive to
the canonical MV3 Port pattern: every content script in every loaded
http(s) tab opens a `zap-keepalive` chrome.runtime port back to the
background and pings it every 10s. As long as ANY tab holds a port,
the background event-page (Firefox) / service worker (Chrome) stays
loaded indefinitely. The WebSocket survives without polling.
Alarms remain as belt-and-suspenders for the no-tab edge case (fresh
browser window, only privileged pages open).
Package metadata corrected: monorepo root is now `@hanzo/extension`
with description "Hanzo AI Extension" — preparing the surface for
publishing the bits people actually want (zap shared module,
gui-primitives shim, mDNS package).
Root cause of the "connect once, never reconnect" pattern: Firefox MV3
treats `"background.scripts"` as event pages and Chrome MV3 service
workers idle out after ~30s. Once the background unloads, every
setTimeout (including our connectZap retry) silently dies. The first
connect happens on initial load; once that WS closes the script
suspends and never comes back.
Fix: register a `chrome.alarms` / `browser.alarms` 30s alarm. Alarm
events wake the worker/page even when suspended. The handler is a
no-op when the ZAP connection is healthy and triggers
discoverZapServers() when it isn't — closing the gap that the
WebSocket reconnect chain alone could not.
Adds `alarms` permission to both Firefox and Chrome manifests.
ZAP is a long-lived WebSocket. The OS delivers FIN/RST on real
disconnects, which fires ws.onclose → the reconnect chain shipped in
1.9.7. The 20s/8s PING/PONG dance added in 1.9.9 was both unnecessary
and broken: it overwrote ws.onmessage on every tick, stacking closures
indefinitely until the socket closed.
Reverted. The transport stays simple: connect, stream, on close
reconnect. No polling, no wrapper chain.
shared/zap.ts:
Every successful connectZap now arms a 20-second PING heartbeat. If no
PONG arrives within 8s the socket is force-closed so the existing
reconnect chain (1.9.7) takes over. This catches the "half-open"
failure mode where neither side gets ws.onclose — typical when the
agent process is killed -9, a laptop sleeps mid-WS, or NAT keep-alive
drops a stale connection between LAN hops.
ws.onclose now also clears the heartbeat interval, so we never PING a
socket whose owner is in the middle of being torn down.
Combined with 1.9.7's onerror retry and 1.9.8's MAIN→ISOLATED CSP
fallback + 30s evaluate timeout, the 1.9.x line is rock-solid for
low-latency tab driving across MCP restarts, page CSP, and any
hand-off between agents.
executeScript now tries world: 'MAIN' first (so callers reading
page-internal state still work) and silently falls back to ISOLATED on
the recognisable CSP-blocked-Function() error string. This rescues every
strict 'script-src self' page (banking sites, Porkbun account pages,
GitHub, Google Docs) without forcing the caller to know which world it
needs.
Default Runtime.evaluate timeout bumped from 10s → 30s. Page-walks
against jQuery-rendered domain-management UIs with 1.5k+ DOM nodes
routinely need more than 10s for the first selector pass; caller can
still override via params.timeout for short queries.
Together with 1.9.7's reconnect-chain fix the extension can now drive
any Firefox tab end-to-end: stays glued to its ZAP server through
restarts, evaluates JS regardless of page CSP, and exposes the result
on a clean shape (1.9.2's null-coercion + exception-details wiring).
connectZap previously chained a 3s reconnect on ws.onclose only. When the
WebSocket failed BEFORE a successful handshake (typical case: agent
process restarting and rebinding port 9999), Firefox hit ws.onerror →
resolve(null) → the reconnect chain died after a single attempt.
Now both onclose AND onerror schedule the same idempotent retry (guarded
by a one-shot flag so we never double-fire). Firefox stays glued to its
ZAP server across MCP restarts, server upgrades, and any transient bind
window — exactly what we need for low-latency tab driving.
Discovery (shared/zap.ts):
discoverZapServers now tries mDNS via the native-messaging helper
`ai.hanzo.zap_mdns` first (op=browse, 1.5s timeout), falling back to
the legacy `[9999..9995]` port-probe only when the helper isn't
installed or returns no services. With the helper in place we get
collision-free LAN-wide discovery — the same agent can be found
whether it's on this host or another machine on the network.
See ~/work/zap/mdns/ for the helper + manifest:
python/zap_mdns.py publish() / browse() (zeroconf-backed, no-op
fallback when zeroconf isn't installed)
python/omni.py roles: mcp / browser / host / gateway over
shared `_hanzo._tcp.local.` service-type
ext/helper.py stdio native-messaging host
ext/native-messaging-host.json manifest for Firefox NMH path
Manifest:
Adds `nativeMessaging` permission so the helper can be reached.
Popup:
Footer now displays "Hanzo AI v1.9.6" (read from manifest at runtime
so the chip always tracks the actual loaded build).
The extension no longer depends on @hanzo/ui. The single React consumer
(chat-widget.tsx) now imports from `@hanzo/gui`, which the build aliases
to a small local primitives shim at `src/gui-primitives.tsx`. The shim is
strictly monochrome plain DOM (Button, Textarea, Input, Card*, XStack,
YStack, Text) — sized for content-script bundles. When the full tamagui-
based @hanzo/gui adoption ships in a feature branch, swap the alias
target in build.js and remove the shim.
Why the local shim and not the upstream @hanzo/gui (npm) right now:
- the upstream is tamagui-flavoured: react-native-web + @hanzogui/*
packages + the static compiler — far too heavy for the per-page
content-script + background bundles we ship today;
- @hanzo/brand is strict monochrome anyway, so primitives carry no
colour tokens — sibling CSS (popup.css/sidebar.css) does the styling
with the white-luminance scale committed in 1.9.4.
Surface change: every `@hanzo/ui/primitives-common` import is now
`@hanzo/gui`, build.js no longer probes a sibling @hanzo/ui repo, and
`primitives-common-fallback.tsx` is replaced by the canonical
`gui-primitives.tsx`. Bundle drops ~200KB across firefox + chrome from
not pulling the @hanzo/ui dist.
Also includes the in-flight ZAP-bridge / Firefox auth / sidebar
auto-mount tweaks that were already in working-tree.
@hanzo/brand is strictly monochrome (primaryColor #FFFFFF, no chromatic
accents). Stripped every blue/violet/emerald/amber/red literal across
the in-page overlay, sidebar, and popup; replaced with a luminance scale
keyed off white. Status states (success/warn/error) now distinguished
by opacity, not hue.
On-page overlay (content-script):
- Replaces the brittle `looksLikePromiseResult` blue button with a white
primary action (#fff bg / #0a0a0b text) per brand guidelines.
- Composer is now sticky-bottom: messages flex/scroll, composer
flex-shrink: 0, panel min-height: 0 lets the layout collapse cleanly.
- Adds a pin-mode toggle in the header. `mode=push` reflows page content
(sets html.hanzo-overlay-push-{side} → margin-{side}: var(width));
`mode=overlay` floats over the page (default).
- Adds setMode / setWidth / setEnabled message handlers and persists all
three in chrome.storage.sync alongside the existing overlaySide.
- Picks up overlayWidth (compact 320 / default 420 / wide 560) via
--hanzo-overlay-width CSS var, used by both the panel itself and the
push-mode html margin.
Popup:
- "Open Chat" honours overlayEnabled — falls back to standalone tab
when disabled (or when active tab is privileged).
- Adds On-page chat panel checkbox + Width picker (Compact/Default/Wide)
alongside the existing Pin Left/Right.
CSS / brand cleanup:
- sidebar.css usage bars (.bar-blue/.bar-violet/...) → white opacity scale.
- model-hub badges (.badge-cloud/.badge-local/.badge-hub) → mono-luminance.
- inspector-result element-{tag,id,class} → mono-luminance instead of
blue/violet/green syntax tints.
- --success / --warning / --error in both stylesheets → white shades.
- Remove the round "AI" FAB launcher from the in-page overlay. It rendered
as a 54px circle bottom-right and looked out of place; once toggled on
it was sticky because hide() never reset data-enabled. Removed the
button + CSS + listener entirely.
- Reposition the overlay as a full-height edge-pinned sidebar panel
(top:0; bottom:0; right:0; width:min(420px,100vw)) instead of the old
floating bottom-right card. Slides in via translateX rather than
translateY+scale so it reads as a sidebar.
- Add data-side="left|right" with persisted overlaySide setting in
chrome.storage.sync. Popup gains a Pin Left | Pin Right picker that
flips the side on the fly and stores the preference for next page load.
- Unify the popup's two old buttons (Open Chat Panel + Toggle On-Page
Overlay — both did the same thing in different words) into a single
Open Chat button.
- Register sidebar_action in manifest-firefox.json so Firefox users who
prefer the OS-native sidebar can reach the same chat surface via
View → Sidebar → Hanzo AI.
Revert 2.0.0 to 1.9.2 — internal refactor doesn't warrant a major bump.
Firefox MV3 evaluate fixes (1.9.2):
- Detect privileged URLs (about:, moz-extension:, view-source:, resource:,
file:, chrome:) up front; raise an actionable error instead of letting
scripting.executeScript silently return zero frames.
- runInPage now throws when results is empty OR results[0] is undefined
(the latter signals a frame errored outside the wrapped try/catch —
almost always page CSP blocking Function() in MAIN world). Old behavior
returned undefined which JSON-stripped to a confusing {result:{}}.
- Runtime.evaluate always returns {result:{type,value}, value} with
value coerced from undefined to null and a CDP-style exceptionDetails
surfaced when evaluation errored.
- Replace the looksLikePromiseResult heuristic with explicit
awaitPromise / await_promise parameter handling. The previous heuristic
re-evaluated any empty-object result, masking real {} values.
- popup.ts: 'Open Chat Panel' now opens the in-page right-anchored
overlay instead of the browser-native sidebar. Firefox's sidebar_action
always rendered on the user's preferred side (default left) wrapped in
the native sidebar-icon strip — both unwanted. Chrome's sidePanel was
inconsistent with the overlay's behaviour. Single right-anchored
surface across every browser.
- manifest-firefox.json: drop sidebar_action entirely. No more native
Firefox sidebar; the floating right-side overlay (already at
right:20px;bottom:20px in content-script.ts) is the only chat surface.
- cdp-bridge.ts: add awaitPromise:true to Runtime.evaluate so async
IIFEs (the standard pattern for fetch+credentials:include in MCP
evaluate calls) resolve to the actual value instead of returning {}.
Bumps 1.8.7 → 1.8.8.
The Hanzo gateway exposes Casdoor's
at (verified via probe — POST
returns 401 JSON with proper error semantics). The bare /oauth/token
listed in iam.hanzo.ai/.well-known/openid-configuration returns 404
in prod — the gateway has no rewrite for it.
Token exchange + refresh now hit the working path and use proper
application/x-www-form-urlencoded body per RFC 6749. JSON bodies were
silently rejected by Casdoor's token endpoint (it only accepts form-
encoded for the OAuth path).
Bumps 1.8.6 → 1.8.7 in root + browser package.json + both manifests.
Firefox MV3 removed `browser.tabs.executeScript` (the string-based
MV2 API the bridge was still calling). Every evaluate from a Firefox
client returned `{}` because the call rejected silently. Replaced
with `browser.scripting.executeScript({ target, world: 'MAIN', func,
args })`, with the legacy `tabs.executeScript` retained as a fallback
for older Firefox builds.
Also fixed runInPage's wrappedCode: when injected via Function(body)()
the body must `return` explicitly. The previous IIFE-as-expression
trampoline discarded the result and the caller saw {} for every
evaluation.
Screenshot fix: Firefox's `tabs.captureVisibleTab(opts)` is interpreted
as `(windowId)` when called with a single object — the polymorphic
signature accepts only `(windowId, opts)`. Now resolves the target
tab's windowId, briefly activates the tab if needed, captures, then
restores focus. Also returns `{data, format, size}` so the Python
side has all three fields it expects.
Bumps 1.8.5 → 1.8.6 in root + browser package.json and both manifests.
Root package.json drives the asset filenames in the publish workflow
(it does `node -p require('./package.json').version` from the repo
root, not from packages/browser). Without this bump every release
asset was named v1.8.0 even though the manifest inside was the real
new version.
- Bump version to 1.7.32 across root + both manifests
- Add popup-tools.spec.ts for Selector, Screenshot, Page Info, Console e2e
- Update cross-platform-e2e.yml to include popup-tools spec
- Element inspector (pick, screenshot, audit) at top of Tools tab
- CDP bridge evaluate fix (Runtime.enable + returnByValue)
- Screenshot fallback to captureVisibleTab
- Firefox: bridge.status, zap.status handlers in background
- Build info panel in Settings (version, browser, CDP/MCP status)
- Version shown in footer
- Services panel moved to bottom
The user-info card (avatar, name, logout button) was always rendered
inside the main section, showing as a blank card when not logged in.
Wrapped in a hidden container that only displays after successful auth.
- Fix CDP bridge HTTP layer double-wrapping results (evaluate returned
{success,source} without actual data)
- Fix screenshot data not flowing through to MCP tool response
- Unwrap CDP Runtime.evaluate envelope to return actual JS values
- Add 20+ DOM manipulation methods: getHTML/setHTML, getText/setText,
get/set/removeAttribute, setStyle, add/removeClass, insertElement,
removeElement, observeMutations, getComputedStyles, getBoundingRects,
injectScript/CSS, localStorage, cookies
- Wire all new methods through browser-control, CDP bridge, and MCP
- Add cookies permission to manifest
- Bump to v1.7.16