v0.3.18

October 12, 2026

Video Agent

  • node10 adds a Video Agent that runs on Cloudflare Workflows (zship-video-agent). It plans the shot, submits it through Provider1, tracks the provider task until it finishes, and supports cancel, retry, resume and a per-user video library. Routes live under /ai/video-agent/*.
  • Run credentials are encrypted with IMAGE_AGENT_CREDENTIAL_KEY (64 hex characters, the same secret Image Agent uses). Without it, Video Agent returns CREDENTIAL_ENCRYPTION_UNAVAILABLE.
  • Nuxt template: /dashboard/video-agent, shown in the dashboard nav when videoAgent.enabled is set in app.config.ts. Astro template: /video-agent.
  • node10 AI calls (chat completions, prompt optimization, decision models) now reserve the worst-case cost up front and refund the unused part when the call settles. Calls with an unclear outcome are reversed instead of charged twice, and stranded refunds are retried in the background.

Chrome extension template

apps/chrome-extension-template was rebuilt into a full consumer side panel:

  • Email, Google (chrome.identity) and guest sign-in, plus guest-to-account conversion that keeps trial credits.
  • Credits, billing (Stripe, Creem, Dodo, Waffo checkout and customer portal), settings, and an AI image studio demo with a “My images” gallery.
  • Refresh tokens rotate in the background worker with single-flight refresh. An API key is created silently after sign-in, so users never see one.
  • Page context (selection, URL, title) is read on demand behind an optional <all_urls> permission instead of running a content script on every page.
  • Developer tools are behind a hidden developer mode and are stripped from production builds unless WXT_DEV_TOOLS=true.
  • Mock mode for previewing every screen without a backend, English and Simplified Chinese, Vitest tests, and preflight:strict to catch demo branding and broad permissions before publishing.

Behavior changes to check before upgrading

  • CDN /public-upload now accepts only Service Binding calls or HTTP calls with x-zship-key matching node6’s ZSHIP_KEY secret. The x-proxy-source header is ignored. If the secret is missing, the route fails closed with 500.
  • Upload object keys are generated by the server as {namespace}/{folder}/{timestamp}-{32 hex}.{ext}. The filename field and the customFilename prop are ignored. /user-upload writes under user-uploads/ and /public-upload under public-uploads/. SVG is rejected on both, existing objects are never overwritten (409), and anything other than images, video and PDF is served as a download with nosniff and a sandbox CSP.
  • Provider1 /generate: safety fields (NSFW, safety checker, moderation) are set by the operator. They are hidden from /models and callers can no longer override them. Fields a model does not declare return 400 UNSUPPORTED_FIELD. Callback URLs must be public HTTPS on port 443; localhost, private and reserved IPs are rejected.
  • Checkout: the referralCode field in checkout requests is ignored. Commission now comes only from the referral bound in node1. A client baseUrl is honored only for the project’s own domain or subdomains over HTTPS, or for origins listed in node3’s new CHECKOUT_ALLOWED_ORIGINS. Anything else falls back to the project domain.
  • node2 /email/* is admin-only (admin.support). The from field is ignored, and tokens, JWTs and codes are redacted from logged and returned HTML.
  • Rate limits: node2 public error reports allow 30 requests per minute per IP with a 32 KB body limit. node7 waitlist joins allow 5 per minute per IP with a 4 KB limit. Over the limit you get 429, and oversized bodies get 413.

Payments: refunds, chargebacks and commissions

  • Partial refunds revoke buyer credits and referral commission in proportion to the refunded amount. Revocation is idempotent.
  • Chargebacks (Stripe charge.dispute.created, Creem dispute.created, Dodo dispute.opened / dispute.lost) revoke all credits and commission from the order and block the account from further payments in t_pay_account_block. Delete the row to unblock.
  • A coupon’s used_count now goes up only once the payment succeeds, not at checkout.

Accounts and guests

  • Converting a guest to a registered account now moves the guest’s API keys and Provider1 tasks to the new account. If the task transfer fails, a cron job retries it every hour at :30.
  • Taking over an unverified account (via email verification, Google or GitHub) revokes the API keys previously attached to that email. Revoked keys cannot be re-enabled, only replaced.
  • Google login: native clients send a raw nonce, and each nonce can be used only once. The existing web flow without a nonce keeps working.
  • Daily check-in credits are recorded as bonus, and zero-reward days no longer fail the check-in.

Migrations for self-hosted deployments

Apply before deploying the matching worker: Provider1 0038–0039 (before node1, which calls the task transfer); node1 0042–0045; node2 0013; node3 0016; node7 0005; node10 0020–0026.

Also check: node6 needs the ZSHIP_KEY secret; node1 needs the PROVIDER1 service binding; node2 and node7 need the new ratelimits bindings from wrangler.jsonc; node10 needs the VIDEO_AGENT_WORKFLOW binding.