更新日志
Infinia(蜂语 / FengYu) 的所有重要变更。仓库中的 CHANGELOG.md 是唯一权威、始终最新的来源 —— 本页 由文档站点在每次构建时自动生成(见 docs/scripts/sync-changelog.mjs)。请勿直接编辑 本文件,请在根 CHANGELOG.md 中修改。
最新发布
v4.0.0 — 2026-09-26 · GitHub 发布
[4.0.0] — 2026-09-26
🐛 Fixed
- Official store connectivity follows the production domain-root API (4.1.0 backport). The default store base is now
https://www.infinia.fyi— production serves the store API at the domain root (/api/v1/...) and the former/storepath prefix no longer exists, so the old bootstrap base could not reach the live store. Catalog aggregation now follows the full cursor chain at the store's 100-row page cap (30 pages ≈ 3000 rows of headroom over the 1300+ live listings); the previous 5×60 bound left everything past row 300 invisible. The Settings copy and configuration docs follow the same base, and the dev-context store host-version fallback now reports4.0.0on this line. - Plugin screen capture is manifest-gated instead of silently unavailable. The security hardening that removed undeclared iframe media grants also removed
display-capture, so plugin UIs such as FY-QRSync failedgetDisplayMediabefore Electron's screen-only handler could run. A new canonicalscreen.capturemanifest permission now restores that capability only for plugins that declare it: the host addsallow="display-capture"to their iframe, while camera, microphone, window capture, and every undeclared plugin remain denied. - Windows portable updates now obey the signed-release gate. The custom portable replace pipeline no longer downloads, extracts, or arms its updater script in an unsigned build; it falls back to the manual download page before any feed lookup or consent dialog.
- Imported MCP servers require an informed enable step. Servers contributed by installed agent-content plugins can no longer be switched on from the summary row. FengYu shows the plugin source, exact command or URL, and imported credential names, requires explicit confirmation, and rejects imported private/loopback/plain-HTTP targets through the shared SSRF policy.
- Generated artifacts can only open passive document types. The chat-artifact Open action is now an allowlist: text/data, images, media, browser markup, PDF, and macro-free modern office documents open with the default handler; unknown or missing extensions, source code, shortcut/launcher formats (
.lnk/.url/.desktopand kin), macro-capable office formats, and archives reveal in the file manager instead of executing the command or program named inside. - The official store bootstrap base is consistent. The production default is
https://www.infinia.fyi/storein the application fallback, Settings copy, and configuration documentation, so thin/custom launches no longer target a different API path. - Outbound URL policy covers all IANA special-purpose IPv4 ranges. CGNAT, benchmarking, documentation, reserved, and broadcast addresses are now rejected by the default SSRF posture.
- Oversized skill and agent-content inputs are bounded. Installed
SKILL.mdbodies are capped at 1 MB, legacy oversized bodies are not retained in discovery snapshots, and third-party agent-content clones/skill materialization have byte and file-count budgets. - AI command permission rules are safer and match their documented grammar.
Effect(command)now really covers command calls, while interpreters, package runners, and common credential-file readers always fall back to human approval under broad command allows. - Browser full-page screenshots have raster-size limits. Hostile pages declaring enormous layouts are rejected before Chromium allocates a screenshot buffer.
♻️ Changed
- Application Java builds now use
--release 21. Building with a newer local JDK cannot silently link post-21 APIs into a Java 21 runtime. - CI gates every backend change and audits all JavaScript dependencies. Backend pushes/PRs run the full Maven suite and boot-level smoke outside release, frontend PRs run the production Vite build, and release audits include development-only packaging/runtime dependencies such as Electron.
- e2e-smoke polls for all four official plugins before snapshotting the runtime list. The Excel/Email listing assertions reused a snapshot captured as soon as Markdown/OfflinePython had registered, racing the async official seed pass — two of three local runs failed on a probe CI had never executed (the check landed after the last tagged run).
- The frontend Node spec suite terminates on every runner. Two middleware-mode Vite specs could leave
yarn testhanging until the job timed out: the dependency scanner died crawlingindex.htmlon CI, the dev-server warmup list leaked in and spawned a sass-embedded child thatvite.close()never reaped, and both servers raced each other for the default HMR websocket port. The specs are now hermetic (appType: 'custom', warmup cleared,ws: false) and the suite runs with a two-minute per-test timeout so any future hang fails fast instead of eating a job. - Backend CI and Plugin Tooling run green again. Backend CI installs bubblewrap and relaxes Ubuntu 24.04's AppArmor userns restriction before the Maven suite — without a native process sandbox the plugin-runtime tests hit the deliberate fail-closed path on every push. Plugin Tooling installs the frontend dependencies before typechecking
@infinia/plugin-ui, whose theme-equality spec resolvesvuetifytypes through the frontend's node_modules.
[4.0.0-rc.3] — 2026-09-20
✨ Added
- Conversation-scoped chat resources with send-time copies. Files, folders, and the output location belong to the conversation they were added to — never a global list. Selecting a file or folder now only adds a draft attachment: no grant, no copy, and no worker exists between the picker and the send. When you send, a server-side send transaction (
{sendId}) takes one host copy per attachment (the content as of that moment), the chat POST commits it idempotently — a network retry never duplicates the message, the copies, or the model call — and the turn's file access is derived from those copies at the execution boundary and released when the turn ends (a later "continue" re-derives it). The scope registry aggregates each committed resource into one record (one chip, no plugin ids) and resolves chat turns server-side from{scopeId, resourceIds}: cross-scope, revoked, or unknown ids are rejected at the gate, removal stops future turns immediately while a running turn drains safely, and refreshing a read-only file pins the old revision for live turns. Typed absolute paths enter the same registry at send time. Sent attachments persist with their messages (display metadata only — after a restart they show as history, never as live authorization). New chats start empty — a draft carrying text or attachments is never silently reused or reset, and delayed responses always land in the conversation that started them. - Host-side save closure for generated results (desktop). A per-conversation output folder authorizes the host to save there (worker writable roots never widen). At a turn's end, staging outputs are collected into a managed pending store (outside the runtime-files sweep, 7-day retention, capacity-capped) and saved into the target — same-name collisions keep both files, a failed save never deletes the generated file and stays retryable, and per-file results are reported individually. Result cards offer Open / Show in Finder (Show in folder) via a narrow IPC whose path resolution happens main-side against the backend registry; scripts and executables only ever reveal. Without an output folder the result waits as ready to save; the web keeps download-based saving. Pending artifacts survive restarts (metadata and content only — never the folder write authorization).
- Official-plugin seeding status.
GET /api/store/statusnow also reports the background install state of the bundled official plugins (officialSeedingDone,officialSeeding[]with per-plugininstalling|ready|failed|skipped), and the Infinia Store shows an installing hint while the first pass runs — a short plugin list during boot is now distinguishable from "nothing installed". - Backend startup phase timing. Every Spring startup step is recorded and the slowest (with bean names) is logged once ready —
Startup timing: N recorded steps, launcher-to-ready X ms— so lazy-initialization decisions target measured offenders instead of guesswork. - Plugin UI entry tickets. Navigating into a plugin UI now goes through a one-time, header-authenticated ticket endpoint, and ticketed assets are served with no-referrer and no-cache headers — a plugin iframe URL cannot be reused from history or leaked to third parties via
Referer. - Portable-style runtime tree on Windows desktop builds. The packaged Windows shell now anchors the
.fengyuruntime tree (config, embedded database, logs, plugins, skills, chat data) to the executable's directory — the NSIS install root or the portable ZIP's extract folder — instead of%APPDATA%\fengyu-desktop. A legacy%APPDATA%tree is migrated automatically on the first writable launch (same-volume atomic rename; cross-volume installs copy through a.fengyu.migratingstaging sibling that is renamed into place, so an interrupted copy never leaves a half-populated tree, and only the single-instance lock holder runs the move); an unwritable install directory (Program Files without elevation) keeps the previous userData anchor, and the legacy tree is never moved off it. macOS/Linux anchors are unchanged (userData), as is the UOS home-directory policy.
♻️ Changed
- The desktop window now loads while the backend boots. The main window is created as soon as the backend reports its port — before the health check — so the SPA load overlaps the JVM cold start + Spring init instead of running after it. A boot gate in the app holds the full shell behind its own health poll (skeleton at first paint, features enable when the backend answers, SETUP mode detected at the gate); a backend that dies during boot fails fast with the same dialog instead of parking the skeleton behind the 30 s health deadline.
- MCP servers connect in the background. Startup no longer waits on remote MCP handshakes (previously one shared 60 s wall-clock budget): enabled servers start as
connectingand their tools appear as each handshake lands — an unreachable external MCP service can no longer block application startup. - The persisted update channel is off the first-paint path. The
/api/settingsread that loads the channel now has a 2 s timeout and runs alongside the renderer; every update check (native and renderer) waits for it, so a slow settings response can no longer delay window creation. - Official plugins install in the background. The seeder no longer runs synchronously inside tool-registry initialization during the Spring refresh: first installs, upgrades, and same-version refreshes run on a background thread, each archive's SHA-256 is computed once (previously three full-file digests per archive), and the mandatory sidecar verification is retained. The tool registry picks each plugin up on its next snapshot as the install lands.
- The upgrade-channel override is gated on the self-hosted posture. The Settings 升级渠道 address only takes effect when the self-hosted posture allows private-network stores (
fengyu.store.allow-private-networkor the live toggle); with the posture off the saved address stays dormant and every surface falls back to the official store, mirror, or GitHub releases. - AI startup is lazy.
AiConfigPropertiesand the tool-callback compatibility beans are now lazy, JPA repository bootstrap is deferred, and MCP annotation scanning is disabled — the cold start does measurably less work before the port opens.
🐛 Fixed
- Windows legacy-tree migration actually moves the data (rc.3 review). The writability probe left its own empty
.fengyudirectory behind, and on Windows a rename onto an existing directory always fails — every upgrading user's migration then misread that failure as "already migrated", anchored the app to an empty tree (the database wizard reappeared) and orphaned the real data in%APPDATA%forever. The probe now cleans up after itself, an empty leftover root is cleared instead of mistaken for data, a rename failure with the legacy tree still present is a safe no-op (userData anchor, tree intact), and the cross-volume copy runs through a staging sibling so a crash mid-copy retries from the intact legacy tree on the next launch. Only the instance holding the single-instance lock migrates. - AI chat: switching conversations during a send no longer wipes its history (rc.3 review). While a send was still preparing (history preload, scope, attachment uploads), switching away unloaded the sending conversation's turns; the follow-up save then replaced the server-side history with just the new exchange. The send now owns the streaming slot from its first statement, a stale send's exit can no longer release a newer send's busy slot (which allowed two stacked streams with the first unstoppable),
stop()during the request window no longer snapshot-saves the empty exchange as a duplicate message, and a persisted-but-unloaded conversation can never be PUT with an empty message list. - Store rollback retries never destroy an already-restored skill (rc.3 review). When a retained transaction journal was retried, a skill item whose backup had already been moved into place by the earlier attempt was uninstalled again — deleting the restored previous version with no backup left. A missing backup on an applied skill update now means "already restored" and the item is skipped.
- UOS menu launches start Electron with
--no-sandboxon the real command line. The UOS build's launch entries now carry the switch themselves (linux.executableArgswrites it into the deb's/usr/share/applications/infinia-uos.desktopmenu shortcut and pins the AppImage's embedded desktop entry) instead of relying solely on the main-processappendSwitch, which is unreliable on UOS where Chromium reads sandbox decisions from the process argv. Upgrading the deb overwrites the arg-less shortcut installed by earlier builds, and a postinst (scripts/uos-deb-postinstall.sh) refreshes the desktop database so menus serve the new entry without a relogin; the JS-side switch stays as a fallback for launches that bypass a desktop entry. - The "approve send to all compatible plugins" confirmation card is gone — a selection is a read-only draft attachment; authorization happens only at send time.
- A chat turn's terminal now settles its resources before the
doneSSE event is emitted, so artifact listing can never race the file work. - Abandoned scoped POSTs no longer leak execution leases (swept with the pending turns); idle scopes are closed after 24 h and their grants reclaimed.
- First install enters the app after the database wizard. The renderer boot gate stayed closed forever after the SETUP→APP restart — the gate flag was never set when the boot probe redirected to the wizard, so completing the wizard landed on the boot skeleton instead of the main shell until a manual reload. The wizard's restart wait also now matches the desktop shell's 2-minute boot patience (previously 30 s, which reported a false timeout on slow hardware while the backend was still legitimately starting) and offers a reload button if the wait ever expires.
- Slow hardware can finish booting. The spawn-side
FENGYU_PORTwait and the/api/healthpoll are raised from 30 s to 120 s (a UOS field boot measured 37.5 s just to the port line, after which the old deadline SIGTERM'd a still-booting JVM); crash paths still fail fast, so the longer deadline only tolerates alive-but-slow boots. - Unsigned builds refuse native self-update in the renderer flow too. The renderer-driven "update now" now enforces the same signed-release gate as the startup check before any download or install, falling back to the manual download page before the consent dialog is shown — over plain HTTP a store MITM can rewrite an artifact and its checksum together, which the consent dialog cannot carry.
- 4.0.0-rc.2 production-review findings (P1/P2). The H2 TCP server no longer allows database creation over its loopback endpoint (
-ifNotExistsremoved — a loopback caller must not mint a database and run Java aliases in the host JVM); failed artifact registration keeps the staging tree with aFAILEDmarker and the rescue path instead of destroying the only copy; a store rollback failure retains the install journal and backups and retries recovery under the transaction lock; self-update follows bounded, validated redirects and refuses https→http downgrades; workflow webhooks verify the secret before reading any body bytes with the payload cap enforced on the stream; the Windows portable replace script treats a still-failing robocopy retry (exit ≥ 8) as an explicit FAILED state that keeps staging, logs, and the app tree for manual recovery and never relaunches a mixed tree; the email plugin leases in-flight sends against the staleSENDINGsweep and reconciles a misfired reclaim to the send's true terminal state; the offlinepython pip path passes the full extracted wheel directory (the basename form always exited 1); chat saves serialize per conversation, block turn unloading while a save is unsaved, and claim the busy slot before the firstawait, killing double-submit races.
[4.0.0-rc.2] — 2026-09-14
✨ Added
- Fresh installs now reach the production Infinia Store out of the box. The bootstrap store default is
https://www.infinia.fyi(HTTPS, mandatory hash and Ed25519 checks unchanged) instead of a loopback dev address, so catalog browsing, plugin installs, cloud-account sign-in, and store-served app updates work with no first-run configuration. Local development pointsFENGYU_STORE_API_BASEback at its loopback store, which the URL policy still exempts. - Production store download trust. Bundle the existing Infinia Store Ed25519 public key obtained through Jenkins, retaining mandatory hash/signature checks and operator revocation through the runtime trust overlay.
- Scheduled tasks in the main application sidebar. Create schedules for published workflows with daily clock times, selected weekdays, monthly dates or the last day, and an explicit time zone. Calendar schedules persist until cancelled, handle short months and daylight-saving changes, and coexist with fixed intervals and one-shot delays. Workflow JSON inputs are under Advanced settings; an immediate first run is optional. Inspect next-run/expiry times, missed intervals and submission errors, open the workflow's runs, or delete a schedule. Reject delayed schedules whose first fire would be at or after their seven-day expiry.
- Desktop sessions survive restarts again — via the store's rotating per-install credential, not a client secret. The store registers
fengyu-desktopas a public OAuth client (no refresh tokens from the authorization server), so long-lived sign-in now rides the store-managed credential: right after the PKCE code exchange the host requests one (POST /api/v1/auth/desktop-session) and refreshes withPOST /api/v1/auth/refresh— the credential itself is the only authenticator, so there is no client-secret pairing that a store upgrade could break (the confidential/clientless registration flip-flop of Sep 3 locked every desktop client out in both directions). The credential is single-use and rotated on every refresh; a replay anywhere revokes the whole family and the session, and the host's dead-session self-heal turns that into a graceful fallback to the local account with a working re-login button. Confidential pairings (FENGYU_STORE_CLIENT_SECRET) keep the authorization-server refresh grant and RFC 7009 revocation exactly as before. - Refresh credentials are only persisted over secure channels. A new
StoreEndpointProvider.secureTransport()gate (HTTPS, or a loopback dev store — mirroring the URL policy's loopback exemption) decides where the refresh token may rest: the OS credential store on secure channels, memory-only otherwise. Signing into a plain-HTTP LAN/cross-site store therefore stays a this-session login by design — nothing long-lived is written where every network observer could read it — and mid-session rotation keeps working from memory. - The store wire contract is pinned by fixtures and real-HTTP contract tests. Canonical store responses live under
FengYu/src/test/resources/store-fixtures/infinia-store/(catalog, listing, resolution, download-ticket, profile, library, sessions, devices, organizations);HttpStoreAccountGatewayTestdrives the user-center gateway against a loopback HTTP server (method/path/bearer/body shapes, error mapping), andStoreApiFixtureContractTestparses the store-plane fixtures throughStoreClient's DTOs so shape drift fails the build.HttpStoreAuthGatewayTestgrew the public-client and refresh-grant request forms.scripts/e2e-smoke.shnow boots a loopback store stub (scripts/fixtures/store-stub/, including the Windows-portable compat-mirror fixture) and asserts the store chains: the shaded-jar config-loading gate, anonymous catalog browsing, signed-out account degradation, and a clean 5xx JSON error with the app still healthy after the store goes offline. - App updates now come from the store — it replaces the FY-Proxy update proxy. The Windows portable self-updater targets the store's compat mirror (
/api/v1/compat/fengyu/fengyu-releases/api/releases/latest, a GitHub-releases-compatible object from an APP listing's stable release with the mandatory SHA-256 digest) instead of the FY-Proxy path, the manual download page becomes the store's web app (/web), and the backend portable-web check keeps rejecting the channel with store-era wording. The lite deb feed keeps the legacy FY-Proxy contract until the store ships an electron-updater feed. - The Settings upgrade channel is now the single runtime route to the production store. Production deploys the Infinia Store separately from the app, so the store base URL now resolves per request through
StoreEndpointProvider: the existing 升级渠道 setting (updateApiBase) overrides thefengyu.store.api-basebootstrap property without a restart, and plugin installs/updates, cloud-account sign-in (OAuth + user center), the store status endpoint, and app-update checks all communicate through that one channel. Every resolution re-runs the SSRF policy — a channel may not point into a private network unlessfengyu.store.allow-private-networkis set — and the Settings copy now documents the channel's double role. - The account page is now a user center mirroring the store platform's. After signing in to the Infinia cloud account,
/accountaggregates identity with the Infinia Level badge and next-level hint, role badges, role-aware quick links (plugin store, online user center, publisher/admin consoles for privileged roles), profile renaming, a library summary (favorites / entitlements / install counts plus top favorites), organization membership, and account security — password change, active-session list with revoke, and registered devices with revoke. Signed out, the page keeps a local-account card that starts the browser OAuth flow and surfaces in-page errors, with a skeleton while loading. New backend endpoints proxy the live store data over the cloud account's access token:GET /api/account/store-profile(addsbeeLevel/createdAt),PUT /api/account/profile,PUT /api/account/password, and/api/account/{library, organizations, sessions, devices}with per-item DELETE; all answer 401 when signed out, and a display-name rename syncs the local binding so/api/account/mefollows. - Desktop browser and Computer Use controls cover more real interaction paths. Browser automation can now move backward/forward or reload, activate hover-only controls, send bounded CDP wheel input to the page or a nested scroller, select verified native
<select>options, and press keys against the active page without inventing a selector. History changes invalidate stale element refs. Computer Use can run an ordered, boundedcomputer_key_sequencein one approved call for keyboard-driven navigation.
🐛 Fixed
The skill marketplace works at the production store's catalog scale. The dev-era guards (2 MB / 2,000 entries, 20 s fetch) rejected the real Infinia Store compat catalog on first contact — it served ~8 MB and 8,200+ aggregated entries in Sep 2026, and the transfer alone outran the fetch timeout on a slow link — so every marketplace view errored once
fengyu.skills.catalog-urlpointed at the store. Caps now carry production headroom (32 MB / 20,000 entries, 90 s fetch) while staying hostile-input bounds; the catalog is served from a 5-minute single-flight cache so views and installs stop re-downloading the whole catalog per call; a store hiccup degrades to the last known good catalog for up to 24 h instead of an error page (installs stay safe — the downloaded.fysis always hash- and signature-verified); and installing a brand-new id forces exactly one catalog refresh instead of waiting out the TTL. An over-cap catalog now surfaces its real "exceeds N bytes" reason instead of a generic read failure. The download trust chain is unchanged: mandatory SHA-256, platform Ed25519 signature from the anchored registry.fengyu.store.require-signature=falseworks again as the documented relaxed posture. Both download paths (native store tickets and skill-marketplace entries) resolved the platform key whenever the payload carried akeyId— regardless of the flag — so a store signing with an unprovisioned anchor aborted every download even with signature verification explicitly disabled; the flag only governed the "missing signature" refusal. Key resolution is now gated by the posture flag: verification runs exactly whenrequire-signatureis on, and the relaxed mode rides the (always mandatory) SHA-256 attested digest instead of failing the key lookup.Native store installs work against a real Infinia Store again. The store's resolution plan carries versioned coordinates (
infinia://skill/ns/slug@1.0.1) while the request and the install ledger use the bare form; the host's exact-match comparison never matched the root item, so every install died with a 500 (NoSuchElementExceptionfrom the empty transaction journal) before downloading anything. Coordinate comparisons inStoreService(plan execution order, journal items, root/plan-item lookup) now normalize away the@versionsuffix, and the ledger keeps binding bare coordinates so updates and uninstalls keep matching. Verified end-to-end against a live store: resolve → ticketed download → SHA-256 + Ed25519 verification → install → installed-state merge → update check → uninstall → signed-in install telemetry landing in the store's install history.Untrusted/empty store trust registries now fail with actionable guidance: the error names the
keyIdand tells the operator to provision the store's platform public key viatrusted-store-keys.json(bundled or runtime-root overlay) — instead of a bare "not trusted" string.Portable web packaging now accepts absolute output directories and resolves relative output paths from the caller’s working directory.
Correct the store trust-file example:
publicKeyis Base64-encoded X.509 DER, not PEM. Add a live StoreClient interoperability probe for signed Skill/MCP/Plugin downloads.Flow rejects provably incompatible whole-value references and invalid pinned results before any node executes. Diagnostics identify the step and input path; text interpolation and unknown or overlapping schema types retain runtime validation. Regression tests verify that rejected plans execute no earlier tools. The release workflow now also gates packaging on the frontend Vitest suite, including Flow compilation, templates, history, and draft recovery.
Sign-in diagnoses a dead store instead of spinning for five minutes. A browser OAuth flow against an unreachable store left the attempt PENDING for the whole attempt window with nothing visible. The host now TCP-probes the store channel (2 s budget) before starting the flow and fails immediately with an error naming the channel, and the sign-in attempt logs each stage (callback received → code exchanged → profile fetched → session credential issued → binding saved) so a wedged stage is visible in the backend log.
Windows sign-out / user center no longer hang on the OS credential store (P0). The Windows backend reads and deletes the refresh credential through a PowerShell helper, and the process handling drained stdout before waiting — but never drained stderr. PowerShell output (the helper's per-call
Add-Typecompilation banners, antivirus chatter) fills the unread stderr pipe's OS buffer, blocks the child forever, and the sequential stdout read then never sees EOF — the 8-second timeout never fired. The wedged helper held the refresh lock, so every user-center request queued behind it (skeleton forever) and the sign-out request — which also reads the credential store — hung with it. Both pipes now drain concurrently with the watchdog as the only bound, and the refresh lock switched to a bounded tryLock: even a pathological helper degrades store access to anonymous instead of hanging the user center. Regression-tested with real misbehaving child processes (a ~1MB stderr flood completes; a never-exiting child is killed at the deadline).A dead cloud session can no longer lock the user center (P0: no sign-out, no local account after restart). The store registers
fengyu-desktopas a public OAuth client, which issues no refresh token — so once the 30-minute access token expires or the app restarts, the session can never re-authenticate, yet the persisted binding kept/api/account/mereporting the cloud user forever while every user-center proxy call answered 401. The account page rendered only a Retry card (the sole sign-out button lives in the loaded-profile branch), so the user was locked out of their own account state. Three layers now compose: a binding whose session can never authenticate again (no stored refresh token, or a definitive refresh rejection — including the public-client store's login-page HTTP 302 bounce, which the rejection matcher previously missed, so a leftover confidential-era token poisoned every call forever) is dropped on the first store call and the local account takes over; the account page's error card gains 重新登录 / 退出登录 actions so a signed-in user can always escape, and on a 401 it re-reads/api/account/meso the shell flips back to the local view; store gateway error messages truncate the response body (a redirect's HTML login page no longer swamps the error card). Transport failures still keep the binding (the store may just be unreachable).A remote self-hosted store is now reachable for sign-in and downloads. The outbound URL policy rejected every plain-HTTP store URL outside loopback with no escape hatch — and a cross-site/intranet store deployment almost never carries a CA-signed certificate, so pointing the upgrade channel at it made
StoreClientfail to construct (boot failure) or every request answer a policy error.fengyu.store.allow-private-network=truenow permits plain HTTP as well as private-network targets (the default posture is unchanged: HTTPS everywhere except loopback, private networks still rejected), a policy-blocked base no longer kills the boot (it warns; the authoritative check runs per request), and the posture became a live setting: Settings → Update channel → "Allow private network" re-runs the policy on the very next store call, no restart. Pinning the store's externally reachablestore.base-urlis also required on the store side — the browser sign-in redirect targets it, and the oldlocalhost:8080default sent the user's browser to their own machine.The shaded fat jar now actually loads
application.yml. maven-shade'sAppendingTransformerconcatenatesMETA-INF/spring.factorieswhole-file, and Properties semantics keep only the last same-key block: spring-boot-autoconfigure'sApplicationListenerentry silently overwrote spring-boot's core listeners, soEnvironmentPostProcessorApplicationListener(which drives ConfigData) andLoggingApplicationListenernever loaded and the shipped jar ran on annotation defaults alone — everyfengyu.store.*, JPA, Flyway-baselining and actuator setting in application.yml was dead and/actuator/*answered 404 (this shipped in rc.1). A new build-timeSpringFactoriesUnionstep (exec-maven-plugin, right after shade) rewrites the entry as a per-key comma union across all dependency jars, and line-unions every collidingMETA-INF/spring/*.importspath (ManagementContextConfiguration.importshad three sources,AutoConfiguration.importsnineteen — which is also why actuator endpoints were dead).scripts/e2e-smoke.shnow gates on this:/actuator/metricsmust answer 200, which only happens when application.yml loaded.The desktop OAuth client now ships secret-less (RFC 8252 §8.5). A secret baked into a distributed desktop build is public knowledge, not a credential, so
fengyu.store.client-secretdefaults to empty — the pure public PKCE form — replacing the interim dev-default pairing with the store's confidentialfengyu-desktopregistration. Deployments whose store still registers the client as confidential opt in explicitly viaFENGYU_STORE_CLIENT_SECRET; token and revocation requests then send it asclient_secret_poston top of the always-mandatory PKCE verifier. Aninvalid_clientrejection keeps surfacing with a hint naming that setting. Long-term login for the public form is a store-side mechanism (per-install credentials or a BFF), not a shipped secret.Approval gates are now credential-checked end to end. The approval-request SSE events carry a
gateId, the frontend sends it with every approve, and the backend answers 409 on duplicate, late, or stale credentials — the client silently refreshes run state on 409 instead of surfacing a conflict. Step results that were size-capped by the backend are markedresultTruncatedin the run panel.The frontend wires the audit's platform disclosures. Install confirmations state when declared plugin permissions are not enforced by the operating system on this platform (
permissionsOsEnforced: false, from the catalog, inspect, and install responses); the scheduled-tasks form selects an explicit permission mode, so the ask-for-approval rejection for uncovered non-read tools surfaces as guidance instead of a surprise.CORS now allows PATCH. The plugin-store and skill enable toggles issue PATCH requests; from the desktop webview's
app://shellorigin every PATCH preflight was rejected. Pinned by a preflight test.A burst of build/test defects from the audit wave (never compiled or run by its agents) is fixed, and the whole verification chain is green again: backend 1152 tests, frontend 169, desktop 252, e2e-smoke. Notable production fixes among them:
StoreClient.ticketcrashed onMap.ofwith null parameters (the legacy one-arg call and null-artifact tickets);PluginProcessManagerteardown could deadlock on a stuck stdin writer (the writer thread is now interrupted before close); the eagerUnattendedTriggerPolicyconstructor broke context boot order (now lazy); MCP servers that died mid-call on stdio now invalidate and rebuild instead of zombie-timing-out on every subsequent call;AgentContentInstallerhas two constructors without an@Autowiredmarker; IPv6[::1]origins are accepted by the token-exempt asset gate; sensitive-directory write-grant refusals resolve symlinked homes (macOS/var→/private/var); staletoolchain/sdk-ts/dist/src/build artifacts are removed from git.
[4.0.0-rc.1] — 2026-09-01
✨ Added
- Flow AI authoring is now a reviewable RC workflow. The docked Flow chat can inspect the live canvas and installed tool contracts, diagnose invalid arguments, unavailable tools, references, dangling edges, cycles, and the last run error, and generate complete Flow graphs from an empty canvas or edit an existing one. AI edits are non-mutating proposals: the builder shows node/connection changes, rejects stale snapshots, and runs the normal canvas validation, optimistic revision check, and save path only after the user chooses Apply and save.
- The store platform ships with a closed trust chain. Every store and remote-skill download must carry an attested SHA-256 and a platform Ed25519 signature from a key in the new
trusted-store-keys.jsonregistry (bundled + user-provided, with revocation), verified over the exact bytes while streaming through a size budget. Store, catalog, and CDN URLs must be HTTPS — plain HTTP only on loopback — and hosts resolving into private/link-local networks are blocked (shared SSRF policy for the store client and the skill marketplace), with bounded JSON responses everywhere. - Cloud sign-in is now an OAuth 2.1 public client. The desktop app ships no client secret: authorization uses PKCE, the browser redirect lands on a one-time ephemeral loopback port (RFC 8252), and the callback renders a branded, localized result page. The access token lives only in memory with serialized refresh (server-side rotation is persisted exactly once); the refresh token only in the OS credential store — macOS Keychain, Windows Credential Manager, or Linux Secret Service — and a Flyway V2 migration removes the legacy token columns from the database.
/api/plugin-marketlives on as a documented compatibility layer. Its local-package lifecycle endpoints forward 1:1 to the new/api/plugin-packagessurface with deprecation headers; the catalog endpoints superseded by the unified store answer410 Gonenaming their replacement. The REST reference now documents the plugin-packages, unified store, Infinia Store, skills, and account surfaces in both languages.
🐛 Fixed
- Windows desktop builds no longer white-screen on startup. The release CSP hash for the inline Vue import map was computed over the raw HTML bytes, so the Windows build runner's CRLF checkout produced a token Chromium refuses (it normalizes inline scripts to LF before hashing); the import map was blocked,
vuecould not resolve, and the window stayed blank. The hash is now computed over LF-normalized content, and the desktop packaging gate (verify:frontend-dist) re-checks every inline script's CSP token instead of only verifying relative asset paths. - Store installs run the complete dependency plan as one journaled transaction. The resolver plan executes dependency-first with per-artifact verification; the ledger binds all coordinates in a single save, plugin rollback snapshots release only after every installer, health preflight, and the ledger commit succeeded, and a failure — or a crash — rolls applied items back in reverse order on next startup. Store installs/updates/uninstalls of plugins now go through the same runtime gate as local uploads (worker stop, health preflight, commit/rollback), via one shared lifecycle orchestrator.
- Update checks follow SemVer precedence.
4.0.0-beta.5users now correctly seerc.1and then4.0.0(previously all prereleases of one version compared equal), numeric prerelease identifiers order correctly (beta.10>beta.2), and build metadata never marks an update. - Remote skills can no longer spoof official identity or override builtin guidance. The official badge displays only when an entry's signing key verifies; a package that claims the official identity outside the verified marketplace/seeder path is rejected at install, and builtin skill ids cannot be shadowed by installed packages.
- A damaged store ledger no longer blocks startup. An unreadable
installs.jsonis quarantined as a timestamped.corruptfile and the store continues with an empty ledger. - macOS desktop traffic lights align with the renderer-owned 48 px window bar. The frameless window now keeps Electron's hidden-title-bar button proxy active, restores the native controls, and applies their custom position after the visibility update so the traffic lights, sidebar toggle, and route toolbar share one centerline.
- The store sources panel's enable/disable switch now labels the action consistently with its tooltip.
[4.0.0-beta.5] — 2026-08-24
✨ Added
- Flow authoring now treats upstream inputs as first-class data. The variable picker separates every upstream node's effective Input from its worker Output, and the runner resolves
\{\{steps.N.input.path\}\}without synthetic manifest outputs while filtering sensitive fields. The canvas also restores debounced local drafts, protects direct-route navigation, fits templates reliably at laptop widths, keeps editing rails mutually exclusive, and warns when disconnected roots will run in parallel. Restart-interrupted runs enter a reviewable recovery checkpoint with stable per-step invocation IDs; expired session file grants are explicitly non-resumable. - Java, Python, and Go plugin scaffolds are code-first by default. A short
manifest.base.jsonplus a language-owned contract generates the RPC methods, AI tools, final manifest, typed client, and method constants.flowNodesis now a UI-only delta over the RPC schema, and locale files use compact display-only node/port deltas; CLI checks and host install reject schema drift and stale localized keys. - Dynamic tool loading (pi's
setActiveToolspattern). When the visible tool catalog exceeds the newai.tool_loading_thresholdsetting (default 25;ai.tool_loading_mode= auto/always/off), the chat loops stop resending every tool schema on every round: a small cheap core stays attached, the rest is advertised by name in a new system-prompt catalog ("Available tools (on-demand activation)" — untrusted-data framed, MCP server tagged) and activated on demand through a new built-insearch_toolstool. Activation is additive-only and capped (40/conversation); the activation set re-seeds on follow-up turns from a machine-readable marker mirrored into chat history, so stateless replay needs no server-side session. Hallucinated calls to inactive tools now get an actionable "activate via search_tools" tool result instead of the turn-killing exception Spring AI's ToolCallingManager raises for unresolvable names; compaction overhead accounting follows the actually-attached set. The plan-and-execute agent applies the same gate as two-phase planning: above the threshold, a schema-less selection call picksselectedToolsfirst and the plan is authored against only those schemas, with a bounded fallback to the classic full-schema call. Settings → AI exposes the mode and threshold; at or below the threshold behaviour is byte-for-byte unchanged. - MCP management rebuilt around per-server control (patterns adopted from cherry-studio, Codex, and deepseek-harness). Dynamic MCP tools are now namespaced per server (
<server>__<tool>), so permission rules can target one server and two servers can expose the same tool name without colliding — previously every dynamic server's tools shared a single prefix and silently shadowed each other. Each server gains per-tool enable/disable (bare name, wire name, orprefix*/*wildcards), configurable request and initialization timeouts (5–600 s, default 30), and a hardened STDIO environment: interpreter-injection keys (NODE_OPTIONS,LD_PRELOAD,LD_LIBRARY_PATH,DYLD_*, JVM variants) are stripped from saved and imported configs. The AI-facing tool catalog is now a cached snapshot — reading it never performs a live MCP round trip, so a dead or slow server can no longer stall chat startup or the flow tool list. - Plugin-declared MCP servers are wired up. Claude/Codex/Grok plugins that declare
mcpServersused to write config files that nothing read. They now surface in Settings → MCP as disabled servers tagged with their plugin origin, are testable on demand, and get adopted into the user-managed registry on first enable (adopted servers survive a plugin uninstall). The store refreshes the import after every install/update/uninstall. - Conversation compaction upgraded to the algorithm pi, grok-cli and deepseek-harness converged on. Summaries follow a fixed structured template (Goal / Constraints / Progress / Key decisions / Next steps / Critical context), tool results are truncated to 2 000 characters in summarizer input, a summarizer failure retries once with the more recent half before failing open, and when even the kept tail overflows the window, recent rounds are traded away down to a floor of two instead of shipping a history the provider will reject. Cuts remain at user-turn boundaries only — a tool call and its results are never separated.
- Token accounting uses provider-reported usage. Chat turns now report the sum of provider completion-token counts across all tool rounds — falling back to the old text-length estimate only when a stream carries no usage — plus a measured tokens-per-second figure.
- Plugin platform hardening and Java/Python/Go Workers. The host and Toolchain 2 now implement the full package-to-runtime trust chain:
- Strict SemVer plus
engines.fengyucompatibility, digest-pinned single-download installs, whole-package integrity records, conventional runtime artifacts, and protocol-v1 startup handshakes. Java remains compatible; new Python 3.12+ and Go 1.26+ SDKs/scaffolds speak the same cancellation, locale, structured-log, and JSON-RPC contracts. - Ed25519 catalog signatures with publisher namespace authorization, bundled/user trust roots, key/package revocation, and
fengyu signsidecars. Official status no longer bypasses remote verification. - Health-gated transactional updates retain a rollback snapshot, reject unconfirmed permission escalation, restore the previous package after failed spawn/handshake, and recover interrupted swaps on startup.
- Runtime observability exposes STOPPED/STARTING/HEALTHY/DEGRADED/BACKOFF/FAILED/UPDATING/ DISABLED states and structured fault categories. Rapid crashes use bounded exponential backoff; manifests can cap worker-tree memory and process count, enforced by POSIX monitoring or Windows Job Object limits.
- Strict SemVer plus
- Flow-builder interaction overhaul — typed, self-explaining nodes (the deliverable of the flow-canvas interaction design). Nodes stop being opaque forms:
- Three-state source control per input (manual / reference / expression). Reference opens a recursive variable tree — workflow inputs plus every upstream node's outputs (nested object fields, array
[0]sample children) — filtered by the input's expected type, with copy-path buttons and drag-to-bind onto any input. Bound values render as readable chips ("node · field") and auto-create the canvas edge. Expression keeps the raw-template escape hatch, flagging unknown references inline before save. - Descriptor v2.
flowNodesdeclarations gain a type system (string/number/boolean/object/array/file/any), nested outputproperties/items,examples, per-fieldhelp/placeholder/required, and node-levelhelp— all optional, fully backward compatible (v1 declarations behave exactly as before). Output handles are colored by type and their tooltips show type + description + example. Schema updated intoolchain/spec/manifest.schema.json(+ synced CLI copy) with a newflow-node.schema.jsonvalidating the host'sbuiltin.json; the excel/email official plugins andjson_formatnow declare v2 metadata. - Data visibility. The node panel gains an upstream-data preview (declared fields → examples → actual last-run values resolved per field path) and an output viewer with the same degradation. Reference paths validate at save time (
unknownNodeReferences) — the "no such output field" error moves from run time to authoring time. The reference grammar now supports array indexes (\{\{node.n.result.files[0].name\}\}) end to end, runner included. - Pinned results. A completed step's real output can be pinned; later runs serve the pinned value without executing that tool (
AgentStep.pinnedResult, capped at 64 KB), so downstream debugging never re-runs an expensive or destructive upstream step. - Start node. New flows open with a Start node — the visual editor for run-time inputs (name/label/type/required/options/example) that replaces hand-written JSON Schema for ordinary users and renders the inputs as typed rows on the canvas. The JSON view remains in the settings drawer for power users.
- Canvas legibility. Live run badges on node cards (running / done / failed), custom node titles (rename any node; every picker and chip uses the title),
Nopens the node palette focused, and a green coach note seeds brand-new flows. The palette lists explicitly declared nodes first and reveals every orchestrable tool behind a "show all tools" toggle — undeclared tools are no longer invisible to flow authors. - Node completion checks now union the tool schema's
requiredlist with descriptor-v2requiredflags, and nodelastRunpreviews persist with the graph (excluded from the unsaved-changes guard so finishing a run never flags the flow dirty).
- Three-state source control per input (manual / reference / expression). Reference opens a recursive variable tree — workflow inputs plus every upstream node's outputs (nested object fields, array
- JSON Schema contracts are enforced at every trust boundary. A new
JsonSchemaContractValidatorbacks the generated manifest schemas end to end:PluginProcessManagervalidates worker input before dispatch and output after it,AiToolRegistryexposes the output schema throughAuditedToolCallbackand injectssessionIdonly when the schema declares it, and workflow compilation validates nested input contracts. Flow runs harden accordingly — pinned failure envelopes fail the step instead of masquerading as completed, template references are validated against declared output paths at plan time, and a skipped producer cascades through implicit template dependencies; AI callers receive the last actually-completed branch result. Email semantics tightened in the same pass: preparing a send is a WRITE (non-idempotent) effect,confirm_sendsuccess reflects the real delivery status (a GreenMail test proves attachments only go out after confirmation), and node labels clarify prepare vs execute. - Host file inputs are declared by structured schema metadata, not wording. Plugin parameters now declare file semantics explicitly —
format: fengyu-file | fengyu-directoryplusx-fengyu-file-access: read | read-write— enforced end to end: the manifest spec and CLI validate the keywords with conditional rules (format requires string, access requires format,fengyu-fileis read-only), the Java/Go/Python SDK field annotations gainformat/fileAccesswith contract-checked constraints, the host's file injector classifies from the metadata (description-wording heuristics stay as a compatibility fallback), and webhook triggers reject directory inputs. The flow UI applies descriptor presentation order and renders display overlays on top of the executable schema instead of duplicating it.
✨ Added (flow control & debugging, 2026-08-20)
- AI generation node (
flow_llm). A host-built-in LLM node distilled from the surveyed builders (n8n LLM Chain, Dify LLM node, Flowise structured output): one non-interactive completion per step with a prompt assembled from upstream references, an optional system role, an optional temperature (0–2, defaulting to the global AI setting), and an optional JSON Schema for structured output. The raw reply always survives in thetextoutput; a schema-shaped reply is parsed intodatawith one targeted repair retry that feeds the exact validation error back into the prompt (the community's measured fix for unreliable output parsers). Each call builds a fresh model client from the live config — parallel canvas steps and flows executed from chat viarun_current_flownever contend with the active conversation's generation lock. - Excel executor outputs its write directory.
excel_executenow returnsoutputDir— the absolute path it actually wrote the split files to (including any host-injected default) — declared both in the RPC output schema and as a canvas output port ("输出目录"). The email batch-send node's attachment directory binds it directly (\{\{node.write.result.outputDir\}\}) instead of re-entering the path; the excel→email template is re-wired accordingly and the email node's field help points at the binding. - IF branch node (
flow_if). A host-built-in control node comparing two values (12 operators: contains / starts_with / numeric order / emptiness / numeric-aware equality; either operand may bind an upstream reference) and exposing true / false output ports — the only multi-port node on the canvas. An edge drawn from a branch port compiles into the newAgentStep.runWhenconditions; the engine skips steps whose branch did not fire (and cascades over steps whose dependencies were all skipped), emitting a newstep_skippedSSE event rendered as a gray badge on the node, a muted chip in the execution panel, and aSKIPPEDexecution in run history. Branch edges carry a labeled chip naming their port and round-trip throughgraph_json. - Run this node. Single-step debugging from the node inspector: the node executes alone via a one-step plan on
POST /api/agent/run, with upstream references resolved from each ancestor's pinned or last-run value (no ancestors re-run) and workflow inputs from the current settings values. The result fills the same surfaces a full run does — badge, panel, history, last-run preview — closing the cold-start debugging gap (a deep node can now be validated before the whole chain ever passes). - Flow templates ×4 and recent flows. The canvas landing now offers "Excel column split" (no email leg), the always-available "JSON tidy-up", a branch demo (format only when a marker is filled — visibly exercises the skip path with built-in tools only), plus the existing excel→email flow; below the templates, the three most recently edited flows reopen in one click now that the sidebar lands on a fresh canvas.
fengyu checkflowNodes cross-validation. Every declared input must name a parameter the referenced tool actually accepts, and impossible widget/type pairs (e.g. anumberwidget typedstring) are rejected at check/build time. The rule immediately caught and fixed a real misalignment in the email plugin (body→plainText: manually authored email bodies were silently dropped). Select options now also accept{value,label}pairs for localized labels (schema widened, frontend renders both shapes).- Published snapshots and version history. Publishing now creates an immutable revision snapshot. Later edits remain a draft while AI keeps invoking the last reviewed version; the settings drawer shows the active revision and full publication history, and any older snapshot can be restored into a new draft without changing what is live. Manual runs and the builder's bound
run_current_flowcontinue to use the editable draft. - Idempotency-aware node retries. Flow nodes can make 1–5 attempts with bounded exponential backoff, but only for read-only tools or write/external plugin tools that explicitly declare
idempotent: true. Unsafe retry plans are rejected before any tool call, preventing duplicate writes; the canvas exposes retry controls only when the live tool descriptor is retry-safe. Live and persisted execution views now retain every failed attempt with its error and backoff. - Durable workflow schedules. Scheduled published workflows now survive application restarts, retain inputs/permission/sandbox posture, keep fixed-rate boundaries without clock drift, and coalesce overdue occurrences into one recovery run with a visible
missedFirescount. A durable at-most-once claim prevents crash recovery from duplicating external side effects; weakened plugin isolation pauses the trigger, and workflow deletion cancels schedules transactionally. - Durable, owner-scoped background tasks. Recent task status and capped output now survive application restarts and are visible only to their owner across REST and model tools. Work that was queued or running at shutdown is recovered as an explicit interrupted failure instead of disappearing, remaining stuck, or being replayed; off-request schedule fires explicitly retain the schedule owner's identity. The host now runs at most 16 task bodies concurrently and holds another 128 in a bounded queue instead of rejecting the first overload burst. A single owner may queue at most 32, preserving admission capacity for other owners even under racing submissions; batch and normal work are further capped at 16/24 per owner and 64/96 globally, preserving eight owner slots and 32 global slots for interactive webhook work. Model submissions are normal, schedule fires are batch, and webhook deliveries are interactive. Admitted tasks are FIFO within each owner-priority queue, scheduled 4:2:1 across interactive/normal/batch work to bound batch starvation, and round-robin across owners, while remaining work-conserving when only one class or owner is runnable. Rejected calls distinguish
owner,global,owner-priority, andglobal-prioritysaturation and include the rejectedcapacityPrioritywhen applicable. Queued state is visible and owner-cancellable, including a race-safe cancellation bridge installed after a kill request. REST, the run panel, and the read-onlytask_capacitymodel tool expose live running/queued pressure and priority mix, reservations, active-owner count, oldest queue wait, global/owner saturation and policy without leaking task details; task snapshots persist their priority, and the panel refreshes while open and warns on saturation or a 30-second wait. Capacity exhaustion now returns retryable HTTP 429 withRetry-After(and structured model-tool retry advice) instead of 500. An unadmitted webhook releases its idempotency claim so the same event ID can retry safely. Task snapshots also record start time, queue wait, and run duration for overload diagnosis. - Per-priority queueing metrics. The background-task scheduler now publishes its queue pressure through Micrometer with semantics calibrated against Kubernetes API Priority and Fairness and Temporal's schedule-to-start latency:
fengyu.bg.tasks.dispatchedandfengyu.bg.tasks.rejectedcounters per priority (rejections tagged with the limitingowner/global/owner-priority/global-priorityscope), afengyu.bg.task.queue.waitschedule-to-start histogram split by executed versus cancelled-while-queued outcome, andfengyu.bg.queue.inqueueplusfengyu.bg.queue.oldest_wait_msgauges per priority.task_capacity, the capacity endpoint, and the run panel also report the oldest queue wait per priority class, so the 30-second delay alert names the workload class (interactive/normal/batch) that is actually aging instead of a global blur. - Loopback workflow webhooks. Published workflows can now expose durable, owner-scoped webhook triggers from the run dialog. Each trigger has a stable loopback endpoint and a 256-bit secret shown only at creation or rotation; only its SHA-256 digest is retained. Optional event IDs are hashed and atomically claimed before task submission for at-most-once admission, request bodies overlay saved default inputs, and accepted deliveries run as ordinary durable background tasks. Ephemeral picker/shared-directory inputs are rejected, weakened plugin isolation pauses a sandboxed trigger, crash-interrupted claims are never replayed, and workflow deletion disables schedules and webhooks in the same transaction. Every accepted delivery — including calls that omit an event ID — now has a bounded, read-only lifecycle record that advances from queued to running and completed/failed/cancelled, records duration and the owning task, and becomes interrupted after a crash. The run panel exposes the latest records without retaining or returning request bodies, secrets, raw event IDs, or event-ID hashes; no unsafe blind-replay action is offered.
- Docs. New user-facing concept page
guide/flow-nodes.md(EN + ZH): references and the variable tree, the three-tier output preview, branch/skip semantics, single-step debugging, and pins;step_skippeddocumented in the SSE events reference; the plugin manifest reference documents labeled select options and the new cross-check.
🐛 Fixed
- Windows portable self-update no longer stalls when a plugin was open. The update-quit path force-killed the backend JVM with a synchronous TerminateProcess while tree-kill's async
taskkill /Twas still spawning: the JVM died first, taskkill then found the root PID gone and exited without ever killing the plugin-worker grandchildren. Those workers run from the bundledresources\jre, so they kept its image files locked and the replace script's robocopy burned its bounded retries and relaunched a half-updated app — the intranet field failure where an update with a previously opened plugin never completed its file replacement.forceKillon Windows now kills the whole backend tree synchronously (taskkill /F /Tby absolute path) before the direct-kill backstop, and the replace bat additionally sweeps any process still running from the app root (scoped by image path — never a blanketjava.exekill) before copying, then retries robocopy once when destination files were still locked. - Agent event decorators preserve control-flow telemetry. Notification and persistence wrappers now forward
step_skipped, while retry attempts are persisted and streamed asstep_retry, so production runs no longer lose skip or retry visibility when sinks are composed. - Workflow publication and concurrent editing are revision-safe. Saving an edited published flow no longer changes the active AI snapshot. Save, publish, and restore requests carry the editor's last-seen revision; stale windows receive a structured HTTP 409 instead of silently overwriting, publishing, or restoring over a newer definition.
- Plugin notifications now always go through the unified host pipeline. The notify bridge in
PluginViewused to require the manifest'snotificationspermission: an undeclared plugin'snotifyfell back to the iframe-internal snackbar queue, which the user effectively never sees (it scrolls away with the iframe). Every plugin'snotifynow creates a real unified host notification — in-app toast, native desktop notification when the window is hidden, and the persisted notification center — with no permission required; the SDK's iframe-local fallback remains only as the last resort when host delivery fails. Thenotificationspermission token is still accepted in manifests (documents intent, keeps existing packages installing). - Plugin uninstall did nothing in the desktop shell — and the drawer stayed open. The uninstall (and every other destructive) confirmation used the synchronous
window.confirm, which sandboxed Electron renderers silently drop (electron#7472): the call returnsfalsewithout showing anything, so "Uninstall" exited before sending the request and the detail drawer never closed. The shell now exposes a nativeconfirmover IPC (dialog:confirm→dialog.showMessageBox, Cancel-focused for destructive prompts) and the SPA routes all sevenwindow.confirmcall sites (plugin/skill uninstall, workflow delete, MCP delete, flow discard) through an awaitedconfirmAction()helper that falls back towindow.confirmin the browser. The plugin drawer also re-syncs after a failed uninstall: the backend deletes files before writing the response, so a mid-teardown error could leave a plugin-less drawer full of dead buttons — it now closes when the row is gone/uninstalled and stays open for retry only when the plugin is still installed. - Plugin installs no longer fail with an opaque "internal error". Local-package install pre-reads the manifest via
/inspect(-native); when the running backend predates that endpoint (404/405 — e.g. an IDE session on stale classes), the UI now falls back to the file-name confirmation and uploads anyway instead of failing the whole install. On the backend,IOExceptionon the install/uninstall paths (staging, extraction, atomic move) and any unmappedRuntimeExceptionnow answer with the real reason in the body and leave an ERROR log line (previously: whitelabel "Internal Server Error" with nothing in the log), and the unified-storeInstallerDispatcherstops rewrapping validation verdicts (IllegalArgumentException→ 400 with the actionable message) into generic 500s. - Connecting a third node no longer drops earlier links. vue-flow re-validates the whole edge list through
isValidConnectionon everyv-modelreassignment; previously each already-stored edge failed the duplicate check against itself and was silently dropped, leaving only the newest connection (the canvas effectively supported two-node chains).canConnectnow recognizes stored-edge echoes by id (also protecting undo/redo) and is extracted intoworkflow.tswith unit tests. - One output port per node. Nodes with several declared outputs rendered one output dot per field; wiring is whole-node, so the canvas now renders a single output port whose tooltip summarizes the declared outputs (the IF node's true/false branch ports are the deliberate exception).
- Code-review fixes across the unreleased feature set. The last-resort
RuntimeExceptionadvice no longer swallows framework status exceptions — deliberate controller 409/429 responses and SSE async timeouts keep their precise status codes instead of degrading to opaque 500s. The LLM node's 180s timeout is now a hard deadline: a hung model call is interrupted and abandoned rather than close()-joined past the deadline. On the flow canvas, node references with array indexes (\{\{node.x.result.files[0]\}\}) compile through the same shared grammar that validates them (they used to pass save-time checks and then reach the tool as literal text), the variable-tree search reaches nested fields (previously only top-level rows were searchable), editing the Start designer preserves annotations it does not model (x-fengyu-auto/-analyze/-enum/-options-from,default,description, nesteditems, thefengyu-directoryformat), and the expression-mode button switches the input without wiping its value. Also: the missingcommon.copiedlocale key (EN+ZH), fallback colors/icons for theai/controlnode categories,flow_if's defensive fallback no longer interpolates unescaped operator text. - CLI/desktop hardening from the same review.
fengyu check's flowNodes cross-validation now also rejects declared inputs against parameter-less tools (the exact "silently ignored field" case, previously skipped wheninputSchemahad noproperties); declaredpackage.resourcescopy targets are containment-checked against the staging directory; and the desktopdialog:confirmIPC validates its invoke payload, answeringfalseinstead of rejecting on malformed input.
♻️ Changed
- Notification entry moved into the account menu. The sidebar's standalone bell button is gone; the notification center now opens from a "Notifications" item in the menu behind the username (with a live unread count chip), and the panel drops in exactly where the account menu was — including the rail-sidebar anchor. Discoverability of new notifications is kept by an unread beacon dot on the user avatar while any notification is unread. The panel itself is unchanged: same live SSE feed, mark-read, and navigation behavior.
🐛 Fixed
- Update checks never ran in the current shell — and now repeat periodically. The startup update probe lived in
StatusBar, a component the current shell layout no longer renders, so no update was ever detected outside the About page's manual button. The probe (plus a new idempotent 6-hour re-check for long-running sessions) now lives inAppShell, the one component mounted on every route. When a newer release is found, the sidebar's About (ⓘ) entry carries a red beacon dot (with the available version as its tooltip) until the update is installed — mirroring the avatar's unread-notification dot.
✨ Added
- Update plugins from a local package, with a version-aware confirmation. Installing a locally uploaded
.fypover an already-installed plugin id now behaves as a first-class update flow instead of a silent replace. New pre-install inspection endpoints (POST /api/plugin-market/inspectand its desktop path twin/inspect-native) read the incoming package's manifest without installing and return whether the id is installed plus the version step (upgrade/same/downgrade, ordered by the same semver comparator behind the catalog's update badge). The marketplace UI routes every local.fyppick through a confirmation dialog showinginstalled → incomingversions — warning on a downgrade or same-version reinstall — and the detail drawer of an installed plugin gains an "Update from local package" entry; the upload itself keeps the existing update gate (stop the running worker, atomic directory swap, enabled state preserved). This also gives non-catalog third-party plugins a working update path: catalog-based/{id}/updatecan only resolve plugins listed in a configured marketplace. - Host-side unified notifications. One pipeline now reaches the user everywhere: a persisted notification center (
/api/notifications— create/list/read/delete with a 200-row retention window), a live ticket-authenticated SSE stream (GET /api/notifications/stream) that every connected shell subscribes to, in-app toasts while the app is visible, and native OS notifications through the Electron shell when it is not (clicking one focuses the window). Producers: the pluginnotifyhost bridge — now gated on the declarednotificationspermission, which was previously advisory-only and always fell back to the iframe-internal center — and agent run termination (completed/failed; user cancels stay silent). The sidebar gains a bell with an unread badge and a notification-center panel. The official plugins whose UIs callnotify(email, excel, offlinepython) now declare thenotificationspermission, so their notifications ride the real host surface instead of the iframe-local fallback.
🐛 Fixed
- About page update row: the unsigned-install confirm showed alongside the "Update now" button and "Cancel" did nothing. The inline confirm popover's
v-elsewas chained to the download-progress bar'sv-ifinstead of the!confirmingbutton, so the popover rendered whenever a download wasn't in flight — overlapping the "Update now" button, ignoring Cancel (its visibility never depended onconfirming), and after clicking "Update now" only "Continue/Cancel" remained. The popover now pairs with the!confirmingbutton and the progress bar is an independent element, restoring the intended gate: "Update now" → warning + Continue/Cancel → download progress. - Plugin/skill detail drawer stayed open on a stale snapshot after marketplace operations. The drawer held the row object captured when it was opened, so after an operation refreshed the list the drawer kept rendering the old version/enabled label — and after an uninstall it stayed open with dead action buttons (a catalog plugin's row even survives uninstalling). Uninstalling a plugin or skill from the drawer now closes it, and every other operation (update, toggle, local-package update) re-points the drawer at the refreshed row — the skill preview also reloads after a skill update.
- Email Center contacts page: the New-contact form and the tag manager were unreachable. In the real host iframe the page usually renders in the narrow single-column layout (the
.fy-plugin-pagecontainer sits below the 1000px breakpoint once the plugin's own drawer is subtracted), which stacks those cards below the tall contact list — and wheel scrolling over the list did nothing because.contact-list-scroll'soverscroll-behavior: containblocked scroll chaining to the page even with an empty list. The containment is removed (the wide two-column layout never scrolls the document, so it gains nothing from it); wheel/touch scrolling now chains and the below-fold cards are reachable again.
[4.0.0-beta.4] — 2026-08-18
✨ Added
- Unified option-source standard for flow-node inputs. Three declarative candidate kinds replace the excel-only vocabulary: static
options; CATALOGsource(a plugin list method the host fetches and maps through value/label/labelSecondary, single- or multi-select); and CONTEXTcontext(datasets derived at edit time from another input's value — e.g. the workbook path → sheets/columns via the analyze RPC — consumed throughoptionsFromContextreferences, including row-field keying). The node inspector loads catalogs on focus with a manual fallback, renders context triggers beside their input, and feeds datalists; the run form's x-fengyu-enum annotations now share the same fetch path. Excel migrated to context/optionsFromContext (the analyze widget and workbook-sheets/workbook-columns annotations retire); Email declares catalog sources for 发件账号 and 收件/抄送分组.
♻️ Changed
- Explicit flow-node declarations (canvas no longer derives node forms from AI-tool schemas). Plugins declare nodes in
manifest.flowNodes[](tool binding, label, color, icon, typedinputswith widget configs — text/number/switch/select/textarea/json/analyze/rows — and namedoutputs); the host ships built-in declarations inflow-nodes/builtin.json(currentlyjson_format). The tool descriptor API carries the declaration through, the canvas palette lists ONLY declared nodes, node cards render declared labels/icons/colors and NAMED OUTPUT PORTS (Flowise outputAnchors), and the node inspector renders the declared widgets (descriptor defaults apply on drop, e.g.action=add). Non-declared tools remain available to the model but never appear as canvas nodes. Excel (复杂拆分/执行拆分) and Email (批量发送/放行发送) ship the first declarations; legacy saved graphs keep the schema-derived fallback rendering.
♻️ Changed
- 1:1 Vue replica of Flowise's AgentFlow v2 canvas (React removed). The flow builder's canvas now mirrors the dark AgentFlow canvas from the screenshot, rebuilt in pure Vue on vue-flow after a full read of Flowise's source: node-type colors from
tokens.tstint each card via MUI'sdarken(color, 0.8)formula (hover 0.7, border alpha .5/.8/selected), the 40px radius-15 icon badge, NodeInputHandle's 5×20 color bar, the hover-revealed chevron output handle, AgentFlowEdge's gradient bezier strokes with hover delete buttons, the #1a1a1a dot grid, bottom-center Controls with snap/background toggles, and the dark MiniMap. The Delete key is now history-aware (vue-flow's built-in deleteKeyCode bypassed the undo stack). react/react-dom/reactflow and the React island are gone. - One-click workflow templates & ordinary-user run form. The canvas ships a built-in template gallery (empty-canvas state and the workflow library); the first template, Excel split → batch email, pre-wires
excel_complex_config → excel_execute → email_send_batch → confirm_send, pre-maps nested output references (confirmation.confirmationId), and ships a run form a non-technical user fills in: file inputs render upload pickers, shared output folders are minted automatically, and account/recipient-group inputs render live dropdowns fed by plugin list tools (email_accounts_list,email_tags_list) instead of numeric ids. - Multi-rule complex split configuration. The template's split rules input is a dynamic row editor — one row per worksheet (
工作表+拆分列), bound whole asentries: \{\{inputs.rules\}\}— and an omittedheaderIndexnow defaults to the first header row in the Excel plugin, so neither the canvas nor the model needs row numbers. Uploading a workbook analyzes it through the plugin (excel_analyze) and turns the sheet/column fields into datalist candidates, so users pick their real sheet and column names instead of typing them blind; the union fallback keeps fields free-text. - Run-scoped file grants for workflows.
POST /api/agent/runandPOST /api/workflows/{id}/runaccept afilesarray (pass-through picker grants, a native path, orcreateSharedDirectory). File-class inputs travel as@file:<name>placeholders that the host swaps for the current plugin's FileRef at dispatch (RunFileContext+AiToolFileInjector.bindRunFilePlaceholders), and a host-minted shared scratch directory is granted live to every eligible plugin — so an Excel split step's outputs are readable by a later Email step on every sandbox backend. confirm_sendas an approval-gated AI tool. The email plugin now exposesconfirm_sendwith effectexternal: chat keeps its confirmation card, while visual workflows mark the send step requires approval, so every permission mode except full-access pauses for a one-click human go-ahead before dispatching the batch.- Nested node-output mapping. The inspector's output picker flattens one nesting level into dotted paths (
confirmation.confirmationId), so a follow-up step can map a specific nested field instead of the whole result object. - e2e smoke: full template chain.
scripts/e2e-smoke.shnow boots a local SMTP sink and drives the whole scenario — uploaded workbook + shared dir + run file grants + approval-gatedconfirm_send— asserting four completed steps and a delivered message. - Permission rules & lifecycle hooks (grok-build-informed agent runtime hardening). A user-configurable guard now sits between the permission modes and every AI tool call, evaluated as
PreToolUse hooks → deny rules → ask rules → allow rules → mode default. Rules follow theCommand(git status)/Tool(excel_*)/Effect(read)/Mcp(server__*)/WebFetch(domain:…)grammar with order-independentdeny > ask > allowprecedence, per-segment shell-chain checks (an allow must cover every segment ofa && b | c; deny/ask match any), and a dangerous-command floor (rm,sudo,git push, …) that allow rules cannot bypass. Lifecycle hooks extend the same pipeline: command hooks (event JSON on stdin; exit 2 denies with stderr as the reason; a stdout{"decision":"deny"}gate document denies on any exit code) and HTTP callbacks forpre_tool_use,post_tool_use,post_tool_use_failure,run_complete, andrun_error, failing open when a hook crashes or times out. Configured in Settings (runtime & security), shared by chat and agent runs; a denied call fails its step with the rule's reason so the model can replan around it. - Plugin-contributed hooks with an enable≠trust gate. A
.fyppackage may shiphooks/hooks.json(grok-shaped or FengYu's flat list); installing or enabling the plugin never activates them — the user trusts the plugin explicitly viaPOST /api/plugin-hooks/{id}/trust, and untrusting takes effect on the next call. Trusted hooks run with the plugin install dir as working directory plusFENGYU_PLUGIN_ROOT/FENGYU_PLUGIN_DATAenv andplugin/<id>/<name>namespacing. - Workflow schedules. Published workflows run on schedules (
task_scheduletools +/api/agent/schedulesREST): 60-second minimum interval, 50-schedule cap, 7-day expiry, optional immediate first fire, delayed one-shots viarecurring:false. Scheduled runs are ordinary background tasks (task_output/wait/kill apply); durable definitions are restored after restart with fixed-rate, overdue-coalescing, and at-most-once crash semantics. - Host-level background tasks. Long workflows no longer block the synchronous tool slot:
task_submit_workflowreturns ataskIdimmediately,task_outputpolls or blocks,task_waitwaits on up to 20 tasks (any/all), andtask_killstops a runaway task with cooperative cancellation first and SIGTERM → SIGKILL escalation for process-backed ones. The same durable, owner-scoped registry backsGET /api/agent/tasksfor the UI and retains the 100 most recent finished snapshots across restarts. - Run-history search, fork, and rewind.
GET /api/agent/runs?q=…searches goal/summary/error text;POST /runs/{id}/forkre-runs a finished run's plan as a fresh peer;POST /runs/{id}/rewind {keepSteps}truncates the plan to its first N steps (inheriting only earlier completed executions) and resumes under plan review — dropped steps' side effects are documented as not rolled back. Runs also record the plugin-sandbox posture they were created under and refuse replay while the host runs unsandboxed, so isolation can never silently weaken. - Read-only batch capability.
POST /api/agent/batchacceptscapabilityMode: "read-only", restricting every child run toread-effect tools — parallel research/review tasks can be declared mutation-free up front. - Cross-session memory (experimental, off by default).
memory_remember,memory_search,memory_list, andmemory_forgettools over a per-user store with keyword × 7-day-half-life recency ranking; relevant memories are injected into agent planning context when the Settings toggle is on. - Usage metrics with OTLP export. Agent runs and per-tool steps are counted and timed through Micrometer (
fengyu.agent.runs/fengyu.agent.steps/fengyu.agent.run.duration), readable at/actuator/metrics; settingmanagement.otlp.metrics.export.urlstreams the same metrics to an OpenTelemetry collector for production observability (the registry stays dormant with no URL). - Marketplace checksum pinning. With "require plugin checksums" enabled, installing a plugin demands its
.fyp.sha256sidecar (fengyu CLI packager output) and rejects mismatches — supply-chain hardening for third-party packages. - Computer use (ChatGPT-desktop-style screen control). Desktop builds now expose a
computer_*AI tool family driven byjava.awt.Robotinside the backend JVM, with the same behavior on Windows, macOS, and Linux: screen capture with Hi-DPI scale reporting (the PNG reaches vision models through the same media bridge asbrowser_screenshot), display/app enumeration, application launch and activation (PowerShell on Windows with.exe-stripped process-name and window-title fallback matching;open/osascript on macOS;gtk-launch/wmctrlon Linux), mouse move/click/double-click/drag/scroll, keyboard typing with clipboard-paste fallback for non-ASCII text, key combos, and a settle wait. Input-injecting calls areexternaleffects that pass the per-turn approval gate; observing calls areread. A Settings card (Runtime & security → Computer use,computerUseEnabled, default on) shows the capability probe and can hide the whole family per registry snapshot. Windows needs no extra permissions (UAC/elevated windows stay OS-protected); macOS needs Screen Recording and Accessibility. Display-less hosts degrade to"computer use unavailable"envelopes. A dedicated cross-OS workflow (.github/workflows/computer-use-ci.yml) runs the family's unit tests on Windows/Linux/macOS and the real-display Robot integration test on a Windows runner. - Dynamic MCP server runtime. MCP servers can now be added, edited, removed, tested, and called from Settings or the REST API. STDIO, SSE, and Streamable HTTP connections are persisted, initialized immediately, discovered with
tools/list, and injected into the live AI tool catalog without restarting FengYu; credentials are kept in a protected sidecar file and are never returned. - Reusable workflows with one manual/AI execution path. Visual DAGs can now be persisted with JSON Schema inputs, loaded and revised in the canvas, run manually with typed
\{\{inputs.*\}\}bindings, and published as dynamically discovered Spring AI tools. Manual and model-triggered calls share the existing dependency-aware runner, tool registry, durable run history, SSE events, and audit trail; saved definitions reject nested workflow tools to prevent recursion. Definitions persist the canvas node layout, so a saved graph reopens exactly as arranged; the workflow library can save a copy of any definition as a starting point, and the run panel shows each step's actual output. - One-call complex Excel split configuration.
excel_complex_configaccepts anentriesarray declaring the complete rule set in one call (one rule per sheet,columnNameresolved to a column index against the analysis) plus an optionalfilePaththat analyzes the workbook in the same call and leaves the session inCOMPLEXmode — a FengyuFlow canvas needs justexcel_complex_config → excel_execute. Re-running the same call is idempotent (entries replace, not append), duplicate rules for one sheet are rejected up front, and the shared AI session is now pinned and synchronized so concurrent workflow steps cannot corrupt it.excel_executewith a blankoutputDirwrites into the plugin's default output folder — injected by the host as a plain sandbox-writable path, deliberately not registered as a file grant because grant registration restarts stateful plugin workers and would destroy their in-memory sessions mid-flow.scripts/e2e-smoke.shcovers the single-node complex-split workflow end to end.
♻️ Changed
- The canvas is now a literal port of Flowise's canvas (vue-flow removed). The flow builder's stage runs Flowise's own React + React Flow stack as an island inside the Vue shell: ported
CanvasNode/ButtonEdge/StickyNotecomponents and the chatflow canvas wiring live insrc/flowise/, mounted throughFlowiseCanvasHost.vue. The Vue side stays the single source of truth — nodes/edges flow in as props and every renderer mutation (select/position/dimensions/ remove, connects, note edits, edge deletes) travels back through controlled-mode change channels (applyCanvasNodeChanges/applyCanvasEdgeChanges). reactflow v11's controlled mode requires the parent to store measured dimensions back onto nodes — miss it and nodes stay invisible with edgeless rendering; that round-trip is now covered by unit tests. All@vue-flow/*dependencies are gone. - Flowise canvas replica. The flow builder's canvas now mirrors Flowise's CanvasNode design (source-verified against the archived FlowiseAI/Flowise repo): 300px node cards with a circular white icon badge and per-category accent colors, grey "Inputs"/"Output" section bands, and Flowise's handle states (red on invalid connection, green on a valid target). Edges are Flowise ButtonEdge replicas — smooth bezier curves with a midpoint delete button — and structural edits (node/edge/note add, remove, moves) gained undo/redo (toolbar buttons, ⌘/Ctrl+Z, ⇧⌘Z / Ctrl+Y, 50-step cap).
- Fixed silently dropped canvas edges. vue-flow re-validates programmatic edge assignments through
isValidConnectionAFTER the parent state already contains the new edge — the builder's duplicate check therefore rejected every auto-connected, restored, or linked edge. Validation now runs against the store-supplied pre-update edge list. (The same latent bug existed in the old AiAgent canvas.) The Flowise edge-delete button also needed an explicitpointer-events: all— vue-flow's edge-label layer disables pointer events for its whole subtree. - One tool-call mode for AI Chat and flows (Flowise chat-with-your-flow). The flow builder ships a Flowise-style docked chat panel: sending a message binds the turn to the flow being edited (
POST /api/ai/chatnow acceptsworkflowId), and the backend exposes that flow — draft or published — to the model asrun_current_flow, an ordinary tool call in the same chat tool-call loop that powers AI Chat (identical permission modes, approval gates, and SSEtoolevents; the panel auto-saves pending edits first). AI Chat reaches published flows through the same loop viarun_workflow_<id>, making chat and the canvas peers over one runtime. Internals: request-scopedBoundToolsContextmerges bound tools into each turn's registry snapshot (cloud + Ollama backends), andWorkflowExecutionService.executeForAi(…, requirePublished)admits drafts on the bound path only. - Flow builder rebuilt Flowise-style (flows refactor). The visual canvas moved out of the AI Agent page into a dedicated Flows section (
/flows): a library page listing saved flows as cards (open / duplicate / delete) plus templates, and a full-workspace builder with a categorized, collapsible node palette (search, drag-to-add), a right-hand node configuration panel, Flowise-style sticky notes (annotation-only nodes), per-category node accent colors, and the same run dialog / execution panel / settings drawer as before. The AI Agent page (/agent) is now the pure AI-planning view;AiAgent.vueshrank from ~3300 lines to ~350 by extracting the shared SSE run engine (useAgentRunStream) and the canvas intoFlowBuilder.vue+ focused child components. - Workflows persist the authored canvas graph. Definitions gained an optional
graphfield (ai_workflow.graph_json) storing the exact nodes/edges/sticky-notes as arranged — node ids are stable across save/reload, so\{\{node.<id>.result\}\}references survive.plan+layoutremain the compiled execution contract; older definitions without a graph still reconstruct the canvas from the plan. The backend validates graph shape (nodes/edges lists, size caps) without interpreting node internals. - FengyuFlow fails fast instead of failing mid-run. Saving a workflow now rejects
\{\{inputs.*\}\}references that the input schema never declares (previously such a graph saved fine and failed at every run) and caps definitions at 64 steps. The manual-run dialog blocks the start until required workflow inputs are filled, host validation messages surface localized in the UI, and unsaved canvas work is protected by confirm dialogs on switch/new/delete plus a browser close guard.scripts/e2e-smoke.shnow probes workflow create/layout/manual-run/ publish-as-AI-tool/delete end to end against a real backend. - Plugin tool failures now carry their reason. Most official plugin methods report failure as
success:falseplus a localizedsummarywith noerrorfield; the host's result normalizer only readerror, so agent runs and workflows showed a useless "Tool reported failure". The normalizer now falls back tosummary, surfacing the actual localized diagnostic. - All JavaScript areas now install through Yarn 4 (corepack).
frontend/,desktop/electron/,docs/, and the four@infinia/*toolchain packages pinyarn@4.18.0viapackageManager+.yarnrc.ymland install from committedyarn.lockfiles (--immutablein CI); thepackage-lock.jsonfiles are gone and npm lockfiles are deprecated for repository areas. Onlyfengyu initstill scaffolds npm-based projects for third-party plugin authors, and the release workflows cache and install with yarn accordingly.
✨ Added
- Three-control complex-split rows + in-node workbook analysis.
excel_complex_configentry rows in the flow builder now show only Sheet 名称 / 拆分列 / 整表拷贝 (headerIndex and columnIndex fold away asx-fengyu-advanced; the new per-entrycopyEntireSheetboolean is the canvas-friendly spelling of the (-1, -1) whole-sheet convention, honored by the Excel worker). The node's File Path input gains an analyze button (pluginanalyzeRPC, isolatedcanvas-<nodeId>session) whose sheet/column results feed datalist pickers — the 拆分列 candidates narrow to the chosen sheet.
🐛 Fixed
- Settings page was a blank screen in production builds (dev worked). Four i18n messages (
settings.mcp.placeholderEnvironment/placeholderHeaders/placeholderArgumentsObjectandsettings.guardHooksHint) embed literal JSON examples, and their{/}was parsed by vue-i18n's message compiler as placeholder syntax — INVALID_TOKEN_IN_PLACEHOLDER. The dev build tolerates the compile error (warn + raw-message fallback), but the production build throwsSyntaxError: 2from the vendored Vue bundle, killing the whole Settings view (the shell survives, the page body dies). The literal braces are now escaped with vue-i18n's{'{'}/{'}'}literal syntax in both locales. - A "fresh" directory silently adopted the config from
~/.fengyu— the setup wizard never appeared.DataSourceConfigServiceprobed legacy locations (<cwd>,~/.fengyu) and migrated theirdatasource.propertiesinto the runtime root on load, so once any run had saved a config under~/.fengyu, every new portable extraction (or any directory the desktop shell pins via-Dfengyu.runtime.dir) booted straight into APP mode and the main window. An explicitly pinned runtime root now stays isolated — fresh directory, fresh setup wizard; unpinnedjava -jarruns keep the legacy migration for upgrades. - Setup wizard's restart detection raced the dying SETUP backend (main shell dead on arrival after setup). The wizard polled
/api/setup/statusforinitialized:true, but the still-exiting SETUP backend answers exactly that for its ~1s grace period (the config was just persisted), so the SPA navigated to the main shell while every app API still 404'd — and once APP mode came up the endpoint 404s by design, so the poll could also never succeed and timed out. The wizard now treats that 404 as the "restarted into APP mode" signal (same contract as the SPA router guard) and ignores the ambiguous 200. - Desktop: launching on a configured machine (or right after finishing the setup wizard) killed the healthy backend with "setup status request failed: HTTP 404".
/api/setup/**is token-bypassed and therefore only mapped in SETUP mode, so an APP-mode backend answers 404 there by design — the desktop shell's startup probe treated that as fatal (and the SETUP→APP restart after the wizard hit the same probe), while the SPA router guard already read 404 as "already configured". The desktop probe now does the same: 404 → APP mode, other statuses stay fatal. - Drag-connect on the canvas. Flowise's NodeInputHandle color bar (the 5×20 vertical strip) broke vue-flow's drop hit-test —
elementFromPointlanded on the bar's inner div instead of the handle, so no connection ever fired. The input handle is now a small node-colored dot with no inner content, which also removes the vertical line from every node. - Canvas follows the app theme. The hardcoded #1a1a1a surface, white text, and dark controls/minimap are gone — nodes tint per Flowise's useNodeColors formulas on BOTH themes (dark:
darken(color, 0.8), light:lighten(color, 0.9)) and the surface, controls, minimap, and text use the Vuetify theme tokens, switching with the app's light/dark setting. - Empty-canvas "添加节点" button and template cards were unclickable. The empty-state overlay rendered inside VueFlow's default slot, where the interactive pane stacks above slot content and swallowed every click. The overlay now lives outside the VueFlow root (pointer-events only on its buttons), so both the centered add-node button and the built-in template cards respond.
- Canvas test runs now bind
\{\{inputs.*\}\}. An unsaved canvas run used to post the compiled plan with literal placeholders, so tools received\{\{inputs.x\}\}strings; the client-side compiler now binds run-form inputs with the same semantics as the backend (exact reference → typed value, embedded → rendered text) while saves keep placeholders for later re-binding. The Excel→Email template's required-but-emptyccGroupTagIdslikewise no longer blocks the run: it maps to an optional input seeded with[]. - Text-only fallback for strict OpenAI-compatible gateways. Some gateways only accept string
contentin chat messages and reject the array-form (multimodal) content that screenshot tools produce — e.g. Go-based relays answeringjson: cannot unmarshal array into Go struct field ChatMessage.messages.content of type string. The chat round now detects that 400, retries once without image attachments, and stays text-only for that endpoint from then on; screenshots remain in the conversation history and UI, and vision-capable endpoints are unaffected.
🔒 Security
- Plugin SDK no longer bridges to a wildcard origin (BREAKING for plugin toolchain 2.0.0).
@infinia/plugin-sdkremoved the historical'*'default for the postMessage bridge: the embedding shell must append?shellOrigin=<its origin>to the plugin URL (or the caller passesallowedOrigin), otherwise the client refuses every request — previously ANY website that iframed the loopback-served plugin page could silently receive every invoke response. Plugin UIs built with this SDK therefore refuse to bridge on hosts that do not pass?shellOrigin=, which is why the unreleased toolchain line moves to 2.0.0. The parameter pins the bridge for well-behaved hosts but is not an authenticity boundary (an embedder controlling the iframe URL can forge it); origin assurance belongs server-side. - Dev-server AUTH token handshake. The
fengyu-plugin-devkitloopback dev server requires every connection to lead withAUTH <token>; the token is random per start and shared with the dev Vite client through a user-only file under~/.fengyu/that is created0600owner-only from the first byte. Loopback binding alone had left the full worker RPC surface open to any local process. - Optional Ed25519 checksum signing for releases.
fengyu-release.ymlsignschecksums.txtintochecksums.txt.sig(base64 Ed25519) whenever theFENGYU_SIGNING_KEYsecret is configured; without the secret the release stays checksum-only, so unsigned builds keep working.
⬆️ Dependencies
vue-i18n10.0.8 → 11.4.8 (frontend + email plugin UI). v9/v10 are EOL upstream — the project now forbids EOL dependencies on every surface. v11's stricter message compiler surfaced one unescaped literal@insettings.mcp.placeholderArguments, now escaped with{'@'}in both locales.vite6 → 7.3.6 (frontend, with@vitejs/plugin-vue6 and a resolution unifying every vite resolve on 7.x). Vite 6 left the upstream support window once Node 20 reached EOL; 7.x is the maintained line the plugin toolchain already uses.vitest3 → 4.1 (frontend + desktop) andpinia2.3 → 4.0.3 (frontend). Vitest 4's new spy implementation is not constructible from arrow-function implementations, so the two desktop suites that stubnew BrowserWindow(...)were switched to function-expression implementations.typescript5.7 → 5.9.3 andplaywright1.50 → 1.62.1 (desktop +@playwright/test), aligning the desktop toolchain with the repo's 5.9 TypeScript line and a current Playwright.- Removed dead dependencies:
miglayout-swing(a JavaFX-migration-era Swing layout manager with zero source references) and the never-referencedcom.microsoft.playwrightmanaged entry + property in the root POM. fengyu-release.ymlnow runsyarn npm audit --environment productionfor the frontend and desktop areas — any advisory, including EOL/maintenance deprecations without an exploitable CVE, fails the release (the recorded no-EOL-dependencies policy).
✨ Added
- Constrained intranet application updates through FY-Proxy. The desktop loads its persisted update channel before the first automatic probe, supports the same channel for manual checks, verifies FY-Proxy SHA-256 metadata for Windows portable ZIP updates, and uses a dedicated electron-updater feed for lite x64 deb packages. The proxy accepts and serves only those two package classes and rejects NSIS, AppImage, macOS, JRE, and portable Web/JAR assets.
- Automatic conversation compaction for long AI chats. The cloud and Ollama chat paths now estimate provider input from UTF-8 bytes and, at 60% of the configured context window, summarize the oldest complete rounds into a marked assistant context note while preserving system messages and the latest eight rounds verbatim. The full transcript remains unchanged for the UI and persistence, and a failed summary falls back safely to the original history. Settings exposes the context-window size (32,768 tokens by default;
0disables compaction). - Stateful, multimodal browser sessions. A Java-side
BrowserSessionnow routes operations to the same isolated Electron context/tab, caches URL/title and snapshot refs per tab, and rejects unknown or stale refs before an action. Four tab-management tools plusbrowser_batchextend the browser surface to 21 tools; the batch path performs one snapshot plus click/type/press in a single serialized bridge request.browser_screenshotnow carries the actual PNG into the next Spring AI model round asMedia(image/png)while retaining its DOM snapshot and accessibility text fallback. - First-class read-only web retrieval. New host-embedded
web_searchandweb_fetchtools separate lightweight discovery/content reading from interactive browser work. Fetches are bounded, follow a limited redirect chain, reject local/private-network targets, and enter the shared approval policy asreadoperations.
♻️ Changed
- Tool-loop context is now bounded. Tool responses larger than 64 KiB retain their first 75% and final 25% with an explicit omitted-character marker before the next model round, preventing a single result from consuming the conversation window while the SSE activity still receives the complete result.
- Chat and Plan-and-Execute share one approval policy. Both paths now use the same decision for
read,write,command, andexternaleffects, eliminating future permission drift. MCP tools remain conservatively wrapped asexternal, isolated plugin tools keep their manifest effect, and the 21 built-in browser tools now declare per-operation effects: inspection isread, while navigation, page interaction, JavaScript evaluation, and window close areexternal. - Browser bridge I/O is asynchronous. Java uses
HttpClient.sendAsyncwith virtual-thread response processing and waits only at Spring AI's synchronous tool boundary. Electron preserves serialized input semantics while routing independent logical sessions, contexts, and tabs.
🐛 Fixed
- Command failures no longer lose their diagnostic tail.
execute_commanddrains stdout and stderr concurrently and returns them separately (stdout/stderrplus per-stream truncation flags), while keeping the combinedoutput/truncatedfields for compatibility. Oversized streams preserve both the head and tail instead of discarding the end where compilers and shells usually report the actionable error.
[4.0.0-beta.3] — 2026-08-13
✨ Added
- One responsive UI system for every official plugin.
@infinia/plugin-uinow providesFyPluginPagefor shared responsive content gutters andFyProgressfor determinate or indeterminate long-running state, plus reusable surface/action/status/log/table CSS hooks.FyPluginShellowns the notification center and supports navigation-free single-workspace apps; notification composables share one client-bound fallback queue with consistent tones and timing. - Plugin manifest schema v2 — the single hand-written contract.
manifest.jsonis nowschemaVersion: 2withadditionalProperties: falsethroughout. RPC methods live once in anrpc.methodstable whoseinputSchema/outputSchemaare JSON-Schema objects (not escaped strings);aiToolsreference those methods bymethodwith a mandatoryeffect, duplicating no schema.backendkeeps onlycallTimeoutSeconds— the worker is implicitlyjava -jar backend/worker.jarover JSON-RPC 2.0 (backend.command/backend.protocolremoved). The CLI validator enforces every rule, including a raw-text duplicate-rpc.methods-key scan (JSON.parse would otherwise silently merge duplicates). - Deterministic contract generator.
fengyu init|dev|buildgenerate a typed TS RPC client (ui-src/src/generated/fengyu-rpc.ts—createPluginRpc(client)+ per-methodInput/Outputtypes) and Java records + a centralizedPluginMethodsname class (src/main/java/<id>/generated/) straight fromrpc.methods. Output is byte-for-byte stable (sorted), handles identifier escaping / reserved words / nullable / required, nests records for object/array fields, and rejects unsupported JSON-Schema constructs instead of coercing toObject.fengyu checkdetects generated-file drift without writing. - Typed Java Worker SDK + standard cancellation.
JsonRpcWorker.method(NAME, Input, Output, handler)registers typed handlers;RpcContextexposescallId/pluginId/pluginRoot/locale/cancellation()/logger(). The dispatch loop is split into a reader + handler pool so a$/cancelRequestnotification cancels an in-flight call (returnsCANCELLED, worker survives); EOF drains gracefully. StableRpcError.Codeset (INVALID_ARGUMENT/PERMISSION_DENIED/NOT_FOUND/CONFLICT/CANCELLED/INTERNAL) maps to JSON-RPC codes and anerror.data.codelabel. The oldJsonRpcWorker.string/integerMap-parsing helpers were removed. - Reserved
_fengyumetadata envelope on the worker RPC frame. The request locale now rides in a host-owned top-level_fengyuobject (_fengyu.locale) instead of being injected intoparams, so a plugin method may freely declare its ownlocaleinput field without it being overwritten by the request locale. The Worker SDK reads_fengyu.locale(binding bothRpcContext.locale()andWorkerLocale, so synchronous handlers now resolve messages in the request language — previously onlyJobspropagated locale) and falls back to the legacyparams.localekey for hosts that have not yet adopted the envelope. Any frame-root key beginning with_fengyuis reserved. - Shared plugin bridge protocol
3.0.0and capability pre-check.@infinia/plugin-sdk/protocolis the side-effect-free source for iframe/host message types, method constants, capabilities, and structured errors.HostEnvironmentnow carriespluginId/pluginVersion/permissions;HostError.codeaddsTIMEOUT/CANCELLED. The SDK validates the capability before each request, posts the cancel notification on both timeout and abort, and silently drops responses for unknown ids / wrong origin / wrong protocol version. - Host v2 install + dispatch. Install accepts schema v2 only and validates
rpc.methods(unknown method rejected before worker start; AI tool schemas read from the referenced method's object schema — no string re-parsing). The iframe/HTTPcallIdis passed through as the JSON-RPCid. Cancel sends$/cancelRequestfirst and only force-restarts on timeout. Worker errors map to typed exceptions:PERMISSION_DENIED→ HTTP 403 (no longer a generic 500),CANCELLED→ 499. - All four official plugins migrated to the typed model.
markdown(canary),excel,offlinepython, andemailnow ship schema-v2 manifests, generated TS/Java contracts, typedmethod(...)workers, and transport-cancellation tests.offlinepython's domainbuild.cancel/deploy.cancelnow reaps the whole Python/pip subprocess tree. - Plugin cards re-fetch their server-localized name/description on locale change. Names and descriptions are resolved server-side per request locale (each manifest's i18n block), but the tools page, market "plugins" tab, and store tab fetched once and cached — so switching the UI language flipped the
$t()strings but left the cards' server-supplied text in the old language until a manual reload. A reactivelocaleRefnow drives re-fetches in the plugins/plugin stores andPluginMarket.vue; the axios interceptor already sends the currentAccept-Languageheader on every request.
♻️ Changed
- Markdown, Excel, Email Center, and Offline Python now use the same official UI foundation. Email's private green palette and custom task rail were removed; all four plugins now use the shared responsive shell/page, notification path, progress treatment, theme tokens, and container-aware narrow layouts. The host sidebar automatically enters rail mode when space is constrained, and both CLI Vue templates emit the same composition for new plugins.
- Convention-based Toolchain 2 CLI. The public command surface is
fengyu init,dev,check, andbuild.fengyu.plugin.jsonand arbitrary command arrays were removed; projects use standard npm scripts and Maven lifecycles, the Worker is discovered as the uniquetarget/*-worker.jar, and packages are written underdist/. The unused direct-ESMdefault.mount(el, ctx)loader was removed; sandboxed iframe + shared protocol is the only UI runtime. offlinepythonRPC method names are lowerCamelCase. Schema v2 forbids dots in method keys, soconfig.get→configGet,requirements.save→requirementsSave,build.start→buildStart, etc. The smoke scripts were updated; external callers of the old dotted names must switch.
🐛 Fixed
- Official plugin layouts now update correctly in real host iframes. Step changes remount their keyed content so restored Excel sessions cannot retain a previous step's layout class; host notification rejection reaches the shared local snackbar; and bundled official plugins are refreshed when same-version
.fypbytes change instead of leaving stale installed UI behind. - macOS no longer shows "compatibility mode" for process isolation. The Settings "Process isolation" badge was a strict
sandboxed ? active : compatibilitybinary, so macOS'ssandbox-execposture (correctly classified by the backend asreduced— a deny-sensitive boundary, not full isolation) collapsed into the no-isolation warning "兼容模式 · 必须明确审批", contradicting the card below it (which keys the compatibility toggle oncompatibilityMode). The badge is now three-state —sandboxed(Linux bwrap) andreduced(macOS) both render green; the yellow "compatibility mode" chip is reserved for Windows Job Object / no-backend. A muted hint explains the macOS deny-sensitive model. Backend behavior was already correct; this is a frontend display fix. - offlinepython no longer crashes on omitted nullable fields, and its logging is no longer silently dropped.
verify'sscopeanddeploy'starget.kindare nullable enums in the manifest (defaulting to ALL / global); omitting them threwNullPointerExceptionbefore the existing null-handling ran. The customOpbLoggerwrapper — wired with a null instance in production — was removed in favour of direct SLF4J, so the package-done milestone now emits and previously silentcatchblocks log. (Excel's analyze/executecatchblocks also warn-log now; cancellation still propagates asCANCELLEDfirst.)
[4.0.0-beta.2] — 2026-08-11
✨ Added
- Settings-driven update channel. The Settings page can now point the app's update check at an intranet/offline FY-Proxy base URL instead of the default GitHub feed. The value is persisted in the
app_settingstore and honored on both update paths: the backendUpdateCheckServicereads it per check (live without a JVM restart), and the Electron shell bootstrapsFENGYU_UPDATE_API_BASEfrom the backend before the first update check and updates it live via anupdate:set-api-baseIPC from the renderer.PUT /api/settingsvalidates the value as an absolute HTTP(S) URL with no credentials, query, or fragment, mirroring the desktopupdate-feed.tsvalidation so both channels accept the same value; invalid values map to HTTP 400. - Full i18n for all official plugins (front + back end). Plugin worker backends now render localized
summary/errormessages, and the Markdown and Excel plugin UIs are fully localized to match the Email and Offline Python Builder plugins. Locale flows per-request from the hostAccept-Languageheader (and the AI chat turn) into the worker, with no change to the JSON-RPC envelope or thePluginHandlersignature:- SDK
1.3.0addsWorkerLocale(per-request locale ThreadLocal bound byJsonRpcWorker.servefrom alocaleparams key),PluginMessages(a classpathi18n/messages[_zh].propertiesbundle resolver), and keyedokKey/failKey/thelpers onPluginHandlerSupport. Workers without bundles keep their prior English behaviour (default localeen). - Host injects the resolved locale on both call paths:
PluginRuntimeController.invokereadsAccept-Language, and the AI path carries the locale through a newAiToolLocaleContextThreadLocal (mirroringAiPermissionContext) bound for the chat turn. - Plugin backends ship
i18n/messages[_zh].propertiesand key their user-facing strings: Markdown, Excel (worker + progress logs), Email (handlers + services), and Offline Python Builder (activating the previously-orphaned bundles). The Offline Python doctor checkid/valueprotocol tokens stay locale-neutral (the frontend translates them, as before). - Markdown + Excel frontends gain a lightweight
i18n.ts+useFengYuEnvironment()composable (mirroring the Offline Python pattern), keying every visible UI string. - Unified production lifecycle. All six plugin-tooling artifacts now agree on
1.3.0.@infinia/plugin-uiprovidesmountFengYuAppandcreateFengYuI18n, so the four official plugin UIs share one ready/theme/locale/mount/pagehide-dispose path. The TS SDK deduplicatesready(), caches and merges environment updates, and exposescurrentEnvironment(). WorkerJobsinherit the initiating request locale, support race-safe cancellation and reject starts after close;JsonRpcWorker.onClosecloses registered resources in reverse order before exit, and the official async workers register their job registries for teardown. Toolchain and official-plugin lockfiles also pick up patchedfast-uri,nanoid,dompurify, andbrace-expansionreleases; their npm audits now report zero known vulnerabilities.
- SDK
[4.0.0-beta.1] — 2026-08-09
✨ Added
- Official Browser Agent. The fifth bundled plugin drives a real Chromium through nine confirmation-aware AI tools for navigation, clicking, typing, DOM inspection, screenshots, waiting, JavaScript evaluation, and session shutdown. Chromium resolution supports a configured system browser, a plugin-managed download, and Playwright fallback.
- Isolated plugin database lifecycle and localized manifests. Database-capable workers now use user-authorized per-plugin credentials with recoverable provisioning/deprovisioning state, while manifest display strings and AI-tool descriptions support locale-family fallback. Plugin logs are persisted per plugin and exposed through ordered REST/SSE history.
- Windows Job Object process isolation backend (
ProcessSandboxWINDOWS_JOB). Plugin workers and AI-authored commands on Windows now run inside a Win32 Job Object configured withKILL_ON_JOB_CLOSE, giving reliable process-tree termination: closing the job handle (orTerminateJobObject) kills the worker and any descendants (e.g. apipsubprocess) without relying onProcessHandle.descendants(), which was unreliable on Windows. The Job Object is a process-layer isolation only — filesystem and network confinement remain a known gap on Windows (the explicit-approval gate still guards every effect there).GET /api/security/process-isolationreportsbackend: "windows-job". JNA 5.19.1 was added for the Win32 binding.
♻️ Changed
- Browser automation moved from
plugin-browser(Playwright) to a host-embedded capability. Browser automation is now built into the desktop application and exposed by the backendBrowserTool, not a.fypplugin. It drives a real browser window through Electron's nativewebContentsand the Chrome DevTools Protocol (CDP) over a loopback HTTP bridge — no Playwright dependency and no separate Chromium download. The nine AI tools (browser_navigate,browser_click,browser_type,browser_get_text,browser_query,browser_screenshot,browser_wait_for,browser_eval_js,browser_close) remain, each approval-gated. The capability is desktop-only: it requires the Electron shell and is unavailable in pure-web / headless mode.
🗑️ Removed
plugin-browser(fan.summer.browser) official plugin. The Playwright-based browser plugin has been removed; its function is now provided by the host-embeddedBrowserTool(see Changed above).OfficialPluginsnow ships four plugins:plugin-markdown,plugin-excel,plugin-email, andplugin-offlinepython.
🐛 Fixed
- Official plugins no longer reappear after the user uninstalls them. Uninstall previously deleted both the package directory and the integrity record, leaving no trace; on the next restart the official-plugin seeder could not distinguish a user uninstall from a never-installed plugin and reinstalled the bundled archive. A persistent uninstall tombstone now marks uninstalled plugins; the seeder checks it before seeding and skips them. A later reinstall (local upload, online upgrade, or a bundled upgrade) clears the tombstone so the cycle is repeatable.
- Local
.fypinstall with a matching.sha256sidecar may now claim official identity. The native install path now verifies a sibling<archive>.sha256sidecar (GNU coreutilssha256sum -cformat — the same credential the official seeder verifies), letting a user install a rebuilt official plugin locally at the same trust level. Without a sidecar (or with a mismatched one) the install stays untrusted, so the existing reservation still blocks official / namespace-squatting. (Asymmetric signature verification remains a tracked follow-up; a sidecar is a tamper/corruption check, not an independent authenticity anchor.) - Excel split no longer falls back to copying the whole sheet after a worker restart. The host tears down and relaunches a plugin worker whenever its file-grant version changes — in the Excel wizard, picking the output folder (Output step) grants the output dir after
configure(Mode step), so the worker servingsplitis a fresh process whose in-memory session store is empty.splitnow re-applies the full split config from its own arguments (fields absent are left untouched, preserving partial-update callers), making the session store a cache rather than a correctness dependency. - Web and desktop dependency advisories. Updated DOMPurify, nanoid, js-yaml, PostCSS, fast-uri, and brace-expansion to patched compatible versions; both npm dependency audits now report zero known vulnerabilities.
- Beta plugin runtime hardening. Official package checksum sidecars now travel with
.fypartifacts through Web/desktop release assembly; plugin DB provisioning/deprovisioning keeps recoverable lifecycle state; JSON-RPC frames, async job logs, and plugin-log SSE replay are bounded and ordered; Windows command Job Object handles are reclaimed on every path. Plugin uninstall now asks whether to retain or permanently delete runtime data and the provisioned DB namespace, and reports deletion failures instead of silently leaving data behind. - Plugin worker processes no longer leak after the host exits. A plugin worker is an independent JVM spawned by the host backend; previously the worker was often not reaped when the host exited (macOS/Windows have no equivalent of Linux's
bwrap --die-with-parent, and Electron only signalled the backend's direct PID with no tree-kill), so leaked workers kept holding the exclusive file locks of embedded databases (H2/SQLite), leaving the database files undeletable. Fixed with four complementary layers of defense:- Worker SDK watchdog (
fengyu-plugin-sdk1.1.0 → 1.2.0): the productionrun()entry point gained dual watchdogs — stdin-EOF (primary) and a parent-liveness poll (auxiliary). On host shutdown/crash the worker auto-System.exits, ensuring it exits and releases file locks even when it holds non-daemon thread pools (HikariCP, etc.). - Explicit host shutdown hook:
HeadlessLauncherregisters a JVM shutdown hook in APP mode that is independent of Spring and callsPluginProcessManager.close()directly, instead of relying solely on the timing of Spring's default hook. - Grandchild-process fallback: after destroying a worker,
PluginProcessManager.Worker.close()recursivelydestroyForciblys its descendant processes (e.g. offlinepython'spipsubprocess) to avoid orphaning them. - Electron tree-kill: the desktop shell adds a
tree-killdependency so that on exit it sends SIGTERM→SIGKILL to the entire backend process tree (including worker grandchildren), as a fallback when the host crashes. - The exit traps in
scripts/e2e-smoke.shandscripts/offlinepython-e2e-smoke.shnow also runpkill -Pto clean up worker subprocess trees.
- Worker SDK watchdog (
- Offline Python Builder deploy now uses the deployment machine's Python. The deploy step previously re-ran Python detection with a null hint, discarding the interpreter resolved from the target — so on machines whose Python (conda/pyenv/venv) was not on
PATH, version detection returnednull, every C-extension wheel (numpy/pandas/…) was judged incompatible, and the deploy silently installed zero packages (or reported success with nothing actually installed).DeployServicenow resolves the version from the target's interpreter and fails loudly if it cannot, instead of masking the failure as an empty match. The Deploy panel also auto-detects this machine's interpreter on load and falls back to a manual path input when detection fails, so offline machines with non-PATHinterpreters can still deploy.
[4.0.0-alpha.8] — 2026-08-04
🐛 Fixed
- Email plugin now works with the default embedded-H2 database. The host holds an exclusive file lock on its own H2 file, so a sandboxed plugin worker could not attach to the same file — H2 rejected the
AUTO_SERVER+ sandbox combination and every email RPC failed at worker boot. Database-permission workers now get their own DB file under their plugin data directory for embedded databases (H2/SQLite), while remote databases (MySQL/PostgreSQL) still share the host URL (real servers handle concurrent connections). The uselessAUTO_SERVER=TRUEoption was also dropped from the host H2 URL template. The e2e smoke now exercises an email RPC end-to-end.
[4.0.0-alpha.7] — 2026-08-04
✨ Added
- Unified plugin store (Claude / Codex / FengYu). A new
/api/plugin-store/*REST surface lets you subscribe to third-party Claude Code and OpenAI Codex marketplaces alongside the FengYu catalog, browse a merged, filtered, source-badged grid, and install Claude/Codex plugins by cloning their git source (JGit) with pinned-sha verification. The frontend ships a unified "Stores" tab with a source manager, install/update/uninstall actions, declared-skills and MCP rendering, and an in-app detail drawer. JGit 7.7.0 was added for clone support. - Windows unsandboxed-plugins toggle. On platforms without a native process sandbox (Windows, or any host where
ProcessSandbox.detect()isNONE), a new Settings row — gated behind a confirmation dialog and defaulting fail-closed — lets a user opt into running plugin workers via theunrestricted()channel (effectiveUnrestricted = fullAccess || unsandboxedPluginsEnabled). AI command-approval and the sandbox fail-closed primitive are untouched; this only unlocks plugin workers.
🐛 Fixed
- Store installer path-traversal (security). A malicious third-party marketplace entry with a
namecontaining path-traversal sequences (../…) could delete or overwrite arbitrary user-writable files, because the raw name flowed unchecked intoskills/<uid>andmcp-servers/<uid>.jsonpaths that are both deleted and written on install. Catalog adapters now slugify the name to a single safe path segment, and the installer asserts every uid-derived path stays inside the runtime root before any delete/write (defense in depth). - Store clone URL scheme validation (security). Clone URLs from third-party marketplace JSON are now restricted to
https/http/file;ftp:,jar:, and bare local paths are rejected before JGit sees them. - Store clone cleanup and timeouts. A failed clone no longer leaves a
.clone-/agent-*temp dir (with.git) behind, and the configuredfengyu.store.git-clone-timeout-secondsis now actually applied to the clone. - Symlink defense in skill extraction (security). A malicious repo containing a symlink whose target escapes the plugin root can no longer leak host-readable files into the runtime tree; the skill walker now skips symlinks and copies with
NOFOLLOW_LINKS. - Codex install integrity. Codex sources (which declare no pinned sha) now record the resolved HEAD commit sha in the install record, so every install carries an auditable content fingerprint instead of
null. - Catalog fetch size cap. Catalog responses are now bounded to 16 MiB, so a malicious or broken catalog URL cannot OOM the backend by streaming an unbounded body into memory.
- Homepage XSS (security). The store detail drawer's Homepage button now allows only
http(s):/mailto:URLs, blockingjavascript:URIs from third-party catalog fields. - Frontend store error surfacing. Install/update/uninstall failures now surface to the user instead of being silently swallowed;
busyis always reset. Malformed catalog array fields are coerced to[]so the template never throws. - Plugin enable/disable marker. Toggling a Claude/Codex plugin's enable state now writes the
.disabledmarker the skill loader reads, so disabling actually stops the skill from loading.
♻️ Changed
- Hardened the unsandboxed-plugins platform gate to rely on
compatibilityMode(isNativeSandboxAvailable()), per the design — the toggle appears on any platform lacking a native sandbox, not strictly Windows.
[4.0.0-alpha.6] — 2026-08-02
✨ Added
- AI chat now has Codex-style action approval profiles. The composer offers Ask for approval, Approve for me, and Full access; command execution and plugin-declared read/write/external effects share one host approval gate. Approval cards stay inside the composer, while calls render as compact progress rows such as
Read FengYu Plugin Dev skill. Plugin manifests can declare an optionalaiTools[].effect, with undeclared third-party effects treated conservatively.
🐛 Fixed
- AI chat now handles Excel file workflows and approval-heavy replies reliably. Attached files/directories are granted to every compatible backend plugin, and existing absolute paths typed in a user message are converted into read-only plugin-scoped FileRefs. Selected directories retain writable access where declared and are injected into single write-directory plugin tool parameters; Excel analysis is preferred before split operations, and unresolved FileRef objects can no longer become map-shaped output folders. IME composition no longer drops a trailing English segment on click-to-send, and final answers render below command approval cards.
✨ Added
- Live visual-workflow tool contracts. Plugin AI tools may declare an optional serialized
outputSchema; the official Excel, Email, and Offline Python tools now publish user-facing input metadata and result-envelope schemas for canvas configuration.
♻️ Changed
- Agent tool discovery now follows the installed-plugin lifecycle. New runs read a live tool registry, while canvas nodes survive tool disable/uninstall, block unsafe execution, and reconcile newly required inputs when the same tool is enabled again.
- A directory the user names as an output target never becomes worker-writable. Instead a plugin-owned staging directory is created per turn and handed to the worker as a writable sandbox root; the host copies its contents to the real target after the turn completes and deletes the staging tree. The real directory stays read-only, so the OS sandbox writable-roots remain stable (one staging grant per turn) and a worker can never overwrite files in a user-named folder. Plugin workers also now receive a per-plugin writable temp directory (
-Djava.io.tmpdir+TMPDIR/TMP/TEMP), andPLUGIN_DATA_DIR_ENVis set for every plugin rather than only database-capable ones. macOS sandbox writable-roots are canonicalized viatoRealPath()before the profile is built (resolves/var→/private/var). - The chat tool-loop cap is now configurable. A new
ai.max_tool_roundssetting (default 50,0= unlimited) replaces the previously hard-coded per-backend limits and bounds the number of tool-call rounds a turn may take, stopping a model that re-requests the same tool from wedging the virtual thread and locking the backend. It is editable in Settings → AI. - Bumped Apache POI to 5.5.1.
🐛 Fixed
- The permission-mode menu is now disabled while a generation is in flight, so an approval profile can no longer be switched mid-turn.
[4.0.0] — 2026-07-29
✨ Added
- Built-in
fengyu-plugin-devskill. A second built-in skill (alongsidefengyu-features) that teaches the in-app assistant the 4.0.0 plugin model:.fyppackages (sandboxed iframe UI + out-of-process JSON-RPC worker), themanifest.jsonfields, the permission enum,aiTools, and thefengyuCLI build/install flow. It is authored for the app's runtime context, distinct from the repo's agent-workflow skill of the same id.
♻️ Changed
- Removed the legacy
FengYu-Apimodule. Host-only AI contracts, tool categories, and theme state now live in the headlessFengYuapplication module. The obsolete JavaFX preview assets and in-process plugin logging bridge were deleted, and Maven no longer manages JavaFX artifacts. - Unified the application version at 4.0.0. Maven, the frontend, Electron shell, built-in skills, and official plugin packages now use the stable version.
- Scoped the VitePress toolchain to
docs/. The documentation package manifest, lockfile, installed dependencies, local commands, and CI cache now live with the documentation sources instead of occupying the repository root.
[4.0.0-alpha.5] — 2026-07-29
✨ Added
- Agent runs are now durable. Each plan-and-execute run is snapshotted to the database (
ai_agent_run) with a sequenced lifecycle-event append log (ai_agent_run_event) coveringplan_ready,plan_approval_requested,step_start/step_complete,step_approval_requested,complete, anderror/cancelled. History is listable/detailable per user, a failed or cancelled run can be resumed from its last completed step, and on restart any non-terminal in-flight run (PLANNING / AWAITING_*_APPROVAL / EXECUTING) is reclassified as FAILED. Persistence failures are logged but never kill a healthy run. - Sensitive tool calls require explicit user approval. A new
ApprovalRequiredToolcontract marks host tools that must never run without confirmation. In ordinary chat,ChatToolApprovalGateblocks each model response containing such calls on a confirmation card (5-minute expiry), and in the Plan-and-Execute Agent each step pauses for the same gate. Cancelling a generation or swapping the backend rejects all pending approvals. execute_commandtool with OS-level process sandboxing. AI-authored shell commands run inside a native isolator when one is available —bwrap(bubblewrap) on Linux andsandbox-exec(Seatbelt) on macOS — with read-only system files, writes confined to the working directory, and the network isolated unless explicitly opted in. Inherited environment variables holdingTOKEN/SECRET/PASSWORD/API_KEY/CREDENTIAL/COOKIE/AUTHORIZATIONare stripped before launch, output is bounded (default 64 KiB, max 256 KiB) with truncation flagged, and a bounded timeout (default 30s, max 600s) forcibly terminates descendants. Where no isolator exists the tool falls back to direct execution and disclosescompatibilityModein the result; approval stays mandatory regardless.GET /api/security/process-isolationreports the active backend.- MCP (Model Context Protocol) client. MCP servers configured via
spring.ai.mcp.client.*are connected at startup and surface their tools to the Agent.GET /api/mcp/statusreports the enabled flag, connection/tool counts, and per-connection detail (name, version, protocol version, initialized). - The host and Java plugin Workers now share one live log level. The Settings page persists
TRACE,DEBUG,INFO,WARN,ERROR, orOFF, applies it to the host's Logback namespaces, and pushes it to running Workers without a restart. The Java Worker SDK replacesslf4j-simplewith a structured stderr provider, preserving logger name, thread, level, message, and exception stack while keeping stdout reserved for JSON-RPC; legacy free-form stderr remains supported.
🔒 Security
- Runtime secret files are owner-only on POSIX.
SensitiveFilePermissionsappliesrwx------to secret/key-material directories andrw-------to their files on macOS/Linux (no-op on Windows, where user-profile ACLs apply).
♻️ Changed
- Default runtime state is self-contained under the launch directory. Without an explicit
fengyu.runtime.dir, the app now stores embedded databases in<program-working-directory>/.fengyu/database/and configuration, logs, plugins, skills, and other writable state under<program-working-directory>/.fengyu/. - Plugin Workers are sandboxed per manifest permissions.
ProcessSandbox.plugin(...)confines each Worker's writes to its plugin-owned roots (broadened whenfiles.writeis declared) and isolates the network according to the manifest, on supported isolators.
[4.0.0-alpha.4] — 2026-07-28
🐛 Fixed
- Plugin UI icons now render reliably in sandboxed third-party plugins. The host CSP explicitly permits same-origin and bundled
data:fonts for compatibility with existing.fyppackages, while@infinia/plugin-uileaves its MDI stylesheet to the consuming Vite app so new builds emit ordinary hashed font assets instead of embedding multi-megabyte fonts in the library CSS. Official plugin UIs now declare@mdi/fontdirectly so the Vite build can resolve the externalized@importunder strictnpm ci. window.fengyuaccess is SSR-safe. The desktop-bridge calls added tosettings.ts,main.ts, androuter/index.tsare now guarded withtypeof window !== 'undefined', so they no longer throwReferenceErrorin vite SSR / thenode --testsuite.
♻️ Changed
- Windows desktop releases ship an extract-and-run ZIP instead of a self-extracting portable
.exe. Both the lite and JRE variants keep the NSIS installer (*-win-x64-setup.exe) and now publish*-win-x64-portable.zip(extract once, then runInfinia.exe); the startup-time self-extraction of the old portable executable is gone. Artifact names were unified to<product>-<version>-<platform>-<arch>[<form>].<ext>across macOS / Windows / Linux (Infinia-4.0.0-mac-arm64.dmg,Infinia-4.0.0-win-x64-setup.exe, …), and the release workflow + its contract test were updated to match. - Desktop startup no longer flashes and no longer requires a live backend to pick a route. The shell shows a splash window while it probes the backend, exposes the pre-probed setup state and the chosen theme to the renderer via
window.fengyu.setupMode()/initialTheme()/setTheme(), and the router consumes that snapshot on first navigation before falling back to live checks.
🔧 Internal
- Backend runtime directories are centralized. Plugin, skill, plugin-data, and transient-file directories are now derived from one stable root via the new
RuntimePathsand overridable throughfengyu.runtime.dir(default~/.fengyu), replacing scatteredSystem.getProperty("user.dir")paths.CryptoUtilderives the.machineidfrom the same root. This makes the packaged Electron shell and the portable Web distribution agree on where state lives. - Portable Web distribution keeps its state self-contained.
run.sh/run.batnow pass-Dfengyu.runtime.dir=<dist>/data, so the database, config, logs, and plugin data land next to the launcher inside the extracted folder rather than in the user's home directory — preserving the "unzip and run, move/delete leaves nothing behind" portability contract.scripts/e2e-smoke.shpins the same property to its temp dir so its run is repeatable.
[4.0.0-alpha.3] — 2026-07-25
♻️ Changed — Desktop shell: Tauri → Electron
- The desktop shell was rewritten from Tauri 2.0 (Rust) to Electron 43 (TypeScript). The backend lifecycle is unchanged — Electron spawns the backend JAR over loopback, waits for
/api/health, and hands the token + api-base to the renderer via acontextBridgepreload (window.fengyu). This replaces the old Tauriwindow.__FENGYU_*globals. - Two release variants per platform. Each platform ships a lite build (user supplies Java 21+ on
PATH) and a JRE build that bundles ajlink-minimized JRE under<resources>/jre/:- macOS (arm64 + x64) —
Infinia-<ver>-mac.dmgandInfinia-<ver>-mac-jre.dmg. - Windows (x64) —
Infinia-<ver>-win.exe(NSIS installer) and a portable exe; plus the-jre.exeNSIS variant. - Linux (x64) —
Infinia-<ver>.AppImage/.deband the-jre.AppImage.
- macOS (arm64 + x64) —
- Auto-updater (
electron-updateragainst GitHub Releases), system tray (hide-to-tray on window close, backend stays alive until app quit), single-instance lock, and file logging (electron-log→~/.fengyu/logs/desktop.log). - Dev mode defaults to connecting an IDE-started backend at
http://127.0.0.1:24056(no spawn, no token, no supervisor); setFENGYU_JAR=<jar>(orFENGYU_DEV_BACKEND=disabled) to make the shell spawn its own backend with the full release lifecycle. - Security posture:
contextIsolation: true,nodeIntegration: false,sandbox: true. Navigation guards —setWindowOpenHandlerdelegateshttp(s)targets to the system browser and denieswindow.open('file://...');will-navigateblocks cross-origin in-page navigation. See docs/en/architecture/desktop.md.
♻️ Toolchain directory consolidation
- Consolidated 7 plugin toolchain directories (2 Maven + 4 npm + schema) into
toolchain/, flattening intermediate layers and unifying short semantic names (sdk-java/devkit-java/sdk-ts/ui/dev/cli/spec):FengYu-Plugin-Sdk→toolchain/sdk-javaFengYu-Plugin-DevKit→toolchain/devkit-javaplugin-sdk/typescript→toolchain/sdk-tsplugin-ui/vue→toolchain/uiplugin-dev→toolchain/devplugin-cli→toolchain/cliplugin-spec→toolchain/spec
- CI/release workflows and skills were renamed to
toolchain-*(plugin-tooling.yml→toolchain-ci.yml,plugin-tooling-release.yml→toolchain-release.yml). The tag prefixplugin-tooling-v*is unchanged. - Maven artifactIds and npm package names are unchanged:
fan.summer.fengyu.sdk:fengyu-plugin-sdk,fan.summer.fengyu.sdk:fengyu-plugin-devkit, and@infinia/plugin-sdk/@infinia/plugin-ui/@infinia/plugin-cli/@infinia/plugin-devare still published under their original names. The repo directories changed; the coordinates did not.
✨ Added
- Skills — a third extension surface (peer to plugins and AI tools) using Codex-style progressive disclosure. Enabled skills appear as a compact catalog in the system prompt, and the assistant loads a skill's full body on demand via the built-in
skilltool — so large guidance documents never bloat the per-request token budget.- Managed like plugins: skills are packaged as
.fysarchives (zip:manifest.json+SKILL.md) and installed under~/.fengyu/skills/<id>/— a filesystem peer of~/.fengyu/plugins/<id>/. The full install/uninstall/enable/disable lifecycle mirrors the plugin system (atomic publish + backup rollback inSkillPackageService). - Marketplace:
fengyu.skills.catalog-urlpoints at a remote catalog JSON;SkillMarketplaceServicemerges remote entries with local install state (the lifecycle twin ofPluginMarketplaceService). Install/update by id from the catalog. - Enable state is a
.disabledfilesystem marker (not a DB row), exactly like plugins, so it survives reinstall. - OfficialSkillSeeder (
ApplicationRunner) idempotently seeds bundled.fysartifacts on boot, mirroringOfficialPluginSeeder. - Two discovery sources: built-in (
classpath:/skills/<id>/SKILL.md, packaged in the JAR, cannot be uninstalled/disabled) and installed (.fyspackages under~/.fengyu/skills/). Installed skills override built-ins on id clash. - New REST surface under
/api/skills: list, detail, market, upload, upload-native, install, update, PATCH enabled, DELETE (409 for built-in skills). All requireX-FengYu-Token. - Frontend: skill management is integrated into the Plugins page (
/plugins) — a Codex-stylePlugins | Skillstab pair at the top switches the view, with an installed fast-row and a card grid. A single Upload button accepts both.fypand.fysarchives and routes each by extension. No separate/skillsroute. - Built-in example skill
fengyu-features(answers "what can FengYu do"), now with amanifest.jsonalongside itsSKILL.md. - Skills are decoupled from plugins — they never touch
plugin-spec/or a plugin manifest. - See docs/en/skills/ and docs/zh/skills/.
- Managed like plugins: skills are packaged as
- IDE plugin debugging (plugin toolchain 1.1.0) —
fengyu plugin devis replaced by an IDE-native flow so third-party authors debug UI and worker with real breakpoints, no JDWP remote attach.@infinia/plugin-dev(new npm package,plugin-dev/) — a Vite plugin that turns the dev server into a FengYu host simulator: serves the iframe shell at/__fengyu, bridges@infinia/plugin-sdk'spostMessagecalls, and forwardsrpc.invoketo the dev worker over loopback TCP.fengyu-plugin-devkit(new Maven artifact,fan.summer.fengyu.sdk:fengyu-plugin-devkit,FengYu-Plugin-DevKit/) — a loopback-only TCP JSON-RPC server (PluginDevServer) that drives the worker'sserve(RpcTransport)loop. Scaffolded asPluginDevMainunderworker/src/test/java; declared<scope>test</scope>so it never ships in the shaded JAR.RpcTransportabstraction in the Java Worker SDK —JsonRpcWorker.serve(RpcTransport)shares the dispatch loop between production stdio (StdioTransport) and the devkit's loopback socket.run()/run(InputStream, OutputStream)are unchanged in behaviour.- The scaffolder now generates a shared
<Prefix>Worker.create()handler factory, the production<Prefix>WorkerMain, and the IDE-debugPluginDevMain. UI-only scaffolds setmockWorker: true.
- Real LLM planner + visual canvas workflow builder (AiAgent). The empty
StubPlanGeneratoris replaced byChatBackendPlanGenerator, which asks the active AI backend for a validated structured workflow while keeping tools disabled during planning (newChatBackend#chatWithoutToolsdefault — bothOllamaLocalBackendandSpringAiCloudBackendhonor the toggle).AgentRunnervalidates every workflow (model- or user-supplied) before any tool runs, resolves step-result references (\{\{steps.N.result\}\},\{\{last.result\}\}), and accepts a caller-supplied workflow viaPOST /api/agent/runso the HTTP API can drive deterministic execution. The frontend gains a Vue Flow canvas (AiAgent.vue) with a tool palette,WorkflowToolNode, andworkflow.tsthat compiles the graph into theAgentPlansent to the backend — a no-code peer to the AI plan path. EN/ZH strings updated. Also: the Electron main window starts hidden on a dark surface and is revealed only on first paint (or load failure), removing the white flash on cold start.
🐛 Fixed
- IDE Worker failures no longer look successful. When
workerEndpointis configured,@infinia/plugin-devreturns connection failures as RPC errors instead of silently substitutingdevMockdata. All official plugin UIs now expose the documentednpm run deventry point. - Plugin-tooling release gates now ship the canonical manifest schema, exempt the independently versioned Worker SDK from the application-parent check, and resolve the patched
fast-uri3.1.4. - First-launch (SETUP mode) no longer crashes.
SkillControllerwas component-scanned into the DB-less SETUP context but depends onSkillRegistry/SkillPackageService/SkillMarketplaceService(in theai.skillpackage, which SETUP mode does not scan), causing anUnsatisfiedDependencyExceptionthat aborted startup before the database wizard could run. It is now excluded fromSetupApplication's scan alongside the other APP-only controllers. - B1 — actuator
restartendpoint removed from the default exposure.application.ymlnow setsmanagement.endpoints.web.exposure.include: healthonly. The/actuator/restartendpoint was reachable, and in the Web bundle's default no-token posture any loopback process could force a context restart (DoS). The SETUP→APP restart already goes throughSystem.exit(SETUP_DONE)+ the desktop supervisor, so no functionality is lost. - B2 — Web bundle generates a per-launch token by default.
distribution/web/run.shandrun.batnow generate a random--token=when the user passes none (previously auth was disabled by default). Explicit--token=<t>still overrides. - D1 — desktop navigation guards.
setWindowOpenHandlerdenieswindow.openand delegateshttp(s)targets to the system browser;will-navigateblocks cross-origin in-page navigation. Prevents a compromised page fromwindow.open('file://...'). - D2 — auto-updater skips the JRE variant. JRE-bundled builds detect
resourcesPath/jreand skip the update check — the updater feed only references the lite variant, so auto-update would silently downgrade JRE users to the Java-dependent lite build. Full per-variant feeds are deferred. - D3 — supervisor
stop()is now saved and called.main.tsstores thesuperviseSetupRestartreturn value and calls it inkillBackend()(defensive; prevents a future leak if a persistent listener is ever added). - D4 — APP-mode backend crash shows a dialog. A lightweight exit listener in APP mode shows
dialog.showErrorBoxand quits on an unexpected backend crash (previously silent — the user saw only connection errors). The alpha does not auto-restart. - Desktop — right-edge dark strip on the window. The shell allowed a document-level scrollbar whose transparent track exposed Electron's native window backing as a thin dark line on the right.
html/body/#appnow setoverflow: hidden— the shell owns scrolling inside its panes (sidebar history, chat column) and no document scrollbar is ever created. The window'sbackgroundColoris also aligned to the dark theme (#0d0d0d) so the native backing never contrasts with the renderer. - Electron migration & tooling gates hardened. New
desktop/electron/scripts/verify-frontend-dist.mjsblocks a desktop build iffrontend-dist/is missing or stale;backend/spawn.ts,supervisor.ts,util/health.tsandmain.tsreceived additional lifecycle hardening with new unit tests (health.test.ts,spawn.test.ts, expandedsupervisor.test.ts). - Plugin toolchain —
sdk-tslockfile desynced from 1.1.0 (root version stayed at 1.0.0); regenerated so the root andpackages[""]agree. Also forcedbrace-expansion=5.0.8vianpm overridesintoolchain/uito clear 6 high-severity audit findings on@vue/test-utils → js-beautify → … → brace-expansion@2.1.2(no upstream fix exists on the 2.x line). - Splash screen — shipped in the JRE build variant.
electron-builder.jre.ymlhad not been synced with the lite config whenresources/splash.htmlwas added to the desktop asar, so the self-contained JRE build (the flagship download) silently never showed a splash. The file list now matcheselectron-builder.yml, and both configs includeresources/splash.html. - AI planner timeout no longer wedges the backend. When a planning call exceeded its 180s budget (e.g. a hung Ollama process or a stalled provider connection),
ChatBackendPlanGeneratorgave up but the underlying stream kept blocking onblockLast()with no way to interrupt it. BothOllamaLocalBackendandSpringAiCloudBackendnow hold the ReactorDisposableand await aCountDownLatchinstead ofblockLast(), socancelGeneration()candispose()the stream mid-flight and release the worker. The planner callscancelGeneration()on any timeout/failure path, guaranteeing thegeneratingflag is cleared and every subsequentchat/ planning request no longer fails with "Generation already in progress". Covered by a new regression test (ChatBackendPlanGeneratorTest).
♻️ Changed
- CLI scope narrowed to
create+build.fengyu plugin devmoved to the IDE (@infinia/plugin-dev+fengyu-plugin-devkit);fengyu plugin validateis now a built-in step ofbuild(the staging tree is always validated before packaging);fengyu plugin installis done through the host's plugin marketplace UI (POST /api/plugin-market/upload).--port,--host,--token, and--ui-portCLI flags were removed with their commands. - Plugin toolchain locked at six artifacts, all released together as
plugin-tooling-vX.Y.Z: the Worker SDK, the devkit,@infinia/plugin-sdk,@infinia/plugin-ui,@infinia/plugin-cli, and@infinia/plugin-dev.plugin-cli/scripts/resolve-tooling-version.mjsverifies all six.
🗑️ Removed
fengyu plugin dev,fengyu plugin validate, andfengyu plugin installCLI subcommands and their source (plugin-cli/src/dev.mjs,worker.mjs,install.mjs). Development now happens in the IDE via@infinia/plugin-dev; theFENGYU_DEBUGJDWP remote-attach workaround is no longer needed (runPluginDevMainand set breakpoints directly).
[4.0.0-alpha.1] — 2026-07-19
First public alpha of the 4.0 line. Infinia (FengYu) is re-architected from a JavaFX desktop app into a headless web + desktop application: a loopback-only Spring Boot backend, a Vue 3.5 + TypeScript SPA (identical for browser and desktop), and a Tauri 2.0 desktop shell that sidecar-launches the backend. Built-in tools become isolated .fyp plugins — a sandboxed iframe UI talking to an out-of-process JSON-RPC 2.0 worker. This alpha publishes unsigned Windows/macOS/Linux Tauri packages and a portable, loopback-only Web distribution.
⚠️ Breaking Changes
- JavaFX is gone. All JavaFX code and dependencies are deleted —
FengYuApp, theui/shell, all built-in tool UI classes, the v1PluginRegistry/PluginLoader, and everyorg.openjfx:*dependency. The running backend is headless (no window). - New entry point:
fan.summer.fengyu.HeadlessLauncher(wasfan.summer.Launcher). It boots a loopback Spring Boot web server:java -jar FengYu-4.0.0-alpha.1.jar --port=<n> --token=<t>. - Plugin contract v2 (
FengYuPluginV2):descriptor()+invoke(action, args)(JSON-in / JSON-out) +aiTools(). The oldcreateView()→ JavaFXNodecontract is removed; UI is now a separately-served micro-frontend ESM bundle (PluginDescriptor.uiEntry). IconStyledecoupled from JavaFX — colours are RGB ints +getColorHex()(nojavafx.scene.paint.Color).- Database layer migrated from MyBatis to Spring Data JPA + Hibernate 7 — see Removed.
✨ Added
- Headless backend (
fan.summer.fengyu.web.*):GET /api/health,GET /api/plugins,POST /api/plugins/{id}/invoke,GET /plugin-ui/{id}/**(serves MF bundles),GET/PUT /api/settings,POST /api/ai/chat+GET /api/ai/stream(SSE: token / thinking / tool / done / error). Loopback-only bind + per-launchX-FengYu-Tokenauth (?token=for the SSE stream). - Alpha desktop + web release pipeline —
v4.0.0-alpha.1publishes unsigned Windows/macOS/Linux Tauri packages and a portable, loopback-only Web distribution. The Vue SPA is baked into the shaded backend JAR (static/) and served by a newSpaForwardController; a release-tag resolver (scripts/resolve-release-version.mjs) drives the version strings, andscripts/package-web-release.shtest-web-release.shassemble and smoke-test the archive. Code-signing, a bundled JRE, and the auto-updater remain deferred to a later release.
- Multi-datasource setup wizard: first launch guides users through database selection (H2 / SQLite / MySQL / PostgreSQL) with connection testing and automatic schema initialization. The backend boots in SETUP mode (minimal Spring context, no JPA) when
~/.fengyu/config/datasource.propertiesis absent, and APP mode (full context) once it exists. The Tauri/desktop supervisor restarts the sidecar after the wizard completes. - JPA migration: the database layer migrated from MyBatis to Spring Data JPA + Hibernate 7 (
ddl-auto=update). All 14 entities ported with@Entityannotations; 14 Spring Data repositories replace the MyBatis mappers. - User-system groundwork:
sys_user/sys_sessiontables,user_idrow-level isolation on all user-scoped tables, and pluggableAuthProvider/SecurityContextinterfaces with a Noop implementation (login UI deferred to a later phase). Local offline mode attributes all data to a single virtual user (id=1, "Summer"), created on APP-mode startup. - AES-GCM encryption (
CryptoUtil) for the datasource password field indatasource.properties— keys are machine-bound via a per-machine UUID. - Official plugin UI kit
@infinia/plugin-ui— a Vuetify 3 (Material Design 3) component library for generated plugins. ShipsFyPluginShell,FyPageHeader,FyToolbar, SDK-backedFyFilePicker/FyDirectoryPicker,FyStepWizard,FyTaskTable,FyNotificationCenter, theFyEmptyState/FyLoadingState/FyErrorState/FyPermissionNoticestate panels, andFyConfirmDialog, pluscreateFengYuVuetify,bindFengYuEnvironment, andprovideFengYuClientso the scaffoldedmain.tsbinds the host theme/locale automatically. - Email Center
.fyp(fan.summer.email) with a sandboxed five-tab Vue/Vuetify/TipTap UI, an isolated official-SDK Java Worker, and package permissions limited to database, email network, and authorized file read/write capabilities. Multi-account SMTP/IMAP configuration, AES-GCM credential storage, address-book/tag CRUD, confirmed single and batch sending, manual IMAP.emlcollection, archive search/detail, and seven manifest-declared AI tools. Plugin-owned tables use theFengTu_PL_Email_namespace across H2, SQLite, MySQL, and PostgreSQL. - Excel Splitter
.fyp(fan.summer.excel) — BY_SHEET / BY_COLUMN / COMPLEX split modes with a stateful four-step wizard, six manifest-declared AI tools, and authorized file read/write. - Markdown Editor
.fyp(fan.summer.markdown) — first official v2 plugin: server-side commonmark render viainvoke("render", {markdown})plus a Vue split-editor + live preview. frontend/— Vue 3.5.39 + TS shell: sidebar (collapsible, categories), theme (dark/light), settings, AI chat (SSE + markdown + collapsible thinking), ToolGrid, and a micro-frontend host that dynamically imports each plugin'suiEntry.desktop/— Tauri 2.0 shell: spawns the Java sidecar (--port=24056), readsFENGYU_PORTfrom stdout, polls/api/health, injects the backend URL + token into the webview, kills the sidecar on close.- Publishable plugin toolchain — the Java Worker SDK (
fan.summer.fengyu.sdk:fengyu-plugin-sdk, independently versioned, GitHub Packages) and the npm packages@infinia/plugin-sdk,@infinia/plugin-ui,@infinia/plugin-cli, plus a release workflow with a clean-consumer smoke job. - Default Vue + Java scaffold —
fengyu plugin createproduces a complete plugin by default: a Vue/Vuetify UI (ui-src/) backed by a Java JSON-RPC worker (worker/), with the Maven Wrapper, a build declaration, tests, and a GitHub Packagessettings.xml.--ui-onlyretains the lightweight UI-only template. - Real-worker dev simulator —
fengyu plugin devbuilds the worker JAR (if missing), starts the real Java JSON-RPC worker, and forwards the UI'srpc.invokecalls overPOST /__rpc. Java source edits trigger a debounced rebuild + worker restart. - Declared build lifecycle —
fengyu.plugin.jsondrives an ordered, atomic pipeline (prepare → install → test → build → validate staging → package) with the Maven Wrapper (no system Maven fallback).--skip-testsskips tests only, never type checking or packaging. - Shared manifest contract — a canonical
plugin-spec/manifest.schema.json+ fixtures shared by the CLI and the host, includingdatabaseandnetwork.emailpermissions and AI-toolmethod/ object-schema validation. - Offline Python Builder
.fyp(fan.summer.offlinepython) — doctor, dependency search, project init, async wheelhouse builds with streamed logs, and output verification.
♻️ Changed
- Vuetify 3 (Material Design 3) adoption — full visual-language switch for the web shell and plugin micro-frontends, from the legacy
--sk-*IntelliJ-token system to MD3. Theme driven by Vuetify's global singleton fromuseThemeStore; plugins share the host's Vuetify instance viaPluginContext.vuetify. - Stateful plugin workflows —
@infinia/plugin-uiprovides a controlled, persistent-ready step wizard with explicit states, async validation, branching, invalidation, and snapshots; the official Excel plugin adopts it with reload re-analysis and configuration replay, worker-faithful mode validation, safe output reselection, and explicit completion/download. - Refined plugin-ui surface —
@infinia/plugin-uigains a theme-driven polish layer so plugins usingFyPluginShellshare one calm, low-elevation design language (hairline borders, soft primary active chips, a brand marker, de-uppercased buttons, denser fields/tables). Every color resolves through a Vuetify theme variable; the email green palette is explicitly excluded. - Node.js 24.18.0 baseline — documentation,
plugin-cliengine metadata, and every GitHub Actions workflow now use the same exact Node.js version, protected by a repository contract test. - Official plugins built by the CLI — Markdown, Excel, and Email are packaged by
fengyu plugin build(a CI matrix); the legacy shell packager and centralized source manifests are removed. - Offline-first install —
fengyu plugin installvalidates the archive (limits, paths, manifest) before any network access; unsafe or invalid packages are rejected with zero fetch calls. - Strict SDK RPC contracts — the worker surfaces canonical JSON-RPC errors (
-32700parse,-32600invalid request,-32601unknown method,-32000handler failure); the TypeScript client removes abort listeners on every settled path. - HeadlessLauncher now selects
SetupApplication(SETUP) vsAiApplication(APP, withfengyu.mode=app) based ondatasource.propertiespresence; the desktop host restarts the sidecar onSETUP_DONE(exit 0) to enter APP mode. AiConfigService/AiConfigServiceHeadless/EmailUtilconverted from static utilities to Spring beans scoped bySecurityContext.currentUserId(). Setup-wizard endpoints (/api/setup/*) bypass token auth (TokenAuthFilter).- Email batch sending creates one message per parsed attachment tag; all matching contacts share the To/CC fields, To takes precedence over CC, and failed-item retry was removed.
🗑️ Removed
DatabaseInit, all MyBatis mapper interfaces (12),mybatis-config.xml, all mapper XML (12), and the MyBatis dependency.- All JavaFX code and dependencies (see Breaking Changes above).
🐛 Fixed
- Headless fat-jar boot: aligned
logback-classic/logback-coreversions (a split pair crashed onJaninoEventEvaluatorBaseat first logger init); added shadeAppendingTransformerforAutoConfiguration.imports(Spring Boot 4 splits web/Tomcat autoconfig across module jars — without merging, embedded Tomcat silently never started); emit-parametersso Spring MVC resolves@PathVariable/@RequestParamnames. VirtualUserInitializernative INSERT now runs inside@Transactional(was throwingTransactionRequiredException).- Atomic
.fyppackaging: a failure at any stage leaves no.fyp, no.tmp-*, and no staging dir. - Offline Python Builder now opens a writable project workspace, passes complete
FileRefobjects through the host bridge, reports translated job states, stops failed polling, and performs real build/deploy cancellation instead of changing UI state only. - Email archive timestamps on SQLite (including upgrade migration), literal wildcard search, account/folder path isolation, UTF-8 filename limits, and temporary-file cleanup.
[3.2.0] — IDEA 2025 New UI Redesign
v3.2.0 — 2026-07-07
This release re-skins the app from glassmorphism-dark to the JetBrains IDEA 2025 New UI look: a flat, token-based theme with switchable dark / light themes, a collapsible sidebar, and native OS window chrome. Theming is driven by JavaFX looked-up color tokens (-sk-*) declared per theme on the scene root, so a theme switch is just a root class swap — no stylesheet reload.
⚠️ Breaking Changes
.glass-*CSS utility classes renamed to.sk-*(infengyu-common.css). External plugins that callgetStyleClass().add("glass-...")or reference.glass-*selectors must update. The full mapping:old new glass-dialogsk-dialogglass-field/glass-field-labelsk-field/sk-field-labelglass-tab-panesk-tab-paneglass-combosk-comboglass-tablesk-tableglass-checkboxsk-checkboxglass-btn-primary/glass-btn-secondarysk-btn-primary/sk-btn-secondaryglass-notif-*sk-notif-*The external plugin repo (
MuskStark/FengYu-Plugin) is updated separately; flag this rename when migrating third-party plugins.
🎨 Theme (strict tokenization)
- Token set expanded 14 → 19 — added
-sk-shadow,-sk-scrim,-sk-success-soft,-sk-warning-soft,-sk-danger-soft(each under both.theme-darkand.theme-light). Custom themes/stylesheets that hardcoded the old 14 must add these 5 or popups/dialogs/cards will have undefined shadows and status soft-fills. - Fixed popups rendering as un-themed white —
GlassNotification(toast/notify/confirm) loaded the stylesheet but never stamped the theme class on its scene, so every-sk-*token was undefined and all popups fell back to JavaFX default white in both themes. Root-cause fix viaThemes.applyTo(scene). - Removed all hardcoded colors from popups, dialogs,
StepWizard,ToggleSwitch, status labels, and CSS drop-shadows. Everything now resolves through-sk-*tokens and adapts correctly to dark and light themes. NotablyStepWizardidle dots andToggleSwitchoff-track were invisible on the light theme.
✨ New
- Dark / light theme system —
fan.summer.api.theme.ThemeService(API module, no DB dependency) holds the activeTheme.DARK/Theme.LIGHT, stamps atheme-dark/theme-lightclass on every registered scene root, and firesonChangelisteners. Switchable from the sidebar footer (☀/☾) and the Settings page; persisted in thethemesetting (darkdefault). - Looked-up color tokens (
-sk-bg,-sk-bg-elevated,-sk-text,-sk-accent,-sk-border, …) declared per theme infengyu-common.css; swapping the root class re-resolves every token with no stylesheet reload. - Collapsible sidebar —
«/»toggle between the label view and a 48px icon-strip; collapse state persisted via thesidebar.collapsedsetting. - Native window chrome —
StageStyle.DECORATEDgives the real OS title bar + close/min/max (macOS traffic lights), replacing the custom transparent window. MarkdownRenderer.render(md, Theme)/renderPlain(md, Theme)overloads (theme-aware dark/light CSS palettes); no-arg forms delegate viaThemeService.current().
♻️ Changed
fengyu-common.cssrewritten: token definitions under.theme-dark/.theme-light, every component flattened to IDEA New UI style (neutral-gray selection with a left accent bar, slim 4–8px scrollbars, flat fields/buttons/tables/tabs/dialogs/notifications), all.glass-*→.sk-*.shell.cssrewritten token-based for the New UI shell (.app-root,.sidebar+.collapsed, capsule.search-bar, flat.tool-card,.detail-panel,.statusbar,.store-*).Themes.applyTo(scene)now delegates toThemeService.registerScene(scene)(loads the common stylesheet + stamps the theme class); the shared stylesheet load is factored intoThemes.loadCommonStylesheet(scene)to keep the delegation non-recursive.FengYuAppreads the persisted theme on startup and registers the main scene withThemeService.AiChatPluginderives its WebView background from the active theme and re-renders the conversation live on theme change.- Inline
#5b8cf7accent literals replaced with#3574F0/ the dark palette across the sidebar and Markdown link CSS.
🔥 Removed
fan.summer.ui.titlebar.TitleBar— replaced by native OS window chrome.fan.summer.ui.util.WindowResizeHelper— nativeDECORATEDresize/drag/maximize replaces it; the macOSisMaximized()-on-TRANSPARENTbug is gone with it.
[3.1.0] — LangChain4j ChatBackend + Plugin-Owned AI Tools
v3.1.0 — 2026-06-25
This release rebuilds the AI subsystem on LangChain4j and unifies the two cloud providers (OpenAI + Anthropic) into a single CloudChatBackend class behind a new ChatBackend interface. Plugins can now self-declare their own AI tools. The local tool-calling model is Qwen3-4B (Hermes <tool_call> + streamed <think> reasoning), running in a hardened out-of-process worker.
⚠️ Breaking Changes
AiServiceinterface removed — replaced byChatBackend. External plugins callingAiServiceProvider.getService()must change the return type fromAiServicetoChatBackend. Seedocs/migration-3.1.mdfor the migration guide.OpenAiServiceandAnthropicServiceconcrete classes removed — replaced by a singleCloudChatBackendclass withopenAi(...)/anthropic(...)static factories. One unified class serves both providers.CloudAiConfigProviderand standaloneStreamingResponseHandlerBridgeremoved — their logic moved intoCloudChatBackend(config accessors are public methods on the class; the stream bridge is a private inner class).AiServiceImplrenamed toLocalChatBackend— pure rename, no behavior change.BuiltinAiToolRegistrarremoved — plugins now self-register AI tools viaFengYuPlugin.aiTools(); the central registrar and its startup call are gone.
✨ New
- Plugins self-declare AI tools via
FengYuPlugin.aiTools()— the registry auto-registers/unregisters them on add/remove (including JAR hot-reload). No central registrar. AiToolinterface declares per-mode visibility (supportsLocal/supportsCloud) and dual descriptions (getDescription/getLocalDescription);AiServiceProvider.getTools()filters by the active backend mode.AiToolDescriptionshelper centralises cloud-rich / local-concise description templates.- Qwen3-4B local tool-calling — Hermes
<tool_call>parsing (ToolCallParser),ThinkingStreamSegmenter(THINK / CONTENT / tool-call stream splitting),Qwen3Adapter(Hermes system prompt +/no_thinktoggle), and a collapsible thinking card in the chat UI. - New
ChatBackendinterface (fan.summer.api.ai.ChatBackend). - New
CloudChatBackendclass withopenAi(...)/anthropic(...)factories. LocalChatBackend(renamed fromAiServiceImpl).AiToolCall.of(id, name, arguments)overload to preserve server-issued tool-call IDs when bridging from LangChain4j.- Tests:
CloudChatBackendTest(11) + adapter tests forChatMessageMapper/AiToolToToolSpecification;ThinkingStreamSegmenterTest(11) +LocalChatBackendMaxTokensTest(3). - Migration guide at
docs/migration-3.1.md(EN + ZH).
♻️ Changed
- All 16 builtin AI tools return standardized JSON
{success, summary, ...payload}; tool descriptions follow a cloud-rich / local-concise dual template. BuiltinToolRegistrar.register()routes throughPluginRegistry.addPluginsto auto-register plugin AI tools in one pass.- Unified
ChatBackendinterface inFengYu-Api— non-sealed (Java forbids cross-module sealed permits). Two known implementors:CloudChatBackend,LocalChatBackend. UI consumers useinstanceofchecks; the interface itself is treated as opaque. CloudChatBackendunifies OpenAI + Anthropic in one class (~450 LOC). HTTP/SSE, tool-loop plumbing, and stream bridging are delegated to LangChain4j's streaming models; provider differences isolated to abuildStreamingModel(...)switch on an internalProviderenum.SynchronousChatHelper(browser planner) rewritten to use LC4j's synchronousOpenAiChatModeldirectly viaCloudChatBackendconfig accessors.AiServiceProviderexposesChatBackendeverywhere (method names unchanged).- Sampling parameters (temperature / topP / maxTokens) are honoured per-call — settings changes take effect on the next message without restarting the chat.
- Default
maxTokensraised 512 → 2048 (the Qwen3 thinking-model floor), enforced once at thechat()entry so both the native and Java backends benefit.
🐛 Fixes
- Qwen3 silent empty answer — a thinking model truncated mid-
<think>produced an empty answer becausestripThinkwiped the unclosed block. ThemaxTokensbudget is now floored toQWEN3_MIN_MAX_TOKENS(2048) at the unifiedchat()entry, with a diagnostic warning when output survives only as a think block. - Qwen3 on the Java backend leaked raw
<think>tags into the answer — now routed throughThinkingStreamSegmenter(thinking → collapsible card) and stripped from the final answer/history, matching the native path. AiConfigService.getAiMaxTokens()default synced to 2048 (was a stale 512 that disagreed with the settings UI).- AI worker IPC — the child process pins a dedicated
logback-worker.xml(noConsoleAppender) so worker logs no longer corrupt the line-delimited JSON pipe on stdout; stderr is drained on its own thread into the shared log. - AI worker native load — the child JVM loads the llama.cpp library at startup (
NativeLoader.load()) soLlamaContextconstruction no longer throws "Native library not loaded". - AI worker crash recovery —
handleChildExitwaits for a real exit code instead of throwingIllegalThreadStateExceptionon stdout EOF, so pending callbacks are released and auto-restart runs reliably. - Qwen3.5 hybrid-model warning — filenames matching
qwen3.5/qwen35now warn that the native worker is known to SIGABRT on multi-turn (use Qwen3-4B). - Cloud
testConnection()null-message bug on macOS —ConnectExceptionwith anullmessage now falls back toe.getClass().getSimpleName() + ": " + e. - Anthropic multi-round tool calling — server-issued
tool_use_idpreserved through theAiToolCall → LangChain4j → AiToolCallround-trip (previously caused HTTP 400 on round 2). - Multi-turn conversation continuity — the assistant's final reply is appended to
historybefore the service returns. - OpenAI tool-round message ordering — the assistant-with-tools message is appended before
ToolExecutor.executeAndFeed. pdf_merge.filePathsparameter type fixed ("array"→"string[]"); enums declared forbase64.mode,hash_calculate.algorithm,color_convert.from/to.ToolExecutorerror output is always JSON{success:false,error:...};ExcelConfigureToolsuccess returnssuccess:true.testConnection()HttpClientwrapped in try-with-resources; thread-safety hardening on the cloud stream handler.
🔥 Removed
BuiltinAiToolRegistrar— superseded by plugin-ownedaiTools().- FunctionGemma adapter and
OfflineNlNormalizer— replaced by the Qwen3 path.
⬆️ Dependencies
dev.langchain4j:langchain4j-open-ai:1.2.0dev.langchain4j:langchain4j-anthropic:1.2.0- (1.0.1 was originally pinned but
langchain4j-anthropicwas never published at that version; bumped to the lowest GA where both modules co-exist)
⚠️ Known Behavior Changes
cancelGeneration()on cloud backends is best-effort (LangChain4j 1.x does not expose mid-stream cancellation on streaming models); the in-progress flag is still cleared. Local mode is unaffected.- Mid-stream SSE errors now surface via
callback.onErroron the JavaFX Application Thread. - The local tool-calling model is Qwen3-4B; the native worker requests full GPU offload automatically on builds that ship a GPU backend.
📉 Net Code Change
- Deleted:
AiService(117 LOC),OpenAiService(244 LOC),AnthropicService(283 LOC),CloudAiConfigProvider(22 LOC),StreamingResponseHandlerBridge(120 LOC),StreamingResponseHandlerBridgeTest(214 LOC),BuiltinAiToolRegistrar, FunctionGemma adapter +OfflineNlNormalizer≈ 1000+ LOC removed. - Added:
ChatBackend(86 LOC),CloudChatBackend(450 LOC), the Qwen3 toolchain (ThinkingStreamSegmenter,Qwen3Adapter,ToolCallParser), worker hardening, tests, migration guides ≈ 1100+ LOC added. - Net: roughly even on LOC, but cloud code is one unified class and local AI has a dedicated tool-calling model + isolated worker.
[3.0.1] — FunctionGemma Offline Adaptation
v3.0.1 — 2026-06-21
✨ New Features
- FunctionGemma Multi-Round Tool Loop: Host-driven
analyze → configure → executeloop for the FunctionGemma-270m-it local model; tool-call tokens are suppressed during call rounds and only the final response is forwarded to the UI - Offline CN→EN Keyword Normalizer:
OfflineNlNormalizerrewrites Chinese tool-name keywords to English before local-model parsing, no network required (resource-backednl-normalizer.properties) - Enum-Schema Tool Parameters:
AiToolParamgains anenumValuesfield; tool declarations now emitenum:[...]constraints to FunctionGemma, OpenAI, and Anthropic backends — materially improves small-model parameter reliability - Enriched Excel AI tool descriptions and added enum constraints on
mode/actionparameters
🐛 Fixes
- Harden
FunctionGemmaAdapterparser: 🪙 (U+1FA99) string delimiter correctly handles values containing commas, braces, and multiple tool calls in a single response - Release
GGUFModelmmap on unload via best-effortunmap - Harden
GGUFReaderagainst malformed or truncated model files - Serialise
PluginLoaderJAR load/unload on a single-thread scheduler - Complete
LlamaRunnergeneration cleanly when cancelled during prefill - Drive
TokenBatcherflushes off the FX thread - Let the native AI worker exit gracefully before force-killing it
- Close target POI
WorkbookinExcelUtileven when copy/write throws - Low-priority stability cleanup (MDI font log, daemon UI threads)
[3.0.0] — JavaFX Migration
v3.0.0 — 2026-06-12
- Update app icons for v3.0.0 release
- Resolve static analysis warnings across codebase (Qodana)
v3.0.0-rc.3 — 2026-06-10
- Slash Commands: Type
/in AI chat to list available tools, get help on a specific tool, or invoke a tool directly without model inference — supports both direct execution and guided model parameter extraction - Plugin Resource Isolation: Child-first
ClassLoaderfor external plugins ensures plugin resources are resolved from the plugin JAR before the host;PluginContextprovides TCCL switching on every plugin lifecycle call and event dispatch - Plugin Store Redesign: Searchable, filterable card grid for the online plugin store with install state indicators and version comparison
- AI Configuration Service: Extracted
AiConfigServicecentralizes AI configuration access, decoupling it from UI settings code - Email Archive: New
email_archivetable, entity, and mapper for email archive storage - Fix sidebar icons not displaying on Windows — switched from JavaFX
Fonticons to MDI webfont - Fix email settings save always failing; now shows missing required field names
- Fix Excel complex split Phase 3 corrupting pre-existing output files — only merge into files created during the split operation
- Fix POI
NullPointerExceptionduring cross-workbook cell style cloning when data format string is null - Harden Excel Splitter progress callback with null guard
- Extract
StorePluginandStorePluginLogicfromOnlineStorePanewith unit tests - Add GPLv3 license file to the repository
- Add JUnit 5 test dependency to
FengYumodule
v3.0.0-rc.2 — 2026-06-05
- Tool Favorites: Bookmark tools with a star toggle on tool cards and the detail panel; favorites persist across restarts via H2 database and are filterable from the sidebar "Favorites" category
- Lazy AI Backend: Local AI backend (native/Java) initialization is deferred until the AI tool is first opened, improving startup performance; Java/Native inference engine toggle in AI settings
- Plugin Uninstall: Uninstall external plugins from the detail panel with confirmation dialog; closes ClassLoader, removes JAR file, and cleans up from registry
- Install Toast Notifications: Success toast notification when a plugin is installed from the online store or local JAR
- Token Batching: AI token output is batched at 50ms intervals to reduce FX thread flooding during high-speed generation
- Crash Rate Limiting: Native worker auto-restart respects a time window (3 crashes within 5 min) to prevent restart storms
- Settings Cache: App settings are cached in memory with debounced DB writes (300ms) to reduce database load during rapid UI interaction
- Fix native library loading on hardened Linux distros (UOS/Deepin/Kylin) where
SecurityExceptionis thrown for unsigned.sofiles - Fix email batch sending mutating shared recipient lists across iterations
- Fix online store plugin catalog parsing — replaced hand-rolled string slicing with Gson-based
JsonHelper - Fix
WindowResizeHelperdouble-attachment causing duplicate event filters - Thread-safety hardening across
PluginLoader,PluginRegistry, andMainWindow(ConcurrentHashMap,volatile,synchronizedSet) - Stagger limit for tool card entry animations (max 30) to avoid creating hundreds of
PauseTransitioninstances - Fix plugin JAR deletion on Windows — retry with
System.gc()hint, fall back todeleteOnExit()if file is still locked - Fix
onUnload()lifecycle callback not fired when unloading plugin JARs - Fix cached plugin view not cleared when uninstalling an inactive plugin, preventing GC of plugin classes
- Fix English locale (
Locale.ENGLISH) returning Chinese strings on Chinese-locale systems —ResourceBundleno longer falls back to JVM default locale - Fix Windows no-JRE release zip redundantly including the fat JAR alongside the Launch4j exe (which already embeds it)
v3.0.0-rc.1 — 2026-06-04
- Browser Automation: AI-callable
browser_automatetool that automates web browsers via natural language; uses Playwright with the system's installed Chrome/Edge/Chromium (no separate browser download); observe-think-act loop with page DOM snapshots, CSS selector targeting, and a planner LLM - Resizable Window: Edge and corner drag resize for the undecorated
StageStyle.TRANSPARENTwindow viaWindowResizeHelper; uses screen coordinates for macOS compatibility - Responsive Layout: Dynamic
FlowPanewrap length bound to viewport width;windowPaneandContentAreaproperly fill parent withsetMaxWidth/Height(Double.MAX_VALUE) - Pure Java PDF-to-DOCX:
PdfBoxToDocxConverterusing PDFBox for extraction and Apache POI for DOCX generation — no external Office installation required; three-tier page strategy (text → extracted images → full-page render fallback) - Native Backend Health Tracking:
NativeLoader.FailureReasonenum for structured failure diagnostics; degraded-mode banner in AI chat when native acceleration is unavailable - Fix macOS window resize not working due to unreliable
stage.isMaximized()withStageStyle.TRANSPARENT - Fix tool grid layout not responsive to window width changes
- Fix Playwright runtime attempting to download browser driver unnecessarily
- Fix AI browser planner recursively invoking
browser_automatetool via tool injection loop
v3.0.0-beta.2 — 2026-05-26