325 Commits
Author SHA1 Message Date
hanzo-dev 8051256382 test(vscode): gate the zsh shell-tool test on /bin/zsh existing — its whole subject is delegation to zsh, absent on CI runners (the one remaining hard-gate failure: 151/152) 2026-07-03 02:16:05 -07:00
hanzo-dev 3e380bcec3 ci: provision electron runtime libs for the extension-host tests — VS Code downloaded but could not load libnspr4.so on the minimal arc node 2026-07-03 02:12:29 -07:00
hanzo-dev 39fc502513 ci: provision xvfb before the vscode extension-host tests — the arc pool is heterogeneous and not every node ships it (Test job hit exit 127 while the E2E job's node has it) 2026-07-03 02:09:01 -07:00
hanzo-dev ce2f186bd2 vscode: resurrect the whole test gate — 0 lint errors, 152 mocha + 3 vitest passing, hard CI gates
The package's `npm test` has been broken-by-construction for years:
tsconfig excluded src/test entirely (out/test/runTest.js never existed),
tsconfig.test.json inherited the exclude that negated its include, the
runner pointed at a ./suite/index path that never existed, two suites used
BDD describe/it under the tdd-configured mocha, a vitest file sat in the
mocha glob, `ws` was never declared (it resolved only cross-package), and
154 eslint errors blocked pretest.

Lint 154 → 0 with real fixes: the ban-types autofix damage reverted
properly (the local `Symbol` interface shadowing the ES built-in — the
actual footgun — is now `CodeSymbol` everywhere), case bodies braced via
AST codemod, empty catches annotated, `Function` types replaced with real
signatures, hasOwnProperty via Object.prototype, and
@typescript-eslint/no-var-requires off — CJS lazy requires are the
intentional activation-perf idiom (the rule is retired to stylistic in
typescript-eslint v8).

Harness: tests compile via tsconfig.test.json in pretest; runner path
fixed; BDD→TDD; glob v10 promise API; esModuleInterop default imports.
Two harnesses, one directory each: vitest owns src/test/vitest/ (scoped
vitest.config, excluded from the mocha compile), mocha owns the rest;
posttest runs the vitest side so `npm test` is the whole gate.

Suites modernized against current tool contracts (34 passing / 64 failing
→ 152 passing / 0 failing / 12 pending): web-fetch rewritten against the
real http(s) path via loopback server, mode against list/activate/show/
current, mcp-tools against config-driven getAllTools filtering, extension
pins the shipped manifest contract, shell/process/git-search/filesystem/
config/bash/mcp-runner exercise real commands, files, repos and spawns.
Dead-subject suites removed; network-dependent integration tests gated on
HANZO_E2E_LIVE with visible skip reasons (12 pending). Tests-only — the
one suspected product bug was an unfaithful Memento mock, and a real
cross-suite settings leak was fixed by making suites hermetic.

CI: cli-tools and vscode test steps lose continue-on-error and become
hard gates (vscode under xvfb-run, same as the E2E job).
2026-07-03 02:05:42 -07:00
hanzo-dev 7c742c07f4 deps: re-pin stacked-advisory packages to their highest patched floor
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.
2026-07-03 00:56:30 -07:00
hanzo-dev 4eb2cf57a8 release: browser-extension 1.9.27 — rebuilt on patched dependency tree
1.9.26 bundles carried the pre-override axios/ws resolutions; this rebuild
ships the patched tree from the dependabot-closure pass.
v1.9.27
2026-07-03 00:40:45 -07:00
hanzo-dev 3ab5ae053c deps: close all 175 dependabot alerts — range-exact overrides, vitest 3, otel 2
- 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.
2026-07-03 00:40:07 -07:00
hanzo-dev fe3c0eabdc release: browser-extension 1.9.26 — working login (hanzo-browser IAM client)
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.
v1.9.26
2026-07-03 00:00:14 -07:00
hanzo-dev e23fbe51f7 dxt: kill dead app-hanzo password-grant, canonical OIDC PKCE only (from PR #6)
Adopted from fix/dxt-login-and-ci-guards: the DXT auth tool offered a
password-grant login defaulting to the unregistered app-hanzo client —
removed; whoami/token/account read /v1/iam/oauth/userinfo, refresh
exchanges at /v1/iam/oauth/token, issuance is authorization-code+PKCE
only. dxt build script now actually builds (mcp cli → server.js) instead
of echoing success. PR #6's test-suite restorations are not taken — main
already restored those suites.
2026-07-02 23:59:10 -07:00
Hanzo AIandhanzo-dev 42bf555a4e fix(extension): login client_id app-hanzo→hanzo-browser (dead client since v1.8.0) — v1.9.25 2026-07-02 23:58:30 -07:00
hanzo-dev 8f8f8b5ae2 llm.hanzo.ai is dead — api.hanzo.ai is the one gateway; harden live HF tests
Migrate the last llm.hanzo.ai references (vscode MCP llm tool provider URL
+ gateway fallback, auth-chat e2e doc comments) to the canonical
https://api.hanzo.ai/v1 (OpenAI-compatible, IAM JWT at the gateway — see
hanzoai/ai). The browser extension chat paths already pointed at
API_BASE_URL = api.hanzo.ai.

model-hub's HuggingFace tests are real live-API integration tests, which
means they fail on HF's weather: repeated runs rate-limit and the 5s
default timeout reads as 6 test failures with nothing of ours broken.
Probe HF once at module load and describe.skipIf with the status logged
when HF itself refuses service; give live calls a 15s budget. A failure
while HF answers 200 is still a real failure. 243/243 across repeated runs.
2026-07-02 23:53:04 -07:00
hanzo-dev c783b89c35 e2e: repair popup specs stranded by the 1.9.3 UI and the tab-hosted-popup harness
Three specs still tested the pre-1.9.3 popup (#open-page-overlay FAB,
'Open Chat Panel' label) — the Extension E2E jobs have been red on them
since the FAB was killed. Repointed at the current contract
(#overlay-enabled + #open-panel, 'Open Chat').

The interaction specs (overlay show, picker tool, console tool) all
funnel through the popup's active-tab lookup, which cannot work when the
harness hosts the popup as a tab: the popup IS the active tab, so the
privileged-tab guard always takes the fallback. Each spec now sends the
exact message its button sends (page.overlay.show / tool.picker.start /
tool.console.start), explicitly targeted at the example.com tab, so the
background relay and content-script surfaces are still exercised end to
end. CI's file set (popup, popup-tools, sidebar): 30/30 locally.
2026-07-02 23:04:29 -07:00
hanzo-dev b3f718dfce release: browser-extension 1.9.25 — green pipeline end to end
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.
v1.9.25
2026-07-02 21:52:58 -07:00
hanzo-dev c4424826f9 release: browser-extension 1.9.24 — one evaluate rule across Chrome+Firefox
shared/evaluable.ts is the single cross-browser rule for caller-supplied
JS: pickEvaluable accepts every code-param alias (expression/code/script/
function/js); wrapEvaluable passes bare expressions through and wraps
statement bodies in an async IIFE with the trailing value auto-returned.
Both dispatch paths consume it — browser-dispatch.ts (chrome.debugger
Runtime.evaluate, which also gains the plain 'evaluate' method alias
Firefox already accepted) and background-firefox.ts, whose previous
`return (${code})` wrap was a SyntaxError on any multi-statement body
and silently returned undefined for `expr;`. The Firefox path now always
settles promises in-page and routes rejections over the __hanzo_error
channel so callers see the real page-side error.

Tests: behavioral suite for the wrapper (evaluates wrapped output, not
string-matching); revive the three suites dead since the node bridge was
deleted — parity contracts repointed at browser-dispatch.ts, the IAM
contracts updated to the HIP-0111 canonical /v1/iam/oauth/token shape,
hub-wiring trimmed to the live background.ts surface, and the node-bridge
routing suite removed with its subject. 243 passing (was 186 + 3 dead
files).
v1.9.24
2026-07-02 21:38:18 -07:00
zeekay 8d5384c1de Merge remote-tracking branch 'origin/rip/api-callers-to-v1' 2026-07-02 13:18:34 -07:00
zeekayandClaude Opus 4.8 3633c5a6ff rip: migrate IAM /api/* callers to canonical /v1/iam/*
extension dxt server: /api/userinfo→/v1/iam/oauth/userinfo, /api/login→
/v1/iam/login. Clean break, no aliases (org rule: one way, /v1/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 13:12:56 -07:00
355f6496fb Debrand: replace Casdoor name with Hanzo IAM in comments/docs/aliases (#5)
Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-06-30 14:59:31 -07:00
z 28c75ec6a8 docs(brand): add hero banner 2026-06-28 20:10:52 -07:00
z 22729e5218 chore(brand): dynamic hero banner 2026-06-28 20:10:51 -07:00
51f59c44ff fix(iam): migrate to canonical /v1/iam/oauth/* (HIP-0111) (#4)
Co-authored-by: Zach Kelling <z@zeekay.io>
2026-06-24 19:16:15 -07:00
Hanzo AI 1c2b9f7915 kms: clarify upstream API path note 2026-06-24 10:51:37 -07:00
zeekay 1298aea79a release: browser-extension 1.9.23 v1.9.23 2026-06-20 13:05:07 -07:00
1560cd1228 ci: run on self-hosted ARC pool (hanzo-build-linux-amd64/deploy), not GitHub-hosted (#3)
Co-authored-by: zeekay <z@hanzo.ai>
2026-06-19 20:34:40 -07:00
Antje WorringandClaude Opus 4.8 c6e3ac5533 docs: tidy LLM.md indexes; CLAUDE.md -> LLM.md symlink convention
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:24:21 -07:00
Antje WorringandClaude Opus 4.8 bb0cd246e8 ext 1.9.22: native-zap singleton — one native port, no host-spawn storm
connectNativeZap had no singleton guard: state.port was overwritten without closing the
prior port, and every onDisconnect scheduled another connect. Called from startup + the
3s reconnect, ports (+ their native hosts) accumulated into a host-spawn storm once the
router stopped rejecting duplicates. Now: tear down any prior port on entry; a replaced
port (state.port !== port) never reconnects. Exactly one native host per browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:42:36 -07:00
Antje WorringandClaude Opus 4.8 52076f2c25 ext: package.json is the single source of truth for version
build.js stamps the package.json version into every manifest (chrome/firefox/safari)
so the three can never drift. Syncs src manifests to 1.9.21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.9.21
2026-06-16 22:04:52 -07:00
Antje WorringandClaude Opus 4.8 03e081e83a ext 1.9.21: native ZAP is the one transport (Chrome+Firefox)
connectNativeZap (shared/native-zap.ts) registers the browser as a zapd provider
over native messaging and dispatches inbound ROUTE commands. Renames cdp-bridge.ts
-> browser-dispatch.ts; deletes the cdp-bridge-server.ts WS bridge. No ws://localhost,
no port roulette, no CDP fallback in the default boot path. Patch bump (no 2.0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 22:03:00 -07:00
Antje WorringandClaude Opus 4.8 3bb7d57a9f ext 1.9.20: re-enable legacy CDP/MCP transport as working fallback
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>
v1.9.20
2026-06-15 14:07:15 -07:00
zeekay 2ad3d464b4 extension(browser): v1.9.19 — decomplected click + cross-window tab switch
hanzo.click: removed framework-specific hacks (Drupal AJAX lookup, jQuery
fallback). Those don't generalize — every new framework would need its
own hack. Replaced with a clean realistic-event-sequence dispatch:

  mouseover → mouseenter → mousemove → mousedown → focus → mouseup → click

with proper clientX/Y/button/buttons/composed. Events still have
isTrusted=false (a WebExtension can't change that), so frameworks that
explicitly check isTrusted (Drupal AJAX behaviors, some React libs)
will still reject — those need the v1.10.0 BiDi backend on the Python
bridge to produce real trusted browser input via Firefox's
--remote-debugging-port WebDriver BiDi endpoint.

Target.activateTarget: now ALSO calls windows.update(focused:true) so
cross-Firefox-window tab switching actually brings the OS window to
the foreground (not just the tab within its current window).

hanzo.switchTab: new Layer-3 ergonomic alias. Same implementation as
Target.activateTarget but with a human-readable name. Recommended for
everyday tab switching in scripts and the MCP browser_tool wrapper.

Architectural note: hanzo.click is the synthetic path (best-effort for
sites that don't check isTrusted). When the v1.10.0 Python bridge BiDi
backend lands, calls to hanzo.click will auto-route to Input.bidi
.dispatchMouseEvent for trusted browser-input events. That is the
proper decomplected design — backend per trustedness level, ergonomic
alias on top. No per-framework hacks in the extension.
v1.9.19
2026-06-10 13:10:16 -07:00
zeekay 5e270c061d extension(browser): v1.9.18 — propagate page-side throws, fix textarea fill
Two bugs caught while driving the SEC TCR form:

1) runFunc silently swallowed page-side errors.
   scripting.executeScript returns [{result, error, frameId}] per frame.
   We were mapping to r?.result which dropped the `error` field — when a
   page-side function threw, callers saw null instead of an error.

   Fix: keep the full InjectionResult shape and throw on first.error.

2) hanzo.fill called HTMLInputElement's value setter on a TEXTAREA.
   That throws TypeError "Illegal invocation" silently (now visible
   thanks to fix #1). The chained `|| HTMLTextAreaElement.prototype...`
   never executed because the first ?.set was truthy.

   Fix: per-tag dispatch — pick the matching prototype's native setter
   (INPUT/TEXTAREA/SELECT each get their own). Wrapped in try/catch with
   plain `el.value = value` as last resort.

Verified on www.sec.gov/forms/tcr-external-form:
  - hanzo.fill on textarea[name^="in_your_own_words"] now sets the value
  - hanzo.clear on same textarea now works
v1.9.18
2026-06-10 11:47:16 -07:00
zeekay bb6ed472be extension(browser): v1.9.17 — fix HTTP-action dispatch fallthrough
handleExtensionRequest() was returning "Unknown extension action" for
every Page.* / DOM.* / Input.* / hanzo.* method added in 1.9.16. The
handler had only routed monitor.*/audit.* and fell off the end for
everything else.

Fix: fall back to executeMethod (the canonical CDP-shaped dispatcher)
before returning Unknown. Strictly additive — all previously-working
actions continue through their existing branches.

Verified locally on www.sec.gov/forms/tcr-external-form: hanzo.listForm
returns 183 elements with full label association; hanzo.scroll +
hanzo.clickByText + hanzo.fillByLabel are now routable.

Next: a v1.10.x multi-backend Python bridge that adds WebDriver BiDi
(Firefox 129+), CDP (Chrome 124+), and safaridriver (Safari 17+) as
secondary backends for network interception, log streams, and other
capabilities the WebExtension scripting API cannot provide. Same wire
surface, prefix-based routing (BiDi.* / CDP.* / WD.*).
v1.9.17
2026-06-10 11:29:59 -07:00
zeekay 610367e6fd extension(browser): v1.9.16 — CSP-safe DOM ops + CDP-shaped surface
Decomplected three orthogonal layers:
  L3: hanzo.* ergonomic aliases
  L2: CDP-shape canonical primitives (Page.* DOM.* Input.* ...)
  L1: runFunc(tabId, fn, args) → scripting.executeScript({world: MAIN, func: ref})

Every DOM op now passes a real function reference to scripting.executeScript
instead of constructing Function(codeStr)(). The Function() path is blocked
by strict page CSP on sec.gov / github.com / any default-src 'self' SPA.
Direct function refs are exempt from page CSP per the WebExtension spec, so
the new path works everywhere.

Added (all CSP-safe):
  • hanzo.clickByText  fillByLabel  findByText  listForm  submitForm
  • hanzo.press        scroll        waitForText  waitForMutation
  • hanzo.uploadFile   dialogAccept  observe/observeRead/observeStop
  • Input.dispatchMouseEvent  Input.scrollWheel
  • DOM.querySelector  DOM.scrollIntoView  DOM.focus
  • Page.printToPDF

Rewrote with runFunc (CSP-safe):
  • hanzo.click  fill  check  uncheck  clear  getText  getHTML
  • hanzo.querySelectorAll

Capabilities handshake bumped to advertise the new method names.
Driver-side mapping in hanzo-tools-browser/browser_tool.py updated with
~100 snake_case action → wire-method entries (Python MCP surface).

API reference: dist/browser-extension/firefox/API.md
v1.9.16
2026-06-10 11:21:29 -07:00
Hanzo AI d268fa9298 cleanup: remove AI-slop summary / status / plan / report files
Reverts violations of the durable rule that current-state docs belong
in LLM.md and history belongs in git log. Removed files were session
handoffs, agent-style "complete success" / "1000%" reports, dated
audit dumps, and stub NOTES.
2026-06-07 13:34:07 -07:00
Hanzo AI 44fccae452 chore: drop stale hanzo-auth.ts.bak
Fossilized copy left over from an earlier rewrite of packages/tools/src/auth.
The live hanzo-auth.ts is the source of truth.
2026-06-03 11:26:39 -07:00
Antje WorringandClaude Opus 4.7 d002cc136c ZAP wire fixes + WebGPU on-demand HF model loader
Fixes the browser ↔ hanzo-mcp ZAP path end to end:

- shared/zap.ts: vendor ZAP_PORTS constant and restore localhost
  port-probe fallback in discoverZapServers when the mDNS native
  helper isn't available. Sequential probe (stops on first hit) so
  the Chrome console stays quiet on the happy path.
- src/manifest.json: add deterministic ``key`` (extension ID
  ``biingenefmanpecedoafkfajbnlgdmbl``) + ``nativeMessaging``
  permission so the ``ai.hanzo.zap_mdns`` helper can be invoked.
- background.ts: configurable HF model source for the WebGPU
  runtime via chrome.storage (``hanzo_model_url`` or
  ``hanzo_model_hf={repo,file,revision}``); falls back through HF →
  bundled artifact → remote providers.
- webgpu-ai.ts: cache-first fetch backed by the service-worker
  Cache API + progress logs, ``WebGPUAI.huggingFaceURL`` helper, and
  ``WebGPUAI.evictCache`` for rotating model artefacts. Drop-in for
  the existing single-blob loader; lays groundwork for sparse-load
  MoE work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:24:31 -07:00
Hanzo AI 4aebce7014 extension(vscode): drop HANZO_IAM_ env-var prefix, use canonical IAM_
Aligns the standalone MCP server's auth path with the canonical IAM_
env contract enforced by hanzo-iam 1.30.0 (python-sdk 3cb6a34) — there
is exactly one prefix, IAM_, with no upstream-brand alias chain.

- HANZO_IAM_ENDPOINT       → IAM_ENDPOINT
- HANZO_IAM_LOGIN_URL      → IAM_LOGIN_URL
- HANZO_IAM_CLIENT_ID      → IAM_CLIENT_ID
- HANZO_IAM_CLIENT_SECRET  → IAM_CLIENT_SECRET

Updates docs/BUILD.md and docs/MCP_INSTALLATION.md to match.
2026-05-14 23:40:22 -07:00
Antje Worring 2287b6a707 extension(browser): gate legacy MCP+CDP WebSockets behind feature flag
Module-load WS connections to ws://localhost:3001/browser-extension
(legacy MCP) and ws://localhost:9223/cdp (legacy CDP bridge) spam
ERR_CONNECTION_REFUSED on every reload when no local hanzo-mcp / CDP
server is running.

ZAP mDNS discovery (startZapDiscoveryLoop / discoverZapServers, added
in 1.9.13–1.9.15) is now the canonical transport, so the legacy paths
are dead-on-load by default.

Adds ENABLE_LEGACY_TRANSPORTS = false (top of background.ts) and gates:
- connectToMCP() body (early-return when off)
- The module-load call site for connectToMCP()
- The module-load call site for cdpBridge.startWebSocketServer(cdpPort)

Flip the flag to true when developing against a local WS server.
ZAP path is untouched.
2026-05-14 15:16:53 -07:00
Hanzo AI 8caa647106 ext 1.9.15: cdp-bridge routing tests + mDNS-only ZAP discovery contract
Browser extension 1.9.14 -> 1.9.15.

- Add cdp-bridge-routing.test.ts: result-unwrapping, awaitPromise, multi-tab fan-out
- Add cross-browser-parity.test.ts: IAM URL, ZAP wire, sidebar/overlay surface, inspect shortcut
- Add execute-script.test.ts: page-world injection contract
- Add webextension-polyfill.test.ts: Chrome vs Firefox parity for chrome.* / browser.*
- Update shared-zap.test.ts to the new mDNS-only contract (DEFAULT_ZAP_PORTS removed;
  HANZO_SERVICE_TYPE = _hanzo._tcp.local. is the canonical discovery entry)
- background.ts / background-firefox.ts: tab_id routing path for ZAP tools/call
- shared/zap.ts: HANZO_SERVICE_TYPE constant, drop legacy port-probe list
- manifest version bump to 1.9.15 (Chrome + Firefox)
- .gitignore: *.xpi / *.crx build artifacts

Driving Porkbun + CF dashboards end-to-end this session surfaced the tab_id
routing path; the new tests pin the contract so it doesn't silently regress.

Tests: 250 pass / 0 fail (vitest run).
Patch-bump.
v1.9.15
2026-05-10 17:17:45 -07:00
Hanzo Dev 07b9bf6239 fix(extension): 1.9.14 — drop data_collection_permissions (Firefox manifest warning)
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.
2026-05-09 14:09:10 -07:00
Hanzo Dev 5932768548 fix(extension): 1.9.13 — mDNS-only discovery via _hanzo._tcp.local.
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)
2026-05-08 13:24:13 -07:00
Hanzo AI aabc15a382 fix(extension): 1.9.12 — content-script port keep-alive + correct package metadata
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).
2026-05-08 12:25:19 -07:00
Hanzo AI 6ca23de63a fix(browser-extension): 1.9.11 — alarms-driven keep-alive across MV3 idle
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.
2026-05-08 12:22:22 -07:00
Hanzo AI 179a5bc718 fix(browser-extension): 1.9.10 — drop application-level heartbeat
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.
2026-05-08 12:13:36 -07:00
Hanzo AI 8677e55f30 fix(browser-extension): 1.9.9 — heartbeat keepalive + connection robustness
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.
2026-05-08 12:10:26 -07:00
Hanzo AI 719cd51919 fix(browser-extension): 1.9.8 — Firefox driving, hardened evaluate path
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).
2026-05-08 11:51:47 -07:00
Hanzo AI ea747cff45 fix(browser-extension): 1.9.7 — connectZap retry chain survives bind-miss
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.
2026-05-08 11:38:10 -07:00
Hanzo AI 3200927f8c fix(browser-extension): 1.9.6 — native mDNS discovery + version footer
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).
2026-05-08 11:34:37 -07:00
Hanzo AI 39cf5950a5 fix(browser-extension): 1.9.5 — drop @hanzo/ui, switch to @hanzo/gui
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.
2026-05-08 11:05:21 -07:00
Hanzo AI 1781862fb1 fix(browser-extension): 1.9.4 — monochrome brand pass + on-page chat config
@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.
2026-05-08 10:50:20 -07:00
Hanzo AI cfd53c8142 fix(browser-extension): 1.9.3 — kill on-page FAB, edge-pinned sidebar, native FF sidebar
- 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.
2026-05-08 10:04:38 -07:00