Skip to content

v10.14.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 21:34
· 328 commits to main since this release

Built on Extension.js 4.1.33.

  • extension_submit and the server instructions state each store's least public outcome as the platform now ships it, and no longer say submissions go to testers. Chrome and Edge upload a draft that is not published and does not enter review, even for an item that is already published. Firefox uses the unlisted channel for a new add-on or one with no listed version, keeps the listed channel for an add-on AMO already lists, and stops before uploading when AMO cannot say which. Safari stops at the TestFlight draft unless the call asks for Apple review. No store's API sets listing visibility, and a store that only uploaded is recorded as "Uploaded, not public", never as live, which the submitted answer now says too.
  • On a public project, extension_publish, extension_release_status and extension_release_promote answer the link that follows a release channel, <workspace>.extension.dev/<project>/channels/<channel> (built with userlandChannelUrl from @extension.dev/urls), which always resolves to that channel's current build. extension_publish puts value.channelUrl beside the build page when a channel serves the published build (stable first), and says when none does; extension_release_status gives each channel row its channelUrl, per browser (/channels/<browser>/<channel>) for a channel promoted per browser; extension_release_promote answers the moved channel's channelUrl in place of publicChannelUrl. The descriptions and the server instructions say to send testers the channel link so they always get the newest build, and that the build page link pins one build. A private project, or one whose visibility could not be read, gets no channel link: value.channelUrlWithheld says why and names the extension_publish share link, and extension_release_status no longer lists a channel publicUrl for it.
  • A tab the browser refuses to script is named as such. The engine now reports a refused injection as InjectionRefused (the CLI answers E_COMMAND_UNSUPPORTED_FOR_TARGET), and extension_eval, extension_dom_snapshot and extension_inspect answer it as E_EXTENSION_PAGE_UNREACHABLE when the tab shows an extension page, with the surface context to use instead, or as the new E_PAGE_NOT_SCRIPTABLE (status page-not-scriptable) for a browser page or a web page outside the extension's host access, with how to get host access or pick another tab. Neither is ever a missing target, and the Firefox debugger-protocol routes for extension pages still take either answer as their cue.
  • extension_wait {for: "rebuild"} reads the reload stamp the engine now writes to ready.json (reloadedAt, reloadKind, reloadLabel). On an engine that writes it, a rebuild waits until both compiledAt and reloadedAt move past their values at call time (or past since) and answers reload.outcome: "reloaded" with reloadKind (full, background, content, page or safari-resync) and reloadLabel, so a content-script, page or Safari re-sync reload is confirmed instead of reported as not-stamped. An engine that writes no reloadedAt keeps the earlier answer: a brief watch for the background to reconnect, then reattached, still-detached or not-stamped.
  • extension_publish takes ttlHours up to 8760 (365 days), where it refused anything past 168. A private project's share link defaults to 7 days, and a caller can ask for up to the workspace plan's share window (30 days on free, 365 on Pro); the platform cuts a longer ask back to the window and the answer carries the expiresAt it set. The description used to say the link defaulted to 24 hours. extension_shares and extension_preview_web take no lifetime.
  • On a Firefox dev session, extension_eval {context: "page"} reaches a tab that shows the extension's own page on MV2 builds as well as MV3. A relative url such as sidebar/index.html (or its moz-extension:// form) is now evaluated over the debugger protocol in the tab that shows it before the MV2 tabs.executeScript fallback runs, which answered "Missing host permission for the tab" first, so the protocol route never ran. With no url, when the active tab is an extension page, the same route evaluates it and a warning names the url and the surface context to pass next time. A web tab still takes tabs.executeScript, and when the session has no debugger port the refusal's hint names the page's url and the declared surface context.
  • On Firefox, extension_open {surface: "action"} clicks a popup-less browser_action or page_action. The engine's replay covers chrome.action only and refused with "chrome.action not available", so the server now runs the browser's own toolbar action for the add-on (the path the _execute_browser_action shortcut takes) in Firefox's parent process over the debugger protocol, and answers triggered with manifestKey. In a headless run the listener's sidebarAction.open(), which Firefox allows only from user input, opened the sidebar. When that route cannot run, the answer is action-not-replayed (E_ACTION_NOT_REPLAYED), names the button the manifest declares and why the click did not run, and offers only routes that work (surface: "sidebar" when declared, context: "background"), not asTab or a headed restart. A manifest with browser_action used to read as "declares no action", which blamed the extension for the route's refusal, and browser_action and page_action popups now count as the action's popup.
  • A boot-failed answer from extension_dev or extension_start now stops the session it started and says so (value.sessionStopped), so the hinted second call starts fresh instead of being refused with E_SESSION_EXISTS. A compiler failure outside a module (compile_failed, a plugin hook or extension.config.js that throws) leaves the engine's watcher unarmed, so fix-and-save could never recover it. When the stop fails, the hint names extension_stop or replace: true. extension_doctor no longer tells a session whose compile failed that its browser is still launching: the executor leg reads skip, blocked by runtime-errors, and both legs name the next call (extension_wait {for: "rebuild"} after a module error, extension_dev with replace: true after compile_failed). extension_wait's contract-error answer carries the same hint.
  • New extension_screenshot saves a .png of one open extension surface (popup, options, sidebar, newtab, history, bookmarks) or web page in a live session and answers its path and pixel size, so agents stop scripting screencapture, AppleScript or Playwright. It asks the browser for that page's own pixels over the session's debug port, never captures the screen and never brings a window forward: CDP Page.captureScreenshot on Chromium (width and height set the viewport, deviceScaleFactor the pixel ratio, and the page gets its own size back afterward), the debugger protocol's screenshot actor on Firefox for a page shown in a tab (width and height crop from the top left), and WebDriver on Safari for web pages, where an extension surface is refused with E_SURFACE_NOT_CAPTURABLE. The file goes under dist/extension-js/<browser>/screenshots unless path names a file or folder. A bare server now lists 24 local tools, 35 in all, and extension_dom_snapshot points to it for pixels.
  • Engine text reaches the agent as plain text. Every answer is stripped of terminal escape sequences (colors, cursor moves, hyperlinks) where it is built, so a failed compile no longer arrives in error.message, value.output or an extension_doctor detail as the engine's colored frame, \u001b[31m×\u001b[0m ..., around the real line. extension_wait lists the compile error it reports in value.errors: the engine stamps a compiler failure outside a module (a plugin hook or extension.config.js that throws) with an empty errors and the cause in message, which used to answer errors: [] beside it.
  • extension_analyze answers the built manifest per browser. value.manifestByBrowser carries the emitted manifest.json of every browser built under dist (emitted), the resolved background, action key, sidebar key, permissions, host_permissions, chrome_url_overrides and browser_specific_settings of each (resolved), the names of those that differ between the browsers (differences), and per browser the source permissions or web accessible resources the build dropped (sourceDivergence). The extension_build hint names it, so comparing Chrome and Firefox no longer means reading dist/<browser>/manifest.json by script. The dropped-permission check behind extension_build's warnings now reads dist at the engine's project root, so a manifest in a subfolder of a package is compared too.
  • Every share, publish, deploy, distribute, release, ship, testers, link or store ask now leads with extension.dev. The server instructions depend on the options: with the platform tools on they route those asks to extension_publish first (the build's public page with a download and a run command for each browser, after extension_project_create and extension_auth when there is no project), with extension_preview_web share:true and extension_shares as the quick emulator link and extension_submit for stores, never a do-it-yourself route around the platform; local only, they say extension.dev does it and that --features=local,platform lists those tools. extension_preview_web leads with sharing, says content scripts run on the emulator's sample pages as a limit of the preview only, and names the Firefox view. extension_publish, extension_shares and extension_submit describe what a person gets, extension_submit says submissions default to the least public option each store allows unless the project's store settings say otherwise, every missing-login answer names extension_project_create, the extension_build hint names extension_publish when the platform is listed, and the held answer says extension.dev does that work once it opens. The /extension command gains a share action, and the shipped rules call the zip a store upload.
  • The condition wait moved from extension_wait to extension_eval, so extension_wait is read-only again. extension_eval takes until: {timeoutMs, intervalMs} (pass {} for 30000 and 500): it repeats the eval until the value is truthy and answers condition-met with value.result, or timeout (E_WAIT_TIMEOUT) with value.lastResult, and it stops at once on a refusal polling cannot change, such as eval-disabled. extension_wait keeps for: "rebuild", carries the read-only annotations again, and refuses a leftover until with E_BAD_REQUEST and a hint naming extension_eval, so a client that auto-approves read-only tools never runs code through it.
  • extension_wait waits for more than readiness. for: "rebuild" waits for the compile after a source edit, until ready.json's compiledAt moves past its value at call time or past since, then briefly for the reloaded background to connect again, and answers rebuilt with the new compiledAt, errors and reload.outcome (reattached, still-detached, or not-stamped for a content-script, page or Safari app re-sync reload, which ready.json does not stamp), or compile-error with the errors. until: {expression, context, url} polls extension_eval every 500 ms until the value is truthy and answers condition-met with it, or timeout with lastResult. Both work on Chromium, Firefox and Safari sessions, and the hints say to call them instead of sleeping or tailing the session log. Since until runs an eval, extension_wait now carries the annotations extension_eval does (not read-only), so a client that auto-approves read-only tools still asks before it.
  • extension_wait checks the browser before saying it is gone. A detached executor beside a live browser (the Safari automation session answering, or the browser pid the launcher recorded alive, including a relaunched Firefox after its first process exited) answers ok: true, status background-asleep, with how to wake the background: Safari and Firefox suspend a non-persistent background after 30 s idle, and Chrome an MV3 service worker. A Safari WebDriver session that Safari ended (someone interacted with the automation window) answers webdriver-session-ended with E_WEBDRIVER_SESSION_ENDED and the restart that opens a new one, in extension_wait, extension_eval and the extension_doctor safari-window leg. On Safari, extension_eval {context: "page", url} matches the window's page the way Firefox does (document, match pattern, then substring) and sends a host and port such as 127.0.0.1:8765 to http://127.0.0.1:8765/ instead of passing it to safaridriver, which refused it with "Could not parse requested URL".
  • extension_dev and extension_start take headless. headless: false opens a browser window the person can see, overriding EXTENSION_HEADLESS and any --headless in EXTENSION_BROWSER_FLAGS for that session only; headless: true forces headless; omitted, the session inherits the environment as before. Every answer that launches a browser reports headless, and a headless answer says the person cannot see the browser and how to open one they can. extension_open reads the session's own setting, so a visible session opened from a headless environment shows real popup, options and sidebar windows. A person who asked to see their extension used to be told it was open on their screen while it ran headless, and no tool input could change that.
  • extension_dev and extension_start say when the browser profile already held state, by any route: the profile input, the profile a project's extension.config.* sets for that browser (resolved against the project), the managed persisted profile, or the folder the engine reports in ready.profilePath. Whether the folder held state is read before the browser launches, since the browser writes into it on start. Every answer that launches a browser carries profileReused, and a reused profile leads the warnings: the extension's chrome.storage, granted permissions and restored tabs come from an earlier run, so read extension_storage before acting on a toggle or a first-run assumption. A profile set in extension.config.js used to be reused with warnings: [], and an agent clicked a toggle that was already on.
  • Next steps no longer hand the person a shell command. extension_create ends with asking "Want me to open it in Chrome so you can try it?" and calling extension_dev with headless: false, and moves the npm run dev line to manualAlternative, for use outside an agent. extension_add_feature ends the same way, and the server instructions tell every client to open the browser itself and never pass "run npm run dev" on as the person's step.
  • extension_start takes allowControl and allowEval (eval implies control), so a production run can be driven with extension_storage, extension_reload, extension_open, extension_dom_snapshot and extension_eval. The browser loads a copy of the build with the control producer added; dist/<browser> keeps the shipped bytes. extension_reload reloads the installed extension under open tabs the way a store update does, so content scripts an update orphans can be tested without a hand-written debugging script. The answer carries allowControl, allowEval and controlPort, and once the extension's background connects to the channel the load reads as confirmed (extensionLoaded: true). An engine whose start verb has no --allow-control (the published 4.1.33) is refused as engine-unsupported with E_ENGINE_TOO_OLD, naming the flag and pointing to extension_dev, instead of a bare exit.
  • extension_stop with browser ends only that browser's session. A project can run a dev session for one browser and a production session for another, and stopping the second used to end the first too: the search for leftover processes matched every extension dev, start or preview command line for the project and every managed profile folder, whatever the browser, and listed the other session's server under reaped. It now leaves alone a command line that names another --browser and looks only in that browser's managed profile folder.
  • extension_eval awaits a promise returned in a tab on every engine. With context: "page" or "content" through the in-tab executor (any Firefox tab, and a Chromium tab reached without CDP), the expression is wrapped so the tab settles the promise and this tool polls for the value, the way relay surfaces already did. Firefox used to fail with "result is non-structured-clonable data", reported as E_TARGET_NOT_FOUND although the tab was found, so an agent read a wait-then-check as the wrong tab. A result the browser cannot copy back now answers E_EVAL_NOT_CLONEABLE, saying to return a synchronous, cloneable value or use context: "background", and the tool description says where promises are awaited.
  • extension_dev runs one session per browser, not per project. A session for another browser now starts beside the live one, since each browser has its own dist/<browser>, ready.json, eval token, profile and port; a second session for the same browser is still refused, and the message now names that browser's output. replace: true stops only the session of the browser being replaced. The session tools that default browser to the live session (extension_inspect, extension_wait, extension_eval, extension_storage, extension_reload, extension_open, extension_dom_snapshot, extension_list_extensions, extension_assert, extension_logs, extension_stop, extension_doctor) answer E_SESSION_AMBIGUOUS (status browser-required) with the list of sessions when the project runs more than one, instead of picking the last one registered.
  • On a Firefox extension_start session, extension_eval and extension_dom_snapshot reach the extension's own pages over the debugger protocol: a surface shown in a tab (context: "options") and a page url (options/index.html or its moz-extension:// form) are read from the tab that shows them, since the production build carries the control producer in its background only and script injection cannot enter a moz-extension:// document. When the session records no debugger port, or no tab shows the page, the answer is E_EXTENSION_PAGE_UNREACHABLE naming extension_dev, never E_TARGET_NOT_FOUND for a page that may be open. extension_open confirms a Firefox options page from the debugger protocol's tab list. extension_start with allowControl no longer says the control verbs cannot attach while the contract has not recorded the control port yet, and no longer names the shipped dist/<browser> as the controlled copy; extension_wait on a production session with a control port says the verbs reach it once the background connects.
  • extension_start keeps one session per browser on a project, the rule extension_dev keeps. A start for a browser that already runs a live dev or production session on the project is refused with E_SESSION_EXISTS, naming that session and extension_stop with its browser, instead of rebuilding dist/<browser> under the running session and overwriting its registry entry (the engine refuses only a second dev session). The new replace: true stops only that browser's session first and reports it as replacedSession; a session for another browser starts beside it untouched.