A model rate limit is the vendor's availability blip, not a BiSheng fault, so it
no longer renders as the red danger card. Transient/retryable errors (rate_limit /
network_timeout / service_unavailable) now show a calm neutral ServiceBusyNotice
(grey, role=status) with a one-tap Retry; terminal errors (quota exhausted / auth /
content filter / unknown) keep the red failure card -- red means act, grey means
just a hiccup.
- ServiceBusyNotice: shared neutral notice (optional title, detail disclosure,
Retry button), styled after the existing task-terminated banner.
- Task mode: TaskErrorCard branches transient->notice and regains an onRetry prop,
wired only on the /linsight ExecutionFlow via continueConversation (same SV).
/c inline tasks get the calm visual but no button (re-send via the main input).
- Daily mode: thread the SSE error status_code through onError onto the message
(ChatMessage.errorCode); AiMessageBubble renders the notice + Retry (reusing the
already-wired regenerate) for transient codes {12046,429,503,10540,12045} and
suppresses the copy/feedback toolbar on it.
- i18n: add com_error_retry (重试 / Retry / 再試行).
Pure frontend; backend classification from the prior commit already routes the
rate limit here. vite build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MaaS gateways (Aliyun Bailian / DashScope and other OpenAI-compatible
endpoints) reuse OpenAI's `insufficient_quota` code + "exceeded your
current quota" message for TPM/TPS throttling (429-Throttling.AllocationQuota),
which recovers on its own. The classifier treated it as billing exhaustion
(FAIL_FAST / quota_exhausted), so a transient rate limit surfaced as a scary
"quota used up / top up" failure instead of a friendly "service busy, retry".
Narrow _QUOTA_SIGNATURES to unambiguous money wording only (arrearage /
insufficient balance / 余额不足 / 欠费) and move the generic quota/额度/配额
family -- including insufficient_quota -- into _RATE_LIMIT_SIGNATURES. Quota is
still checked first, so a genuine arrears signal keeps winning over a co-occurring
throttle word. Net: insufficient_quota + 429 now auto-retries (middleware) and, if
exhausted, renders the friendly rate_limit copy in both task and daily chat mode;
genuine balance exhaustion still fails fast with the top-up guidance.
Update the classifier / resilience / workstation unit tests and add R4-R6 to the
mock-model E2E runner (retry-then-recover, exhausted->rate_limit, billing->fail-fast).
53 unit tests + 9 E2E checks green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F037 license banner shifts all admin content down by its height, but page
scroll containers hardcode viewport-relative heights (calc(100vh - Npx)) that
ignore it, so their bottoms fell off-screen and could not be scrolled to when
the banner was shown.
- LicenseBanner publishes its rendered height as the CSS var --license-banner-h
on :root (0px when hidden, so all subtractions are no-ops without the banner)
- sweep every calc(100vh - Npx) page/scroll height to subtract the var
- MainLayout sidebar nav max-height subtracts the var too
- SystemPage 组织同步/角色管理: root lacked a fill+scroll container (or used a
viewport calc miscalibrated for the nested TabsList) -> switch to h-full +
internal overflow-y-auto so they fill their flex parent, banner-agnostic
- InputFormItem: in edit mode, switching the upload file type to an image-capable
type no longer backfilled the image variable name (the new-item auto-fill effect
is skipped when editing); backfill image_file / file_path on file-type change
Point users to System Model Settings → Knowledge Base Model → ASR when media parsing fails due to missing or invalid ASR configuration, including platform upload preview and client knowledge space tooltips.
Co-authored-by: Cursor <cursoragent@cursor.com>
The space children/search endpoints no longer return the per-folder file_num
(an expensive recursive count). Folders now expose a lightweight has_failed_files
boolean (computed from the same status-grouped query, true when a child is
FAILED/VIOLATION) which drives the "retry" affordance precisely.
Client: folder rows drop the "x个项目" count and simply show the folder's most
recent update time; folder retry gating (single + batch) now uses has_failed_files
instead of successFileNum < fileNum. The actual retry stays fully backend-driven.
session-version-list unconditionally narrowed the version list to
meta_data.versionId after the shared_to_session grant. A whole-conversation
(workbench_chat) share carries no versionId, so the filter collapsed the
list to empty and non-owner viewers saw "任务详情加载失败" while the owner
(user_id match, bypass branch) always worked.
In 2.6 task mode the 2.0 integer-"version" re-run concept is dead (version is
now a datetime sort key, one session_version per turn), so a whole-conversation
share must return every turn's version. Narrow only when the share explicitly
pins a versionId (single-version linsight_session share); also guard
meta_data=None against a 500.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sharpen the earlier 'schema vs script' guidance into a hard rule: alembic
revisions may contain only DDL; the sole permitted data effect is a
server_default fill on an added column. Any read-then-write on existing rows
(SELECT->UPDATE/INSERT, INSERT...SELECT, dedup/backfill/cleanup/reconcile) is a
separate operational procedure, never in a revision — including cleanup needed
before a constraint (run as an ops prerequisite; the DDL fails loudly if data
wasn't prepared).
Removes the prior 'not part of a schema change' loophole. Notes existing
revisions (f001, f011 dedup, f035, ...) as grandfathered. Motivated by the
100k-user case where inline SELECT-then-UPDATE / per-row Python loops stall the
(now fail-fast) startup migration.
Updates both src/backend/AGENTS.md and the alembic AGENTS.md (Sec 5 + checklist).
Clipboard screenshots always arrive as an image File named "image.png"
(or with an empty name), so pasting a second one collided on InputFiles'
name-based dedup and was dropped with a "duplicate file" toast. Pinned
image chips also only showed an icon + name, with no way to view the
image content.
- useFileDropAndPaste: give generically-named pasted images a unique
name (image-<uuid>.ext); real filenames are preserved.
- InputFiles: generate a local object-URL preview for image files and
thread it through chatFiles; revoke on remove / clear / unmount.
- AttachmentBar: image chips render a thumbnail and open a full-size
preview on click; the remove button stops propagation.
- useAiChat: strip the client-only previewUrl before it reaches message
state or the SSE payload.
DeepThinkingGroup regains an outer collapsible wrapper (正在/已深度思考(N 秒) header with a live ticker; auto-collapses on the streaming→done edge). ThinkingContent becomes the inner 思考内容 block (no timer; default-expand follows store.showThinking) and both it and ToolCallDisplay render a vertical rail connector (showConnector) so the timeline stays continuous when collapsed; web tool result auto-expands after finish.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The response interceptor returns (does not throw) on a status_code===500
envelope, so http.get can hand back an error object as .data. getFolderSessions
used ?? [] which only rescues null/undefined, letting the object reach
ConversationHistory's sessions.map() and crash with 'e.map is not a function'
when switching knowledge spaces mid-load.
Coerce getFolderSessions/getFolderChatHistory/getFileChatHistory to arrays and
add an Array.isArray guard at the ConversationHistory render site.
Expose optional speech-to-text model on the knowledge-base model tab and
resolve media transcription from knowledge LLM config instead of workbench ASR.
Co-authored-by: Cursor <cursoragent@cursor.com>
Root cause: the workflow worker drives process-global async singletons (async
MySQL engine pool, OpenFGA httpx, aioredis) from multiple event loops —
run_async_task (Loop A), run_async_safe (Loop B), and transient loops from
asyncio.run / LangChain's per-batch asyncio.Runner. Pooled connections are
loop-bound, so cross-loop reuse raised 'Future attached to a different loop' /
'Event loop is closed', rolling back the DB session and failing workflow nodes.
- async_utils: run_async_safe now reuses a worker-registered bridge loop
(set_preferred_bridge_loop) so run_async_task and run_async_safe share ONE
loop; add run_on_bridge_loop() to hop foreign/throwaway-loop async work onto
that bridge loop, propagating contextvars (tenant_id).
- worker/_asyncio_utils + main: register/unregister the persistent loop.
- workflow/common/knowledge: asyncio.run(_fetch) -> run_async_safe.
- workflow/callback/llm_usage_callback: hop on_llm_end/on_llm_error DB writes
onto the bridge loop (LangChain runs async callbacks on a throwaway
asyncio.Runner loop under sync invoke, poisoning the shared async pool).
No-op for FastAPI / non-worker processes (no bridge loop registered -> inline
await). Tests: test/common/test_async_utils.py (bridge unit tests),
test/workflow/test_llm_usage_callback_bridge.py (callback integration).
batch_mark_as_read / mark_as_read used check-then-insert with no protection
against a concurrent transaction inserting the same (message_id, user_id) in
the SELECT->INSERT window, tripping ix_inbox_message_read_msg_user. batch used
one add_all + one commit, so a single conflict rolled back the whole batch —
every message stayed unread and the API 500'd (prod trace gkpj / DM -6602).
- batch_mark_as_read: on IntegrityError, rollback, re-read what now exists and
insert only the still-missing ids (dialect-agnostic, same pattern as
ChannelInfoSourceRepositoryImpl.batch_add); dedupe input ids too.
- mark_as_read: on conflict, rollback and return the row that won the race;
re-raise only when it is not a duplicate-key race.
- handle_approval: guard the post-action auto-read (narrow IntegrityError
catch) so a read-record race never surfaces the finished approval as a 500.
Adds deterministic concurrency regression tests that inject a competing insert
into the exact race window.
useFolderChat memoizes the active chat id per (space, folder) so a remount restores it instead of defaulting to the newest session; the collapsed-dock expand button now re-reveals the previous conversation rather than starting a new one (new chat stays a separate action).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use Platform-aligned summary text with visible gray-on-white tooltip instead of the long format list that rendered invisible.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace split upload button with a single dropdown listing folder creation,
file upload (with format tooltip), folder upload, and web link in order.
Co-authored-by: Cursor <cursoragent@cursor.com>
Lowercase file extensions in tmp preview object name resolution so
uppercase uploads like .MP3 upload transcript markdown to MinIO and
step-3 RichPreview can load recognized text.
Co-authored-by: Cursor <cursoragent@cursor.com>
Upload media transcript to MinIO for step-3 comparison with RichPreview
on the left and segments on the right. Set document limit to 50MB and
media limit to 1024MB across backend defaults and Platform upload UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
KnowledgeSpaceSelect book icon rendered as a CSS mask so it turns brand-600 when active (was a baked #999 img); ToolsSelect/KnowledgeSpaceSelect active color documented as brand-600 to match checked rows; TaskModeToggle suppresses the hover binoculars→x swap on touch layout (≤1023px) where a persistent exit-x already shows, avoiding a double x.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integer HSL (222 100% 54% blue / 154 67% 29% green) rounded ~3/255 off the brand-500 rgb; use fractional HSL (221.51 100% 54.22% / 156 68% 29%) so bg-primary surfaces (e.g. picker checkboxes) match the brand exactly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Give native audio controls vertical room like video preview and drop
overflow-hidden on the player/transcript split so upward popups render fully.
Co-authored-by: Cursor <cursoragent@cursor.com>
ClarifyCard custom answer: Enter confirms and advances for single AND multi-select (matches the 下一题 ↵ hint), guarded against IME composition Enter. IntentRow reworked to the DeepStepGroup layout — a self-toggling, collapsed-by-default node that lines up with the 已深度思考 node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace OGDialog/OGDialogTemplate delete/confirm dialogs in Prompts (admin settings, version delete, dash group item) with the app-wide useConfirm; touch up the ConfirmDialog gallery section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Separate the media player from scroll containers so native audio playback
speed menus are not clipped, and align Platform video transcript tabs with
Client scroll layout. Reduce default video height in split preview panes.
Co-authored-by: Cursor <cursoragent@cursor.com>
The system-prompt guardrail (don't echo workspace paths in the final answer) is
probabilistic — the model still mirrors tool results like `/output/report.md` into
its summary, leaking the internal workspace zone to end users. Add a deterministic
net: stripWorkspacePaths() removes the output/ · scratch/ folder prefix (keeping the
filename) from the answer before it renders, so the path never reaches the user
regardless of model adherence. Targeted regex leaves prose and non-file mentions
alone. Applied in ResultSection, so all three carriers (ExecutionFlow /
TaskTurnPanel / ConversationRound) are covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fixed report-link row and the model's final answer both announced the
deliverable (and the answer leaked the internal output/ path), and a single-file
run listed the same file in both the link row and the output-files card. Give
each block one job:
- prompt step 4 (收尾): summarize the deliverable's content/conclusion only, no
filenames / output/ paths / "已完成" status (the UI owns completion + files).
- ResultSection: render the output-files card only for multi-file runs; a single
deliverable is already surfaced by the report-link row above.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
qwen3.5-35b-a3b leaves <think>/</think> boundary markers in its output and
BiSheng has no generic stripper, so they leaked into two surfaces:
- task title generation failed -> session stuck on "New Chat" (empty content
raised, or a dirty long string overflowed message_session.name VARCHAR(255))
- the thinking narration rendered bare <think> / <think tags (stream-split)
Add common/utils/think_tags.py (strip_think_block / strip_reasoning_tags) and
apply it at the two linsight sources:
- _extract_thinking (stream_event_mapper) drops the markers, keeps the
reasoning, and emits nothing for a marker-only chunk
- task_title_generate cleans + clamps the title and falls back to a slice of
the user's question instead of "New Chat"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task-mode LLM calls are not persisted to llm_call_log (that F017 audit is
workflow-only, and lacks finish_reason / tool args anyway), so the write_file
truncation loop was hard to diagnose from the DB. Emit one greppable line per
model call in the resilience middleware (BS_LINSIGHT_LLM_CALL): finish_reason,
token usage, and tool-call ARG KEYS only — never values, so a huge write_file
`content` is never dumped. The loop fingerprint is unmistakable:
finish_reason=length + write_file(file_path) (content key absent) + truncated=True.
Best-effort: the diagnostic never raises and never affects the model call.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause: a large write_file `content` arg is truncated (finish_reason=length)
before its bytes are emitted, so parse_partial_json drops the key and every retry
re-raises "content: Field required", spinning to recursion_limit=200 (~19min). Not
a model-can't-count-2-args problem — the huge tool argument gets cut off/parsed away.
- L2: detect finish_reason=length + a truncated tool call in the resilience
middleware, nudge to write in smaller parts, bounded retry (truncation_retry_limit).
- L3: new LinsightToolLoopBreakerMiddleware — soft corrective hint then hard stop
(aafter_model raises LinsightToolLoopError) on consecutive same-tool failures,
with guards against aborting a model that recovered / switched tools.
- L4: classify GraphRecursionError / LinsightToolLoopError as TASK_ABORTED.
- Salvage: _handle_task_partial renders the intermediate analysis + retrieved
knowledge as a normal (COMPLETED) result instead of a raw recursion error;
shared across the fresh/resume/continue drivers via _stash_partial_abort.
- Config (LinsightConf): tool_failure_soft_limit=3 / tool_failure_hard_limit=8 /
truncation_retry_limit=2.
72 unit tests + 1 integration test (aafter_model raise propagates through a real
create_agent().ainvoke(), beating recursion_limit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every other block in the seed message is headed (# 当前时间 / # 可用文件 /
# 可用知识库 / # 前情回顾); the question alone was bare and bled into the time
block above it. Add a # 用户问题 header (both the fresh _build_agent_input and the
continue path) so the question is clearly delimited.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>