When a desktop monitoring task fails with a non-retryable authorization failure
(login expired, challenge required, risk control), all pending same-client/same-platform
tasks for the same business day are immediately bulk-failed as non-retryable, avoiding
wasted execution attempts. Adds preflight authorization checks before task execution,
enriches failure payloads with disposition metadata (code, category, retryable flags),
and triggers account health reports on auth state degradation.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Shrink desktop presence TTL to 30s and heartbeat to 15s so unexpected
client exits surface within one TTL even if the explicit offline call
is missed; keep migration default aligned.
- Trust a known presence miss in resolveDesktopClientOnline instead of
falling back to the recent-heartbeat heuristic, so a still-cached
LastSeenAt cannot mask an offline client.
- Release the runtime session before clearing renderer desktop sessions
on shutdown to avoid leaving stale leases behind.
- Auto-refresh the MediaView account list every 5s and revamp card
layout (inline platform badge, status tags, UID row, meta box).
- Let the brand-question AI wizard accept multiple seed topics via a
tag-style input; candidates from each topic are merged and deduped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Introduce `navigateRemoteURL` / `showLocalPage` / `showErrorPage` helpers
that centralize remote navigation, watchdog wiring, and error fallback,
replacing the ad-hoc logic inside `loadWindowURLSafely`.
- Show a localized loading page (`loadingPageDataURL`) while a remote URL
is fetching, so the window no longer sits on blank white during slow
navigations.
- Add a 25s remote-load timeout watchdog that switches the window to the
error page when the target never finishes loading, plus an active load
token so stale watchdogs from earlier navigations cannot fire on the
current attempt.
- Track `localPageKind` and `activeTargetURL` per window so blank-page
retries, did-fail-load handlers, and ready-for-detection checks behave
correctly when the window is currently showing a local loading/error
page.
- Escalate the blank-page watchdog: after the retry attempt, if the page
is still blank, surface the error page instead of looping silent
reloads.
- Restyle the error page (drop gradients, use a neutral light/dark token
palette, single primary retry button) and add a dedicated loading page
template with the same look.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Map dongchedi adapter failures (raw "server exception" or
dongchedi_platform_exception) to a Chinese prompt steering the user to
the dongchedi backend, so renderer/admin views and desktop-task error
payloads no longer surface the opaque platform message.
Playwright serializes page.evaluate callbacks via Function.prototype.toString and runs them in the target page context. Obfuscating the main bundle injected string-array helper references that the browser page could not resolve, surfacing as `ReferenceError: wO is not defined` for bilibili and dongchedi publishes. Mirrors the existing Vite `minify: false` guard for main/preload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Detect the passport.qianwen.com bridge page after a successful SSO login
and navigate the webContents back to the original returnUrl so account
binding can proceed instead of stalling on the bridge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setName pins the runtime app name (menu bar, task switcher) and
setAboutPanelOptions overrides the macOS About panel which is sourced
separately from Info.plist when running unpackaged. Packaged builds are
unaffected: electron-builder productName "省心推" still drives the .app
bundle and NSIS installer metadata. The macOS Dock label in dev still
shows "Electron" because that comes from the unpacked Electron.app
bundle's CFBundleName, which is outside Electron's API surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Baijiahao throws baijiahao_challenge_required when its risk control flags
the network environment, but PublishManagementView previously showed the
raw "code: ..., message: ..." string. Route the error through the existing
normalizer pipeline so users see a clear "需要人机验证 + 去后台手动完成
一次发稿" prompt instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Playwright serializes the function passed to page.evaluate via .toString()
and runs it in the browser context. With main-process minify on, esbuild
renames inlined helpers (e.g. __name) and closure references to two-letter
identifiers like FK/p/ed; their definitions stay at module scope on the
Node side, so the browser eval throws ReferenceError. Mac dev builds were
unaffected because dev mode doesn't minify; only packaged Windows builds
hit this. Main process bundle size is irrelevant (loaded once on startup)
so turning minify off is the cheapest correct fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Toutiao's wap_login redirect chain raises ERR_ABORTED (-3) without the
literal symbol in the message, so the previous regex fell through to the
fallback error page. Now match the numeric form too, and add a 7s blank-
page watchdog that auto-reloads stuck "Loading..." popups once per fresh
navigation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
K2.6 introduced new UI surfaces that polluted the answer extraction and
the busy-signal probe, breaking monitor runs even though Kimi rendered a
correct answer:
- collectCandidates now drops elements that contain a chat composer child
(<textarea>, non-hidden <input>, [contenteditable]). This filters
wrapper containers like .chat-detail-content that the wide
[class*="content"] fallback would otherwise pick, pulling editor
placeholder text ("可快捷使用技能", "Kimi K2.6 已就位") into the answer.
- isAuxiliaryPanelElement skips elements with role="dialog" /
"alertdialog", aria-modal="true", or the <dialog> tag, so the K2.6
launch-announcement popover ("Kimi K2.6, 已就位 / 一键部署 OpenClaw")
no longer counts as answer content.
- Drop the class-based loading-indicator probe entirely. The previous
global [class*="loading"]/"stream"/"typing"/"spinner" sweep matched
K2.6's always-on text-stream wrapper and held busy=true forever, which
caused kimi_query_timeout even after the assistant turn had fully
rendered. The textual busySignalsPattern (思考中/搜索中/生成中…) is
the actual semantic signal and survives any UI rename.
- Surface a capacityNotice from the assistant turn when Kimi shows its
short capacity-exhausted message ("算力不够 / 请稍后再试"). The top
level falls back to it when answerText is empty so the SaaS pipeline
records the user-visible message instead of an unknown row, with
capacity_limited / capacity_notice flagged in raw_response_json.
All filters lean on HTML form / ARIA semantics and visible text rather
than Kimi-specific BEM classes, so they survive future redesigns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- findInteractiveByText now also accepts elements carrying business
attributes [dt-button-id]/[data-button-id]. The new yuanbao toolbar
renders the deep-think toggle as <div dt-button-id="deep_think"> rather
than a <button>, so the previous text-only locator missed it and probes
silently ran in the fast model — without inline citations.
- Lower <div data-idx-list="1,2,10"> placeholders to bare "[1][2][10]"
(previously "[citation:1] [citation:2] [citation:10]") and accept both
forms in YUANBAO_CITATION_MARKER_PATTERN. Final answer text runs through
normalizeCitationMarkers so any residual SSE [citation:N] is rewritten
to [N], matching DeepSeek's chip rendering on the SaaS side.
- searchGuid frames in the new protocol carry every reference inside
docs[] (each with a 1-based index that mirrors the answer markers) and
citations is now always null. Promote docs into the citations bucket so
citations[index-1] aligns with the inline marker, eliminating the
spurious yuanbao_incomplete_citations failures.
- Update the SaaS detail view: createYuanbaoCitationPattern accepts both
"[N]" and "[citation:N]" so yuanbao answers render the same clickable
superscript chips as DeepSeek.
Tests cover the new searchGuid.docs alignment, fast-cache answers (no
markers, no sources -> still succeed), legacy [citation:N] -> [N]
rewriting, and mixed-format marker extraction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactor readDeepseekAccountIdentity so the JSON payload parser is a pure
ts function (extractDeepseekIdentityFromPayload). The webContents IIFE now
just polls for userToken and returns the raw API JSON; payload picking lives
in ts and is unit-tested.
Tests lock the contract for both login types we observed in the wild:
- WeChat-bound account: id_profile.name = '阿白', picture present.
- Phone-only login: id_profile = null, mobile_number = '177******08',
email = '' (empty string, not null) — the case that originally regressed.
Plus regressions for the priority order (profile.name > mobile_number > email),
the all-empty fallback, and non-record payloads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phone-number logins return id_profile=null, mobile_number="177******08", and
email="" (empty string, not null). The previous fallback used `??`, which only
short-circuits on null/undefined, so an empty email string clobbered the
mobile_number fallback and the displayName ended up null — leaving the
DeepSeek card stuck on the "${label} 会话" placeholder.
Switch to a truthy pickString helper that requires a non-empty trimmed string
before accepting a candidate, and prefer mobile_number over email so the
masked phone number shows up exactly the way DeepSeek's own sidebar renders it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The generic AI page-state heuristic only catches displayName via DOM patterns
like [class*="user-name"], so wenxin/yuanbao/deepseek (CSS-module hashed
classes or no exposed store) fell back to the placeholder "${label} 会话".
Add per-platform readers under adapters/{wenxin,yuanbao,deepseek}/account-identity.ts:
- wenxin: GET /eb/user/info with BDUSS cookie; fall back to __NEXT_REDUX_STORE__
and sidebar DOM if the API fails or hasn't responded yet.
- yuanbao: poll the stable BEM .yb-nav__user .nick-info-name + avatar img,
since yuanbao exposes no user-info API and the sidebar renders late.
- deepseek: poll localStorage for userToken, then call /api/v0/users/current
with Authorization: Bearer <token>; reads biz_data.id_profile.{name,picture}.
detectWenxin/Yuanbao/DeepSeekPlatform now run the credential-backed detector
first, then override the placeholder displayName/avatar via the per-platform
reader. Existing rows in DB still hold the placeholder until the account is
re-bound — probe does not write back display_name yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The AI 平台 badge counted distinct platforms with at least one
active-auth account, while 媒体账号 counted raw account rows
regardless of auth state. The two numbers shared a slot but meant
different things, which made them not comparable at a glance.
Switch the AI 平台 count to mirror 媒体账号: account-level count
of accounts whose platform falls in the monitoring catalog, with
no authState filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Business calls (heartbeat, account sync, lease/pull/resume tasks,
preempt monitoring lease) used to short-circuit any 401 into
handleAuthExpired, which tore down the runtime and switched the
window to login. That stole the screen on transient or per-request
auth glitches and conflicted with the renderer-side proactive token
renewal.
Now business 401s fall through to the existing warn/danger activity
log paths and runtime keeps running. The login redirect is owned
solely by the renderer's renewAuthenticatedSession → on refresh /
rotate failure → forceLogoutAfterRenewFailure path. Server-side
revocation via error code 40991 is unaffected.
Removed the now-orphan plumbing: handleAuthExpired,
isApiClientError helper, emitRuntimeAuthExpired /
onRuntimeAuthExpired events, and forceLoginWindowForAuthExpired
listener registration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expose tenant-authenticated publish task list and retry endpoints so the
admin web can show desktop publish state without an Electron bridge, and
register a `shengxintui://` deep-link so the page can hand off to the
desktop client workbench.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the 1400px max-width cap on Home/Accounts/AiPlatforms/PublishManagement
so content stretches with the window, and clean up the unused online-clients
badge from the settings sidebar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Emit a 'publish-task-lease' runtime invalidation event whenever the
scheduler activates a publish task, and have PublishManagementView
listen for it to trigger an immediate refresh. The view also switches
to a 5s active-task poll only while a task is in_progress, replacing
the unconditional 20s timer.
Move the Qwen page-state probe, session detection, and silent probe
helpers out of account-binder into apps/desktop-client/src/main/adapters/qwen,
mirroring the Kimi adapter layout. Generic AI auth no longer special-cases
qwen since the platform now owns its own credential rules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Dedup AI platform accounts by platform ID rather than identity key, preferring higher health rank then newer verified_at
- Clean up session data and health records when duplicate or replaced accounts are removed
- Remove reconcileTrackedAccountRemoteState — no longer optimistically trust remote health state
- Spread initial probes over a random window (5s–3min) to avoid thundering-herd on startup
- Stop scheduling a next probe after an account is marked expired
- Update AI platforms stats strip to show authorized/pending instead of configured/authorized
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scope desktop media accounts by the authenticated desktop client_id so
the same user logging in from Mac and Windows no longer sees a shared
account list. The list, identity lookup, upsert, patch, tombstone and
async health sink paths now all require matching client_id, and the
active uniqueness index moves from (workspace, platform, uid) to
(workspace, client, platform, uid).
Also strengthen the desktop client_id seed: when the OS machine id is
unavailable, fall back to a hashed fingerprint of stable hardware MAC
addresses before the random installation id, so the same hardware
keeps the same client across reinstalls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shell badge and the AI Platforms overview now report platforms
whose binding is locally authorized (authState=active) instead of the
total bound platforms, so the number reflects how many monitoring
sources are actually usable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace ad-hoc trim/fallback logic with a shared normalizer that strips
trailing slashes and accidental /api suffixes, rejects non-http schemes,
and rewrites Vite dev origins (517x) back to localhost:8080 so the
desktop client never points its API client at the renderer dev server.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restrict account tracking, probing, and health reporting to accounts owned
by the current client (client_id match). Extend identity-match verification
to bilibili, juejin, and smzdm so session validity is confirmed locally
rather than deferred to remote health state. Server-side account view now
uses stored client_id as authoritative owner instead of presence data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Increase default main window size from 1320x860 to 1420x960
- Use transparent titlebar overlay for all window modes
- Remove hardcoded CONFIG_PATH from dev-ops-api make target
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Translate all login error messages to Chinese (network errors, HTTP
status codes, URL validation errors)
- Add password visibility toggle (eye icon) to login form
- Add live countdown timer when login is locked, disable login button
during cooldown, auto-clear error when countdown ends
- Add X button red hover feedback in server settings dialog
- Add LoginGuard.Unlock() method to clear Redis lock/failure keys
- Expose POST /admin-users/:id/reset-login-lock endpoint in ops API
- Add "重置登录保护" button in ops-web user management table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Refactors openAtLogin and keepRunningInBackground to make the macOS/Windows
divergence explicit instead of leaning on Electron's cross-platform
abstraction:
- openAtLogin on Windows now passes path: process.execPath and args: []
so the registry HKCU Run key always points at the right binary.
- keepRunningInBackground on Windows shows a one-shot tray balloon ("省心
推已最小化到托盘…") so first-time users notice the app is still alive;
persisted via a private hasShownBackgroundBalloon flag so it never fires
again. macOS preserves the dock icon and stays silent.
- Sets AppUserModelID early on Windows so tray.displayBalloon's Win10+
Toast routing actually fires.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a post-build obfuscation step using javascript-obfuscator with
hidden vite sourcemaps chained back to TypeScript via @ampproject/remapping,
so production packages ship mangled code while debug-time maps still
resolve to original sources. Wires obfuscation into all package:* scripts
via a new build:obf step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets the settings window parent on creation and re-applies it on reveal
so it stays modal to the main window on win32.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add creation promise guards (mainWindowCreatePromise etc.) so concurrent
ensureXxxWindow() calls share a single in-flight BrowserWindow, avoiding
duplicate windows on rapid IPC
- Add windowModeSwitchQueue to serialize desktop:set-window-mode calls and
the initial startup switch, preventing races when login/logout fire quickly
- Fix retireInactiveAppWindows: also retire existing login windows when
switching to login mode, so stale login windows don't accumulate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add appTitleBarStyle() and appTitleBarOverlay() helpers for cross-platform title bar
- Use titleBarOverlay on Windows/Linux so system caption buttons render correctly
- Disable minimize button on login window (non-resizable, no need to minimize)
- Detect platform at renderer startup and set data-platform on <html> for CSS targeting
- DesktopShell: flip drag bar layout — full-width minus caption area on Win/Linux, traffic-light gap on macOS
- LoginView: move top-bar to Win/Linux caption area (top: 34px), mirror to left on macOS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add suppressNativeWindowMenu() to hide native menu bar on all windows (Win/Linux only)
- Set autoHideMenuBar: true on BrowserWindow creation as belt-and-suspenders
- Import Menu from electron/main to support menu suppression
- Login form: rename field from email to identifier, support phone number input
- Migrate saved credential key from geo_rankly_saved_email to geo_rankly_saved_identifier with fallback
- Update login error message: "邮箱或密码错误" → "账号或密码错误"
- Remove unset ELECTRON_RUN_AS_NODE from dev script (not needed on Windows)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track each window's renderer mode in a WeakMap (and fall back to
parsing ?window= from the URL) so retireInactiveAppWindows can
destroy stale login/main/settings windows when we switch modes,
not just the previous one of the canonical pair.
- Replace closeWindowAfterIpcReturn with retireWindowAfterIpcReturn
(hides immediately, then destroys instead of close()) so the IPC
reply still reaches the caller before teardown.
- Add waitForWindowReady() with a 1.2s ceiling and call it from
revealWindow so we don't show a half-loaded shell while
switchWindowMode is running. Pass the source window from the IPC
event into switchWindowMode so the closing target is the actual
caller, not a guess based on mode.
- Silence console.* and skip the observed-fetch install in packaged
runtime, and only auto-openDevTools in dev.
- Eagerly import LoginView in App.vue instead of defineAsyncComponent
to remove the boot flash on the login window.
- package.json: prefix package:* with `pnpm run build` and add
package:win and package:linux for parity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>