Skip to content

Releases: dwgx/WindsurfAPI

v3.2.0

Choose a tag to compare

@github-actions github-actions released this 11 Jul 05:19

v3.2.0 — Statistics dashboard overhaul

2026-07-11 (UTC+9)

A feature release focused on the statistics dashboard: it closes a real
data-collection gap, adds a per-model dimension to the time series, enriches the
rankings, and fixes two chart-rendering bugs found by an adversarial pre-commit
review. Every change was verified against a real headless-Chrome render via CDP,
and the whole batch passed a multi-reviewer adversarial pass before commit. Full
suite green (2531), i18n green.

Added — statistics

  • Token usage breakdown now populates on DEVIN_CONNECT deployments. The
    dashboard's fresh-input / cache-read / cache-write / output breakdown had been
    fed only from the Cascade paths, so a connect-only deployment (e.g. homecloud)
    showed all-zero token totals despite hundreds of requests. recordTokenUsage
    is now fed from the DEVIN_CONNECT request path too — the non-stream choke point
    plus all three streaming sites (primary, transient-replay, re-login-replay).
    Safe no-op when a turn carries no usage.
  • Request time series shows which models were requested. Hovering an hour in
    the request-volume chart now lists the top models used in that hour (aggregated
    client-side from the recent-requests ring buffer — no backend change).
  • Rankings gained dimensions. The overview leaderboard rows now carry a
    second line: a success-rate dot (green / amber / red), lifetime credits, and
    p95 latency — so it answers "which model, how reliable, how expensive, how
    fast", not just raw request count.
  • Credits are readable. The credits total compacts large numbers with a k
    suffix (e.g. 41.2k) and keeps the exact value in a hover tooltip. No USD
    conversion — Windsurf retired the credit system (2026-03 → quota tiers), so
    there is no authoritative credit→$ rate; credits stay an internal accounting
    unit.
  • Cache-efficiency indicator. The token card shows what share of input was
    served from cache (a real credit-saving signal), filling what was previously
    dead whitespace.

