Add self-service password change that re-hashes the credential and bumps a
per-user session version so all existing access/refresh tokens are rejected.
Session version is tracked in Redis with an in-memory fallback and enforced in
middleware and refresh.
Scope prompt rules and rule groups to a brand: new brand_id column (migrated to
a "未归属" fallback brand for existing rows), brand-filtered queries/indexes, and
brand-aware admin-web tabs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove industry, target_audience, content_goal, tone, and length_goal from the imitation form; brand_name is now mandatory with autocomplete from brand library, keywords are pre-populated from the selected brand's search keywords, and output length is hardcoded to ~2000 characters in the prompt.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
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>
Port admin-web's chunk-reload guard to ops-web (vite:preloadError +
router.onError) so a stale tab landing on a lazy-loaded route triggers
a single reload instead of a hard 404. Also force index.html through
revalidation on both apps so the reload actually fetches a fresh entry
referencing the new chunk hashes; hashed asset URLs keep their long
immutable cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unknown URLs previously rendered a blank page. Add a catch-all route
under AppShell that shows the requested path plus back / workspace
actions, with zh-CN and en-US copy.
Co-Authored-By: Claude Opus 4.7 (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>
- add /questions/combination-fill endpoint with AI-driven, IP-region-aware matrix fill
- extract ip2region resolver from ops/app into shared/ipregion for cross-service use
- thread question_id filter through dashboard composite, citation summary, and collect-now
- switch template wizard from keyword inputs to brand-question selection (primary + supplemental)
- pass brand_question and supplemental_questions through assist/title/outline prompts
- add AiWaitingModal + 45s client timeout and request_timeout error mapping for long AI flows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Implemented QuestionExpansionService for generating and materializing questions based on combinations and AI distillation.
- Added question metadata classification logic to infer intent and layer of questions.
- Created API handlers for question expansion operations including combination preview, AI distillation, metadata classification, and materialization.
- Introduced database migrations to support new question-related fields and constraints in brand_questions and brand_keywords tables.
- Added caching mechanism for AI distillation results to improve performance.
- Defined JSON schemas for question distillation responses to ensure data integrity.
Polling analyze/title/outline task results every 1.2s was over-eager
for AI tasks that typically run 15-30s. Bump interval to 3s and lower
max attempts to 40, extending total timeout from 72s to 120s while
roughly halving request volume.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When ops staff reset a tenant's current-plan usage, the existing
'refunded' terminal status conflated user-initiated refunds with
ops-driven resets, and tenants still saw the old balance in the top-nav
chip until the quota-summary cache TTL expired.
- Introduce a dedicated 'reset' status for quota_reservations, separate
from 'refunded'. Both the initial and an incremental migration widen
the CHECK constraint to allow it; the down migration folds 'reset'
rows back into 'refunded' before tightening the constraint.
- resetQuotaReservations / resetAIPointReservations now write 'reset'
without touching refunded_amount, and stop cascading into
ai_point_usage_logs (carried no audit value for ops resets).
- AdminUserService gains an optional cache dependency and calls
invalidateQuotaSummaryCache(tenant_id) immediately after a successful
reset so the tenant's top-nav chip refreshes on the next request
without waiting on TTL.
- Wire the existing appCache into AdminUserService at boot.
- Drop the now-unused ai_point_usage_logs field from the reset response
and from the ops-web AdminUsersView type.
- Add a unit test covering the cache-key invalidation contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-nav quota chip (会员到期 / 生成文章 / AI 点数) only refreshes when its
query is invalidated. Several mutations and stream-based flows that debit
points or article quota on the server were not invalidating it, so users
had to refresh the page to see updated balances.
- TemplateWizardView: invalidate `['workspace']` inside `endAssistTask()`,
covering analyze / title / outline assist tasks in one place.
- FreeCreateView: invalidate after `articlesApi.create` success.
- GenerateTaskDrawer: add `['workspace']` to the instant-generate
`Promise.all` alongside the existing `articles` / `instantTasks` keys.
- KolPromptEditor: invalidate in the `finally` of both `runAiAssistStream`
(generate) and `generateSelectionAiPreview` (optimize) so abort / error
paths also refresh.
- ArticleEditorCanvas: bring in `useQueryClient` and invalidate in the
selection-optimize stream `finally`.
Together with the existing invalidations in TemplateWizard.generate,
ImitationGenerate, KolGenerate, and article-regenerate, every quota-
debiting call site now feeds the cache refresh.
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>
- Add `ai_points_insufficient` / `ai_points_unavailable` Chinese mappings in
errors.ts so the toast copy is localized.
- Add `showAiPointsInsufficientModal()` helper that renders a centered
`Modal.confirm` with localized title/body and a "查看点数明细" action that
routes to `/account/ai-points`. The helper is debounced so concurrent
failed requests do not stack multiple modals.
- Trigger the modal globally from the admin-web response interceptor when
the backend returns `ai_points_insufficient`, so every AI-generating
endpoint surfaces the same prominent prompt without per-callsite changes.
- Drop the English `detail` for ai_points_insufficient in `formatError` so
the secondary toast stays pure Chinese.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Carry `from=workspace` query from the workbench into the wizard so cancel
and post-generate redirects return to the workbench instead of always
landing on /articles/templates.
- AppShell now resolves an effective navKey from `route.query.from`, so the
sidebar highlight and breadcrumb track the entry source while inside the
wizard.
- Redesign the brand-info AI analyze CTA into a standalone banner with
template-aware copy (default / product_review / research_report) and add
the matching i18n keys.
- Drop the unused 批量生成文章 button and its styling from the templates
page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Listen for vite:preloadError and router onError, force a one-shot reload
(10s cooldown via sessionStorage) when a hashed lazy-loaded module 404s
because a new deploy replaced it. Eliminates the user-visible "Failed to
fetch dynamically imported module" error on login and route navigation.
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.
Hoists hasActiveGenerationStatus into the shared display helper so all
views agree on what counts as in-flight generation, shows the backend
generation_error_message inline on the imitation list and detail drawer
when status is failed, and drops the workspace/templates/imitation poll
interval from 10s to 3s (with a 5s fallback when streaming is enabled)
plus explicit query-cache invalidation so failed/completed states show
up promptly.
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>