Fixed — chart rendering

  • High-error bars no longer render gray. The bar color mixed brand-blue →
    amber → red linearly by error ratio, which passed through a dead gray (~#a09f9a)
    around an 18% error rate — a high-error hour looked disabled, not errored.
    Bars now use discrete semantic buckets (healthy blue / warning amber / high-error
    red), matching the rankings dots and the model table's red/green.
  • Trend error line no longer dives off-canvas (regression fix). The new
    converged Y-axis (which lifts the floor for steady high-volume data so small
    variation stays visible) mapped the low-magnitude error overlay below the plot,
    silently hiding real errors. Anchor points are now clamped to the plot band, so
    a sub-floor error series sits truthfully on the baseline instead of vanishing.
    Spiky/low data keeps its zero baseline unchanged.

Fixed — tooling

  • check-i18n gate #8 no longer false-positives on JS expression fragments. A
    nested-${} template (inline IIFE) could leak arithmetic/member-access
    fragments into the hardcoded-English scan. The scanner now skips
    operator/paren arithmetic, optional chaining, method calls, and code-shaped
    member access — while tightening the member-access guard so it does not
    skip real English copy that contains a dot (v2.0 released, 95.5% done still
    flag correctly).

Housekeeping

  • Repo reorg: all AI / handoff / dev notes moved under a git-ignored
    docs-internal/ (root keeps only README*.md, LICENSE, CONTRIBUTING.md,
    SECURITY.md, CLAUDE.md); source/script comment path references synced.

Verification

  • npm test2531/0 (+B1 token-wiring tests over v3.1.5).
  • node src/dashboard/check-i18n.js green (new copy double-written en/zh).
  • CDP against real headless Chrome: token breakdown populates, credits compact +
    tooltip, rankings sublines with colored dots, per-model hover, converged-Y trend
    with error line on-plot, high-error bars red (chroma-asserted, no gray).
  • Adversarial multi-reviewer pre-commit pass: 1 real regression (error line) + 3
    edge findings caught and fixed before commit.

v3.1.5

Choose a tag to compare

@github-actions github-actions released this 10 Jul 22:17

v3.1.5 — Dashboard a11y + JS-region i18n gate + CORS & proxy SSRF fixes

2026-07-11 (UTC+9)

An accessibility and i18n-hardening release. Every change landed test-first
(a failing/asserting test first, then the change) and the dashboard work was
verified against a real headless-Chrome accessibility tree via CDP, not just
static assertions. Also closes a dashboard CORS-preflight allowlist bypass and a
DNS-rebinding TOCTOU in proxy connections. Full suite green (2527), i18n green.

Added — accessibility

  • Segmented controls are now real radio groups. The four .seg-group
    single-select toggles (pool view, overview trend range, stats chart type,
    stats range) declared role="tablist" while behaving as radio groups — a
    screen reader announced a tablist whose "tabs" had no selected state. They are
    now role="radiogroup" with role="radio" + aria-checked buttons and a
    localized group aria-label. Selection state was CSS-class-only; a new
    App._syncRadioGroup toggles .active and aria-checked together, and all
    six toggle handlers route through it so the a11y state always tracks the visual
    one. The .active CSS is unchanged (selectors key off the class, not the role).
  • Chart canvases are labelled images. The three chart canvases
    (overview-trend, stats, model-pie) now carry role="img" + a dynamic
    aria-label. The overview trend also gets an sr-only data-table fallback and
    a spoken summary (_updateTrendA11y, refreshed on every render) so a
    screen-reader user gets the same numbers a sighted user reads off the chart.
  • Modals are dialogs with focus management. Utils.confirm / Utils.prompt
    now render role="dialog" + aria-modal="true" + aria-labelledby. A new
    App._trapFocus keeps Tab focus cycling inside the open dialog, and the
    previously-focused element is captured on open and restored on close. The
    existing Esc/Enter handling and the custom-select Esc deferral are preserved.
  • data-i18n-aria-label runtime. I18n.apply gained an aria-label
    translation handler (mirroring the existing title handler: store original,
    restore under zh). New aria.* and overview.trend.a11y* strings are written
    in both en and zh-CN.

Fixed — security

  • Dashboard CORS preflight bypassed its own allowlist (#9). The dashboard API
    had an allowlist-gated OPTIONS handler, but it was dead code: server.js
    answered every OPTIONS — including /dashboard/api/* — with a blanket
    Access-Control-Allow-Origin: * before the dashboard dispatch, so preflights
    never consulted DASHBOARD_CORS_ORIGINS. Not a data-read hole (actual dashboard
    responses stayed allowlist-gated, and the global preflight omits
    X-Dashboard-Password from Allow-Headers, so authenticated cross-origin calls
    were blocked at preflight regardless), but the documented "preflight shares the
    allowlist decision" never happened, and in DASHBOARD_ALLOW_NO_AUTH=1 mode it
    weakened CSRF protection for cross-origin JSON POSTs. The global OPTIONS
    short-circuit now excludes /dashboard/api/, so dashboard preflights fall
    through to the allowlist handler (allowed origin echoed + Vary: Origin,
    disallowed origin gets no ACAO). /v1/* preflight is unchanged (blanket *,
    no credentials — correct for the open, key-authenticated API).
  • DNS-rebinding TOCTOU in proxy connections (#11 / W6). validateProxyHost
    resolved a configured proxy host once for its private-IP check, but the actual
    net.connect(host) (SOCKS) and HTTP CONNECT tunnels re-resolved the hostname
    at dial time — a second DNS lookup an attacker's server can answer with a
    private address (169.254.169.254, 127.0.0.1, …), turning a "public" proxy
    into an SSRF pivot into the host's internal network. New
    net-safety.resolveProxyConnectHost resolves the proxy host once, rejects
    if any returned address is private (mixed public+private rebinding answers
    included), and returns a vetted IP literal to dial — so the socket performs
    no second resolution and the address we validate is exactly the address we
    connect to. Wired into all five proxy connect paths (SOCKS tunnel + the four
    HTTP CONNECT tunnels, including dashboard proxy-test). Honors
    ALLOW_PRIVATE_PROXY_HOSTS (still pins to a literal; skips the private
    rejection). The destination reached through the proxy is left as a name on
    purpose — the proxy resolves it, not us.

Added — i18n gate

  • check-i18n gate #8: hardcoded English in the App <script> region. Checks
    #6/#7 stop at the <script> tag, leaving the entire JS half — where most
    runtime UI copy is generated — unguarded: el.textContent = 'Save' or an
    innerHTML template with English text nodes could ship untranslated with a
    green gate. The new scan flags English string literals assigned to DOM copy
    sinks (textContent/placeholder/title/…) and text nodes inside innerHTML
    templates that bypass I18n.t(). It is calibrated to 0 findings on the
    current index.html (multi-word phrases are copy; single tokens must clear
    identifier / CONSTANT / dotted-key / URL / unit exclusions plus a
    universal-technical-term allowlist; metadata rows are split on separators). The
    gate is warn-first — it reports findings but does not touch the exit code,
    so a future regression is visible without breaking the build; it can be
    promoted to a hard error once the script region stays clean.

Verification

  • npm test2527/0 (+23 over v3.1.4: 12 a11y + 2 i18n + 7 proxy SSRF + 2 CORS).
  • node src/dashboard/check-i18n.js green (including the new #8 = 0 findings).
  • CDP against real headless Chrome: live-DOM radiogroup/radio roles + aria-checked
    toggling, canvas image role, sr-only trend table, role="dialog" modals with
    focus-return on Escape, and Accessibility.getFullAXTree confirming
    radiogroup/radio/image roles (no tablist) and aria-label switching between
    en/zh live.

v3.1.4

Choose a tag to compare

@github-actions github-actions released this 10 Jul 20:26

v3.1.4 — Self-update restart, in-flight leak, fire lifecycle, safer update.sh

2026-07-10 (UTC+9)

A hardening release from a full adversarial review of the v3.1.0→v3.1.3 work.
Every fix landed test-first (a failing regression test first, then the change),
and the whole set was independently re-reviewed for release-blocking regressions
before shipping. Full suite green (2504), i18n green.

Fixed

  • Self-update would stop the service on systemd. After a successful dashboard
    self-update the process did process.exit(0), whose comment assumed PM2
    autorestart. On a systemd Restart=on-failure host (e.g. homecloud) exit 0 is
    a success and systemd does not relaunch — so "Update" bricked the service
    until a manual restart. The self-update path now exits with a non-zero
    restart-requested code (75 / EX_TEMPFAIL) so systemd Restart=on-failure and
    PM2 autorestart both relaunch. Normal SIGINT/SIGTERM shutdown keeps its separate
    exit(0) path in index.js (unchanged). Only fires when the update actually
    changed the commit, so no restart loop.
  • In-flight slot leak on mid-request re-login. chat.js released the account
    by its mutable apiKey (releaseAccount(acct.apiKey)) in two attempt paths. A
    background re-login rekeys apiKey in place, so releasing by the stale key
    leaked the in-flight slot forever (the same class as REF-1/#165). Both paths now
    release by the immutable releaseAccountById(acct.id), matching the already-correct
    finalize path. Regression tests drive the real non-stream and stream handlers
    through an in-place rekey and assert the slot returns to 0.
  • WebGL pool fire didn't recover after leaving the overview, and a late poll
    could remount hidden fires.
    Returning to the overview hit the _poolSig
    short-circuit and never re-mounted the fire; an in-flight overview request that
    resolved after navigating away could mount WebGL contexts on a hidden panel.
    _teardownFires() now invalidates _poolSig, removes the canvases, and restores
    the emoji fallback; navigate()/loadOverview() carry an overview-load
    generation + active-panel guard so a stale async load can't remount. Verified via
    CDP: 5 saturated rows → 3 contexts; leave → 0; return → 3; late render while on
    another panel → 0.
  • Trend chart could still clip a pure-nice double peak. niceMax + padding
    didn't fully cover data like [0,100,100,0] (peak on the top row, Catmull-Rom
    overshoot above it) or a 100% success-rate plateau. The trend spline now clamps
    its bezier control points to the plot bounds (_smoothPath/_trendSample take an
    optional yBounds, passed only by the overview trend); the main stats chart is
    unchanged (default null).

Changed

  • Docker image defaults DEVIN_CONNECT=1. v3.1.1 set it in docker-compose, but
    a bare docker run / K8s pod running the image directly still fell back to
    Cascade+emulation (#210). The Dockerfile now also sets ENV DEVIN_CONNECT=1.
    Overridable: docker run -e DEVIN_CONNECT=0, compose env, and K8s env all win
    over the image default.
  • update.sh no longer hard-resets on any pull failure. It used to
    git reset --hard whenever pull --ff-only failed, discarding local changes/
    commits. Now a dirty tree or local-ahead commits fail closed (asks you to
    review first); a destructive reset requires WINDSURFAPI_UPDATE_FORCE_RESET=1,
    which stashes (--include-untracked) before resetting. Clean deploys still
    fast-forward normally (runtime files are gitignored, so they don't trip it).

Ops notes

  • If your systemd monitoring alerts on "main process exited non-zero", a dashboard
    self-update now exits 75 once per successful update — add SuccessExitStatus=75
    to the unit to silence it. (The repo ships no systemd unit; this is deployment-side.)
  • update.sh is stricter than the dashboard updater (bare git status --porcelain
    vs -uno): an untracked, non-ignored stray file makes it fail closed by design —
    review it or set WINDSURFAPI_UPDATE_FORCE_RESET=1.

Notes

  • Verified: full suite green (2504/0, +10 new regression tests), i18n green,
    bash -n update.sh clean, fire lifecycle + trend clamp verified via CDP.

v3.1.3

Choose a tag to compare

@github-actions github-actions released this 10 Jul 17:44

v3.1.3 — Harden the WebGL pool fire (context leaks fixed)

2026-07-10 (UTC+9)

A follow-up to v3.1.2. An adversarial code review of the new WebGL pool fire
found three ways it could leak or thrash WebGL contexts; all three are fixed.
No user-visible behaviour change beyond "the fire no longer wastes GPU / breaks
under a full-pool rate-limit event".

Fixed

  • Unbounded fire contexts under pool-wide saturation. A row got its own
    WebGL2 context whenever the account was saturated, with no upper bound. A
    pool-wide rate-limit event (the exact state this dashboard exists to show)
    marks many accounts saturated at once, so a 20-account deployment would spawn
    20+ contexts — past the browser's ~16-context cap, which silently drops the
    oldest and thrashes the GPU. Fires are now capped to the 3 hottest .hot
    rows (by intensity, _FIRE_MAX); the rest keep the emoji/CSS highlight.
  • Orphaned fires on a transient empty poll. When a /connect-metrics poll
    came back empty (cm===null, a common transient blip), _renderPoolHealth
    rebuilt the panel to the empty state and returned without disposing the
    running fires — their RAF loops kept rendering the full 3-pass pipeline against
    detached canvases until a later non-empty poll. Empty-state now disposes first.
  • Fires running forever after leaving the overview. display:none does not
    pause requestAnimationFrame (only a hidden tab does), so navigating to
    another panel left every fire burning its 3-pass GL in the background. navigate()
    now disposes all fires (re-mounted on return to overview).

All three funnel through a new _teardownFires() that force-loses each WebGL
context. Verified via CDP: 5 saturated accounts → 3 fires; leaving overview and
a cm===null poll → 0 fires (contexts reclaimed).

Notes

  • Verified: full test suite green (2494), i18n check green, dashboard syntax green.

v3.1.2

Choose a tag to compare

@github-actions github-actions released this 10 Jul 17:25

v3.1.2 — Self-update fix, trend-chart clipping fix, WebGL pool fire

2026-07-10 (UTC+9)

A small fix + polish release: the dashboard Update button works again, the
request-trend chart no longer clips its peak, and saturated accounts now burn a
real WebGL flame in the pool-health view.

Fixed

  • Dashboard "Check Update" printed ✗ Invalid JSON. An empty-body dashboard
    POST (App.api('POST', path) with no payload → Content-Length: 0) hit
    JSON.parse(''), which throws, and the route surfaced it as Invalid JSON.
    The Update button (POST /self-update) sends no body, so it always failed.
    Empty bodies are now treated as {}. This fixes every no-payload dashboard
    POST (self-update, probe-all, langserver restart, …). Route-level regression
    test added.
  • Request-trend chart clipped its peak (最高点的线超出图外). The smoothed
    (Catmull-Rom) request line overshoots above a peak when both neighbours are
    lower; the peak data point sat on the very top row (12px padding), so the
    overshoot drew above the canvas. The left-axis max is now rounded up to a
    "nice" number (niceMax, e.g. 74 → 100) and the top padding is larger, giving
    the overshoot headroom. Axis labels also come out cleaner (80/4=20 not 74/4).

Added

  • WebGL fire on saturated pool rows (ported from KiroStudio FireCanvas). In
    the StatusBars pool-health view, an account that is RPM-saturated now burns a
    real flame across its row: a WebGL2 three-pass pipeline (flame sim → gaussian
    blur → glow composite) with 7-stop intensity coloring — the harder the account
    is working (RPM / inflight), the hotter the color (cyan → green → gold →
    orange → violet → ice-white → ruby-red at full tilt).
    • Scales to hundreds of accounts: the flame is mounted only on saturated
      (.hot) rows — usually 1-2 at a time — so there are never more than a couple
      of live WebGL contexts. Unmounting (or switching to the grid view) force-loses
      the context, so contexts never leak. Verified via tools/verify-fire.mjs
      (CDP): fires dispose to 0 on view switch, contexts stay alive while mounted.
    • Graceful fallback: no WebGL2 (or prefers-reduced-motion) → the row keeps
      its existing emoji / CSS-glow highlight.

Notes

  • Verified: full test suite green (2494), i18n check green, dashboard syntax
    green; fire + trend verified via CDP DOM inspection + screenshot.

v3.1.1

Choose a tag to compare

@github-actions github-actions released this 10 Jul 05:00

v3.1.1 — Docker defaults to native tool calling, fable env-lift fix

2026-07-10 (UTC+9)

A small follow-up to v3.1.0. Two changes, both aimed at agent clients (Claude
Code / Cline / Codex) that declare tools: Docker now opts into the native
tool-call path out of the box, and the fable family no longer idles to an empty
completion when a caller <env> block is present.

Fixed

  • claude-5-fable-* empty completion with tools + <env> (#209). On the
    prompt-emulation path, lifting the caller's <env> block (working directory /
    git status / platform) into the proto tool_calling_section alongside tools
    made the fable planner return 0 text / 0 thinking / 0 tool_calls. The env-lift
    is now skipped for the weak-model family (shouldLiftCallerEnv), and a global
    escape hatch WINDSURFAPI_ENV_LIFT=0 disables it for every model. Non-fable
    models are byte-identical — they still get the env-lift.

Changed

  • Docker defaults DEVIN_CONNECT=1. A fresh docker compose up now uses the
    pure-HTTP cloud egress + native tool-call path out of the box. Native is what
    lets weaker models (glm-5.2 etc.) follow the tool protocol reliably in agentic
    clients — with DEVIN_CONNECT OFF, requests fall back to Cascade +
    prompt-emulation, where those models intermittently narrate instead of
    emitting a tool_call (root cause of #210). Overridable: set DEVIN_CONNECT=0
    in your .env to force the legacy Cascade path. Bare-source / systemd deploys
    are unchanged (the code default stays OFF; only the Docker compose env opts in).

Notes

  • #210 ("无法在 claude code 持续运行", glm-5.2) was reproduced live: on the
    emulation path glm-5.2 dropped ~1/3 of turns to narration-instead-of-tool_call;
    on the native path (DEVIN_CONNECT=1) it ran 50+ consecutive tool calls with
    zero stalls. The Docker default change is the fix.
  • tools/model-probe.mjs gained a --tool-follow / --repeat mode that
    quantifies tool-call adherence per model (the glm-vs-fable comparison).
  • Verified: full test suite green (2493), i18n check green.

v3.1.0

Choose a tag to compare

@github-actions github-actions released this 10 Jul 02:28

v3.1.0 — Native tool calling restored, 429-lockout mitigation, calmer dashboard

2026-07-10 (UTC+9)

A fix + reliability release. The headline is restoring native tool calling for
Claude-family models through the DEVIN_CONNECT path (agent clients like Claude
Code and OpenCode work again), plus a set of small-pool rate-limit mitigations
and dashboard polish. Every root cause was confirmed live against real
devin.exe wire captures before a fix landed.

Fixed

  • Native tool calling for Claude-family models (opus / sonnet / fable).
    Requests that declared tools were being rejected upstream (internal error occurred / content policy) for Claude-family selectors while gpt/swe worked
    with byte-identical encoding. Five independent causes, all fixed:
    • Competitor-identity content-policy block. The client system prompt's
      self-identification (You are Claude Code… / …Claude Agent SDK), the
      x-anthropic-billing-header line, and the interactive Environment "brand
      block" (Claude Code product blurb + Claude model-ID catalogue) tripped the
      upstream content policy. neutralizeClientIdentity now rewrites all of
      these to a generic assistant identity. It moved to a standalone
      handlers/identity-neutralize.js so the DEVIN_CONNECT egress can apply it
      without a circular import — neutralization now also covers Codex
      /v1/responses and direct /v1/chat/completions, not just /v1/messages.
    • permission_denied misclassified as a dead token. A content-policy
      rejection used to be read as a dead session token, benching a perfectly
      healthy account and cascading the pool to "all accounts exhausted". It is
      now classified CONTENT_BLOCKED400 invalid_request_error with no
      account penalty.
    • Empty/absent system prompt with tools. The upstream rejects a
      Claude-family request that declares tools but carries an empty system
      prompt. A minimal system prompt is injected when tools are present and no
      system text was supplied.
    • Over-long tool descriptions. A long tool description pushes the request
      past an upstream content threshold. Tool descriptions are capped
      (WINDSURFAPI_TOOL_DESC_MAX, default 500) — a model hint only, never the
      tool name or schema.
  • Circuit-breaker help tooltip clipping. The "熔断与限流" section's ?
    tooltip grew leftward off a left-edge icon and was clipped by the section's
    overflow:hidden. It now drops down-and-right and stays fully inside the card.
  • Config files written 0600. writeJsonAtomic now creates config files
    (accounts, runtime-config, credential stores — which can carry the runtime API
    key, dashboard password hash, and upstream tokens) as owner-only, so they are
    not world-readable on a shared host.

Added

  • 429-lockout mitigation for small account pools. A single throttled
    account could black out the whole pool (hard filter → empty → 429 → the
    client's auto-retry re-extends the cooldown). Modelled on KiroStudio's
    cooldown design:
    • Tier-aware last-account exemption. A pro account whose only healthy peer
      is free is still treated as "last usable" for paid selectors, so it is not
      quarantined into a pool-wide blackout.
    • Degraded-serve fallback (opt-in, WINDSURFAPI_DEGRADED_SERVE). When the
      whole entitled pool is transiently throttled, serve the least-cooled account
      instead of returning 429. Default off = byte-identical to prior behaviour.
    • Client-replay backoff clamp. The advertised Retry-After on a 429 is
      clamped to [floor, ceil] (WINDSURFAPI_RL_CLIENT_BACKOFF_FLOOR_MS /
      _CEIL_MS) so an agent client's auto-retry backs off usefully instead of
      hot-looping. Default floor 0 = unchanged.
    • Shorter internal-error quarantine. Default cut from 5 min to 2 min — an
      upstream internal error is transient and self-heals in seconds.
    • Tunable bare-429 cooldown (WINDSURFAPI_RL_BURST_MS, default 5 min).
  • Full-chain request tracing (WINDSURFAPI_TRACE=1, default off). Stitches
    client request → routing decision → raw Devin wire bytes → client response
    under one trace id for offline debugging / RE. tools/model-probe.mjs fires
    one prompt at N models; tools/trace-view.mjs inspects and hexdumps a trace.
    Never a served endpoint; secrets redacted.

Changed

  • Dashboard pool health defaults to the calm StatusBars view instead of the
    glowing GlowGrid. The grid is still one click away (choice is persisted), and
    its glow was toned down (narrower breathe, slower cadence, a smooth ember
    instead of a fast stepped flicker) so opt-in grid is pleasant too.

Notes

  • All new tunables resolve env → runtime override → historical default, and
    every default is byte-identical to prior behaviour — upgrading changes nothing
    until you opt in.
  • Verified: full test suite green (2488), i18n check green, dashboard syntax
    green; UI fixes verified via CDP DOM inspection.

v3.0.2

Choose a tag to compare

@github-actions github-actions released this 09 Jul 01:55

v3.0.2 — Foolproof deploy, public onboarding, global settings

2026-07-09 (UTC+9)

A feature + hardening release focused on making WindsurfAPI easy and safe for
anyone to deploy and operate, with a much stronger account-onboarding surface.

Added

  • Global Settings page. New dashboard panel for shared preferences and
    security thresholds (stored server-side, shared across browsers). Adjust the
    email / dashboard-IP lockout thresholds and durations, or disable a lockout
    entirely by setting its threshold to 0.
  • Public remote onboarding. Add accounts from a public deployment, not just
    loopback: email/password login can optionally encrypt-and-store the password
    for auto-relogin (gated by DEVIN_CONNECT_ALLOW_REMOTE_CRED_STORE=1 + an
    explicit per-request opt-in), plus an in-memory OAuth session flow
    (/oauth/start · /oauth/callback · /oauth/status) that lets you finish
    Google/GitHub/Devin sign-in in your own browser and paste the callback URL
    back — no localhost callback needed.
  • OAuth login chooser. Clicking Google/GitHub offers "open login page" vs
    "copy login URL", with a "don't ask again" preference (re-enable it from
    Settings).

Fixed

  • Deploy foolproofing (both Docker and bare source).
    • DEVIN_CONNECT / DEVIN_ONLY deployments skip the language-server startup
      and its auto-install entirely — no more downloading a ~100 MB binary a
      binary-less Devin deploy never uses, and no spawn/ENOENT noise on boot.
    • docker compose up on a fresh clone no longer aborts on a missing .env
      (env_file is now optional); the bundled nginx LB sets TRUST_PROXY_* so
      per-caller lockout works behind it.
    • install-ls.sh runs under a timeout so a slow network can't hang boot;
      DATA_DIR mkdir failures are surfaced instead of silently swallowed.
  • Dashboard lockout false-positives. Opening the dashboard fired ~a dozen
    authenticated API calls with an empty password before you'd typed anything,
    and each empty-password 401 counted as a failed attempt — banning your IP
    the instant the page loaded. Empty-password preloads no longer count; only a
    submitted wrong password does. The frontend now halts polling and shows a
    countdown on 429 instead of stacking error toasts.
  • Lockouts are configurable and releasable. Setting a lockout threshold to
    0 now releases any active ban immediately (not just at natural expiry). A
    no-password / OAuth-only account no longer counts a wrong-method attempt
    toward the email lockout.
  • Account onboarding token routing. A devin-session-token$… pasted into
    the OAuth flow or smart-import is now added via the api-key path, not the
    Firebase-only RegisterUser path, so session-token onboarding succeeds.
  • Credential-store gate hardened for reverse proxies. The "store my
    password" gate now requires the request to come from a loopback peer (not
    just a loopback bind host), so a reverse proxy in front can't let a remote
    user bypass the operator opt-in.

Notes

  • No breaking API changes. Docker users: docker compose pull && docker compose up -d.
  • All new UI is fully bilingual (English + 简体中文). Plaintext passwords and
    tokens are never written to logs.

v3.0.1

Choose a tag to compare

@github-actions github-actions released this 08 Jul 23:25

v3.0.1 — Docker boot fix

2026-07-09 (UTC+9)

A patch release that fixes a critical startup bug in the v3.0.0 Docker image.

Fixed

  • Docker image failed to boot. Runtime code (src/devin-connect-models.js)
    loaded the Devin catalog snapshot from test/fixtures/, but the Docker image
    only ships src/ — so docker run / docker compose up crashed on startup
    with ENOENT: .../test/fixtures/devin-catalog-snapshot.json. The snapshot now
    ships under src/data/devin-catalog-snapshot.json, so the image boots cleanly.
    Source (systemd / node src/index.js) deployments were unaffected.

Notes

  • No config or API changes. If you run v3.0.0 via Docker, pull v3.0.1
    (or latest) and recreate the container:
    docker compose pull && docker compose up -d.
  • The catalog-drift test now reads the same shipped src/data/ snapshot, so the
    guard also protects the file that actually ships.

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 08 Jul 21:11

v3.0.0 — A New Chapter

2026-07-09 (UTC+9)

Today isn't just another release.

It marks the beginning of a new chapter for this project.

When I first started WindsurfAPI, the goal was surprisingly simple: I just wanted a stable solution for my own daily use. I never expected it to become an open-source project that so many people would actually use.

At that time, there were very few open-source projects exploring this direction publicly. Even while the project was still incomplete, it received far more stars, issues, discussions, and pull requests than I had imagined. That support became the motivation to continue improving it release after release.

Over the past months, the project has grown far beyond its original scope.

Today it is capable of:

  • Reverse proxying the latest Devin cloud models
  • Native Tool Call support
  • Vision / image understanding
  • Fable 5 coding and code reading
  • OpenAI / Anthropic / Gemini compatible APIs
  • Running entirely on Node.js with zero npm runtime dependencies

Keeping the project dependency-free has always been intentional. I wanted every line of code to be understandable, auditable, and easy to deploy without bringing in an entire ecosystem of packages.


Community

Thank you to everyone who opened issues, submitted pull requests, tested edge cases, and patiently reported bugs.

Some issues took me much longer to solve than they should have. Some answers were delayed. Some problems remained unresolved for longer than anyone wanted.

For that, I sincerely apologize.

Every report helped make the project better, even if I wasn't able to respond as quickly or as perfectly as I hoped.

Thank you for staying with this project.


What's New in v3.0.0

This release represents the largest architectural update since the project began.

Highlights include:

  • Major Devin Connect improvements
  • OAuth workflow refinements
  • Dashboard redesign and new management features
  • Stronger internationalization (i18n) system and validation
  • Improved model catalog synchronization
  • Better compatibility with OpenAI / Anthropic / Gemini clients
  • Expanded automated testing and release validation
  • Numerous internal optimizations and stability improvements

This version also introduces a much stronger localization audit, helping prevent untranslated UI text from slipping into future releases.


Demo

A real-world demonstration is available on Bilibili:

https://www.bilibili.com/video/BV1AfM56BE5t

The video isn't professionally produced, but it shows the project running in real-world scenarios, which I believe is more valuable than polished marketing.


About the Name

One funny problem remains.

The backend now talks to Devin.

The frontend still carries the history of Windsurf.

Both ecosystems coexist.

So... is this project called DevinAPI?

Or is it still WindsurfAPI?

Maybe one day I'll end up calling it WinDeSurfingAPI.

Whatever the name becomes in the future, the goal stays the same:

Build a simple, transparent, dependency-free AI gateway that anyone can understand, deploy, and improve.

Thank you for being part of this journey.

See you in the next release.