Commit Graph

10933 Commits

Author SHA1 Message Date
lobehubbot 049c81d53b 🔖 chore(release): release version v2.2.1 [skip ci] v2.2.1 2026-05-29 01:54:38 +00:00
Arvin Xu 53e13ea3b1 🚀 release: 20260528 (#15302)
# 🚀 LobeHub Release (20260528)

**Release Date:** May 28, 2026  
**Since v2.2.0:** 220 merged PRs · 15 contributors

> This cycle brings heterogeneous "platform agents" you can dispatch to
local or remote devices, a rebuilt onboarding flow, document-centric
chat, and a unified model-runtime error model — with new DeepSeek V4 and
Gemini 3.5 Flash support along the way.

---

##  Highlights

- **More Hetero Agents (OpenClaw / Hermes)** — Create heterogeneous
agents and dispatch them to local or remote devices through the device
gateway, with an execution-target switcher in the composer and
persistent CLI sessions. (#15065, #15179, #15022)
- **iMessage on Desktop** — New iMessage setup and bridge on desktop,
plus bot attachments across every platform. (#15228, #15227, #15029)
- **Skills in the Composer** — Drag skill chips into chat, trigger
installed skills from the slash menu mid-line, and surface project-level
skills in the homogeneous agent runtime. (#15095, #15061, #15110)
- **New Models** — DeepSeek V4 Flash/Pro and Gemini 3.5 Flash across
providers, with thinking params for structured output and chat cost
estimates. (#15031, #15001, #15051, #14876)
- **Agent Runtime Observability** — OpenTelemetry GenAI semantic
conventions plus per-call generation tracing. (#15123, #15124)

---

## 🤖 Agents & Heterogeneous Runtime

- **Platform agent creation** — OpenClaw/Hermes creation UI, device
guard, and remote dispatch backend. (#15065)
- **Execution-target switcher** — Pick local vs remote execution
directly in the composer; device-selection UX with actionable guidance.
(#15179, #15111)
- **CLI hetero dispatch** — OpenClaw/Hermes dispatch with persistent
sessions and a notify protocol. (#15022)
- **Gateway snapshot as source of truth** — Consume the gateway
`uiMessages` snapshot at step boundaries to keep chat state consistent.
(#15153, #15152)
- **Client sub-agent as a normal tool call** — Simplifies the sub-agent
execution path. (#15281)
- **Hermes agent chain** — Implements the Hermes agent chain logic.
(#15189)
- **Device registry** — TRPC endpoints to register, list, update, and
remove devices. (#15299)
- **Desktop device routing** — Route gateway agent runs through `lh
hetero exec`; restore `userId` in gateway dispatch and gate local-system
by execution target. (#15132, #15232)
- **Agent signals** — Anchor agent-signal receipts to messages and
isolate memory-agent messages into a child thread. (#14969, #14921)

---

## 🚀 Onboarding

- **Simplified first screen** — Defer topic creation to first send.
(#15090)
- **Market Agent Picker** — Added as a classic onboarding step, with
template prefetch. (#14980, #15041)
- **Welcome guidance** — Show agent welcome guidance on first run.
(#15098)
- **Mobile** — Adapt agent onboarding UI and restore Classic-step
padding on mobile. (#15019, #15032)
- **Discovery** — Streamline discovery to a single profession question.
(#14987)
- **Analytics** — Track onboarding step events and create-agent modal
source. (#15133, #15028)

---

## 📄 Documents, Pages & Knowledge

- **Thread chat in preview** — Embed thread chat in the document preview
portal. (#15216)
- **Non-markdown rendering** — Render non-markdown docs as a read-only
highlight. (#15272)
- **Multi-select** — Multi-select delete in the document tree. (#15125)
- **Page-agent streaming** — Preview `initPage` streaming arguments.
(#15039)
- **Per-agent topics** — Per-agent topic management page. (#15207)
- **Server-side category** — Derive document category server-side and
drop frontend predicates. (#15076)

---

## 🧩 Skills & Tools

- **Drag skill chips** — Drag skills into chat input and register
agent-document skills. (#15095)
- **Slash menu** — Installed skills appear in the slash menu with a
mid-line trigger. (#15061)
- **Project skills** — Recognize project-level skills in the homogeneous
agent runtime and surface them regardless of active device. (#15110,
#15177)
- **VFS archiving** — Archive oversized tool results to VFS instead of
truncating. (#15074)
- **@localFile mentions** — Drag folders into chat input as `@localFile`
mentions on desktop. (#15071)

---

## 🧠 Model Runtime & Providers

- **Error spec registry** — Unify error codes into a spec + pattern
registry, split `ProviderBizError` into finer codes, classify Cloud-only
codes via a tier digit, and add `DatabasePersistError`. (#15262, #15286,
#15278, #15279)
- **New models** — DeepSeek V4 Flash/Pro (opencode-go) and Gemini 3.5
Flash; DeepSeek V4 Pro on SiliconCloud. (#15031, #15001, #15017, #15267)
- **Structured output** — Thinking params for structured output, Bedrock
structured generation, and DeepSeek `generateObject` tool choice.
(#15051, #15174, #15054)
- **Cost** — Chat cost estimate support; preserve usage cost in custom
streams. (#14876, #15218)

---

## 💬 Chat & User Experience

- **Follow-up chips** — Extend follow-up chip suggestions to general
chat with scene-specific model config. (#15101, #14797)
- **Input drafts** — Persist unsent input drafts across tab switches and
prevent repeated draft restore. (#14992, #15024)
- **Command menu** — Order topic/message search by recency and promote
inline type filters. (#15094, #14986)
- **Zoom HUD** — Show a zoom-level HUD on Cmd +/− and Cmd 0. (#15294)
- **Copy** — Unescape markdown escapes when copying user messages.
(#15253)

---

## 🖥️ Desktop

- **App Nap fix** — Prevent App Nap from dropping the gateway WebSocket
during display sleep. (#14994)
- **File preview** — Preview `.cjs`/`.mjs`/no-extension files instead of
binary fallback and expand `~` when opening local files. (#15168,
#15284)
- **Cross-platform settings** — Open settings via main-window navigation
on Windows/Linux and restore the route after an update restart. (#15036,
#14922)
- **Token refresh** — Prevent frequent logout from token-refresh
retries. (#14928)

---

## 📊 Observability

- **OTel GenAI** — Instrument Agent Runtime with OpenTelemetry GenAI
semantic conventions. (#15123)
- **Generation tracing** — Per-call `llm_generation_tracing` with a
pre-allocated tracingId and recordFeedback router. (#15124, #15146)
- **Error classification** — Persist `ERROR_CODE_SPECS` classification
on operation errors. (#15273)

---

## 🗃️ Database Migrations

- **Batch migrations** — Topic usage stats, push tokens,
`tasks.editor_data`, and document shares. (#15280)
- **Tracing & eval tables** — Add `llm_generation_tracing` and agent
eval experiment tables. (#15126)

> Self-hosted operators should run the database migration (`pnpm
db:migrate`, or restart with auto-migrate enabled) after upgrading. The
changes are additive and backwards-compatible.

---

## 🔒 Security & Reliability

- **Security:** Remove the `getPlaintextCred` tool to prevent plaintext
credential exposure. (#14998)
- **Security:** Prompt account selection for Google OAuth and add
`prompt=consent` to the OIDC authorization URL to fix missing refresh
tokens. (#15234, #15010)
- **Reliability:** Preserve streamed content across a mid-stream cancel.
(#15173)
- **Reliability:** Bound the Redis command timeout and configure the
Anthropic client timeout. (#15091, #15042)
- **Reliability:** Prevent infinite recursion in the assistant chain.
(#15288)

---

## 👥 Contributors

Huge thanks to **15 contributors** who shipped **220 merged PRs** this
cycle.

@AnotiaWang · @sxjeru · @algojogacor · @hardy-one · @arvinxx · @Innei ·
@tjx666 · @LiJian · @AmAzing129 · @Rdmclin2 · @Neko · @cy948 ·
@CanisMinor · @sudongyuer · @rivertwilight

Plus @lobehubbot and renovate[bot] for maintenance.

---

**Full Changelog**: v2.2.0...release/weekly-20260528
2026-05-29 09:51:56 +08:00
Arvin Xu 671b2527b8 feat(device): device registry TRPC (register / list / update / remove) (#15299)
Server-side foundation for the device registry. Builds on the `devices` table
(already on canary) so devices persist beyond the gateway's in-memory WS
sessions and stay visible/bindable while offline.

- new DeviceModel: register upserts on (userId, deviceId) and only refreshes
  machine-reported fields + lastSeenAt, so user-owned friendlyName / defaultCwd
  / recentCwds survive re-registration
- device.* router gains register / updateDevice / removeDevice (DB row only, no
  OIDC token revocation); listDevices is rewritten as a DB ∪ online union so
  offline devices stay listed and not-yet-registered online devices surface as
  transient entries
- HeteroDeviceSwitcher adapts to the richer listDevices shape (null-safe
  platform, prefers friendlyName)

Desktop / CLI auto-registration ships in a follow-up PR that depends on this.

Part of LOBE-9572. Closes LOBE-9575.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:51:35 +08:00
Arvin Xu 6d94635631 feat(bot): add iMessage Desktop setup and bridge (#15228)
 feat(bot): add iMessage Desktop bridge with Labs gate

Desktop-side BlueBubbles bridge for the iMessage channel:

- Bridge runtime (ImessageBridgeCtr/Srv) + gateway message_api_request routing;
  chat-adapter-imessage api lists all webhooks instead of the 500-prone url
  filter (first-time save no longer fails).
- iMessage channel UI: desktopDeviceId + webhookSecret are auto-filled/generated
  (not user fields); a single "Save Configuration" persists both the cloud
  provider and the local bridge via a post-save extension point — no separate
  "Save Bridge" button.
- Gated behind the `enableImessage` Labs preference (off → "Coming Soon").
- Group local-testing bot skills into per-channel folders + add iMessage
  bridge/outbound regression scripts.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:48:44 +08:00
Innei 109545c3b1 feat(desktop): show zoom level HUD on Cmd+/- and Cmd+0 (#15294)
*  feat(desktop): show zoom level HUD on Cmd+/- and Cmd+0

Replace Electron built-in zoomIn/zoomOut/resetZoom menu roles with custom
handlers backed by a new ZoomService, which clamps the zoom level to
[-3, +3] and broadcasts zoom:changed to the renderer. The renderer mounts
a macOS-style frosted HUD that fades in for 1.5s after each zoom change
so users can see the resulting percentage and confirm when they're back
to 100%.

* ⌨️ fix(desktop): preserve plus zoom shortcut
2026-05-28 21:24:56 +08:00
Arvin Xu 47daf09be1 Revert "🐛 fix: resolve file access urls via file service (#15295)"
This reverts commit 41172a6740.
2026-05-28 20:51:35 +08:00
YuTengjing 41172a6740 🐛 fix: resolve file access urls via file service (#15295) v0.0.0-nightly.pr15228.13999 2026-05-28 20:26:46 +08:00
Arvin Xu caa7905be2 🔨 feat(db): batch topic usage stats, push tokens, tasks editor_data & document shares migrations (#15280)
* 🔨 feat(db): batch topic usage stats, push tokens, tasks editor_data & document shares

Bundle four independent schema changes onto one migration branch:

- 0104 topics: add usage/cost aggregate columns (total_cost, token totals,
  cost/usage jsonb, model, provider) + model/provider indexes
- 0105 push_tokens: new table for Expo push notification tokens
- 0106 tasks: add editor_data jsonb column
- 0107 document_shares: new table for document share flow

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 🔨 chore(db): combine batch schema changes into a single migration

Squash the four sequential migrations (0104-0107) into one 0104 SQL file
containing all DDL: topic usage/cost columns, push_tokens table,
tasks.editor_data column, and document_shares table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 🔨 chore(db): make push_tokens unique constraint device-only

Drop the userId prefix from the push_tokens unique index — one row per
device, reassigned to the new user on switch (upsert by deviceId).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(db): add user_connectors and user_connector_tools schema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

*  feat(db): add user_connectors and user_connector_tools schema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ♻️ refactor(db): merge connectorTool schema into connector.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

*  revert(db): restore push_tokens unique constraint to (userId, deviceId)

This reverts commit addf14c2a6 (device-only unique index).

The device-only index conflicts with #15186's pushToken upsert, whose
onConflict target is (userId, deviceId). Restore the composite unique
index so the upsert lands consistently with both PRs.

Also re-point 0105 snapshot prevId to the restored 0104 id and carry the
(userId, deviceId) index forward so the migration chain stays consistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(db): add devices table and consolidate batch migration into 0104

Add the `devices` identity anchor (surrogate uuid PK + unique(userId, deviceId))
as the stable, reinstall-proof base for binding agent runtime instances per
machine. Fold the prior 0104/0105 migrations and the new table into a single
idempotent 0104 migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  test(db): add topic usage/cost columns to topic.create assertions

The batch added 8 nullable topic columns (totalCost/usage/model/...) but
topic.create.test.ts still asserted the pre-batch 19-field shape via toEqual.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ♻️ refactor(db): use uuid primary key for document_shares

Align document_shares.id with the other new batch tables (uuid defaultRandom);
table has no consumers yet so no compat impact. Regenerated 0104 + snapshot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: ONLY-yours <1349021570@qq.com>
2026-05-28 19:36:45 +08:00
Arvin Xu a7f38114d5 ♻️ refactor(bot): slim iMessage setup schema to user-edited fields (#15291)
♻️ refactor(bot): drop iMessage desktopDeviceId + webhookSecret from user schema

These are not user-supplied: the Desktop client fills the device id from the
local gateway and generates the webhook secret on first save. Removing them
from the platform schema keeps the iMessage setup form to the fields the user
actually edits.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:48:16 +08:00
Arvin Xu 1b74566b4c feat(model-runtime): split ProviderBizError into finer codes + reclassify catch-all at write time (#15286)
*  feat(model-runtime): split ProviderBizError into finer codes + reclassify catch-all at write time

Add UpstreamGatewayError (E8010), UpstreamMalformedResponse (E8011), and
UpstreamHttpError (E8012), migrating the matching patterns out of the
ProviderBizError catch-all. Add a refineErrorCode() step (message-pattern match
+ HTTP-status fallback) wired into formatErrorForState so generic ProviderBizError
is reclassified into the correct existing code (rate-limit / quota / network /
service-unavailable / model-not-found) instead of collapsing into one opaque
8xxx bucket. Production sampling showed ~72% of ProviderBizError actually belongs
to existing codes and only ~5% is a true residual.

*  feat(model-runtime): add isFallback flag to mark catch-all error buckets

Add an `isFallback` boolean to ErrorCodeSpec / ChatMessageError, set on the
catch-all codes (ProviderBizError, UpstreamHttpError, AgentRuntimeError,
DatabasePersistError). It flows onto agent_operations.error via the write-path
enrichment so monitoring can track how much volume still lands in fallback
buckets — the signal for where finer codes are still worth carving out.

*  test(model-runtime): add refineErrorCode to @lobechat/model-runtime mocks

formatErrorForState now imports refineErrorCode, so the partial module mocks in
AgentRuntimeService / RuntimeExecutors must expose it or vitest throws on access.

*  test(model-runtime): bump UpstreamGatewayError numericId to 8011 after canary 8010 collision

canary claimed 8010 for ProviderContentPolicyViolation, so the Upstream* codes
shifted to 8011/8012/8013 during rebase; update the refinement test assertion.
2026-05-28 17:02:39 +08:00
LiJian 1024ee961b 🐛 fix(cc-adapter): emit reasoning chunk before text in batch mode (#15289)
In the batch path (CLI / sandbox without --include-partial-messages),
the adapter extracted thinking and text from the complete assistant
block and emitted text first, reasoning second. This reversed order
caused `gatewayEventHandler` to call `startReasoningIfNeeded()` AFTER
text had already been dispatched, making the brain icon appear below
the rendered text content instead of preceding it.

Fix: swap the emission order so reasoning is always emitted before
text in both the main-agent and subagent batch paths, matching Claude's
natural output order (thinking → response) and the streaming delta path.

The desktop driver uses --include-partial-messages (partial deltas
arrive in correct order naturally), so it is unaffected.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:00:54 +08:00
Innei 980c2e74d8 🐛 fix(desktop): expand ~ when opening local files and folders (#15284)
shell.openPath() does not perform tilde expansion, so paths like
~/git/work failed silently. Run expandTilde() (shared with the rest
of LocalFileCtr) on the incoming path before handing it to the OS.
2026-05-28 16:05:48 +08:00
Innei 84598524df 🐛 fix(chat-input): keep input mounted while intervention panel is shown (#15283)
* 🐛 fix(chat-input): keep input mounted while intervention panel is shown

Conditional render swapped <DesktopChatInput> with <InterventionBar>,
unmounting the Lexical editor and wiping any unsent draft. Wrap the
input area in a display: contents | none container so the editor's
React subtree stays mounted and its in-memory document survives.

* 🐛 fix: hide expanded chat input during interventions
2026-05-28 16:05:39 +08:00
Arvin Xu 5e2ef88c13 🐛 fix(conversation-flow): prevent infinite recursion in assistant chain (#15288)
* 🐛 fix(conversation-flow): guard collectAssistantChain against cyclic chains

collectAssistantChain checked `processedIds` for loop protection but never
populated it, so when a topic contains duplicated tool_call_ids (the same
tool result reachable from multiple assistant messages) the assistant→tool→
assistant walk revisited already-seen assistants and recursed without bound,
crashing the conversation view with "Maximum call stack size exceeded".
Mark each assistant visited up front.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  test(conversation-flow): cover collectAssistantChain cycle guard

Regression test for the duplicate-tool_call_id cycle that previously
overflowed the stack: two assistant turns declaring the same tool_call_id
make one turn's tool result resolvable from the other, so the
assistant→tool→assistant walk revisits an already-collected assistant.
Asserts the walk terminates and collects each assistant once, plus a
control case for a normal acyclic chain.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 🐛 fix(conversation-flow): skip already-visited followers in collectAssistantChain

The cycle guard stopped the infinite recursion but, with a duplicated
tool_call_id, collectToolMessages can surface an earlier turn's tool result
before the current assistant's own. Its child is an already-visited assistant,
so the recursive call is a no-op — yet the unconditional return after it made
the walk stop there and silently drop the current turn's real continuation
under a later tool. Skip already-processed followers so the loop advances to
the current assistant's own tool result.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 15:53:05 +08:00
YuTengjing 403de538d6 🐛 fix: improve Google image error handling (#15285) 2026-05-28 15:40:15 +08:00
Arvin Xu 8949e89535 ♻️ refactor(agent): run client sub-agent as a normal tool call (#15281)
* ♻️ refactor(agent): run client sub-agent as a normal tool call

Make lobe-agent callSubAgent/callSubAgents execute the sub-agent in an
isolated thread via the current client runtime (executeClientAgent +
threadId + isSubAgent) and return a normal tool result, instead of the
stop:true + exec_sub_agent instruction + polling detour. UI now mirrors
the Claude Code Agent tool: a collapsed tool row that opens the sub-agent
thread in the portal. No more role='task' messages on the lobe-agent path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 💄 style(agent): refine sub-agent tool UI and unify subagent thread display

- Inspector mirrors the Claude Code Agent tool: leading bot icon, "Call SubAgent" / "Call SubAgents" label, description as a chip, and a compact run-stats tail (model · tools · tokens)
- callSubAgents collapses to the first description + "等 X 个" beyond 2, with per-row stats
- rename the open-thread action to "View Detail"
- unify subagent-thread detection on ThreadType.Isolation so lobe-agent sub-agent threads indent in the sidebar and render read-only like CC subagents
- fix: refresh threads right after creating the client sub-agent thread so the "View Detail" button and sidebar entry appear immediately instead of only after a topic switch

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 💄 style(agent): unify sub-agent workflow group label to "Call SubAgent"

Align the collapsed workflow group summary (workflow.toolDisplayName) with the
inspector copy so callSubAgent / callSubAgents read "Call SubAgent" / "Call
SubAgents" instead of "Dispatched a sub-agent".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 15:14:08 +08:00
Arvin Xu 8aa075cd80 feat(model-runtime): add DatabasePersistError code for failed DB queries (#15279)
*  feat(model-runtime): add DatabasePersistError code for failed DB queries

Drizzle stringifies a failed query/transaction as `Failed query: <sql>
params: <values>`. These are harness-side persistence failures, but they
were landing in the operation dashboards as `unknown` — and worse, the
embedded SQL/parameter text (model names, error_log rows, user messages)
contains substrings that trip unrelated provider patterns, so naive
message-matching misclassified them as CapabilityNotSupported /
InsufficientQuota / ModelNotFound.

- `agentRuntime.ts` — new `DatabasePersistError` code.
- `specs.ts` — E7004 under the 7xxx Stream/Runtime (harness) bucket,
  `attribution: harness`, `countAsFailure: true`, httpStatus 500.
- `patterns.ts` — `Failed query:` substring pattern placed **first** in the
  registry. matchErrorPattern is first-match-wins, so claiming it up front
  both classifies these correctly and stops the embedded blob from matching
  anything below.
- `match.test.ts` — assert the wrap classifies as DatabasePersistError and
  that a blob embedding `InsufficientQuota` / `context length exceeded` still
  resolves to DatabasePersistError.
- `modelRuntime.ts` — en-US `DatabasePersistError` copy (others auto-translate).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(model-runtime): add StateStorePersistError; stop classifying Redis aborts as provider-network

`Command aborted due to connection close` is an ioredis error — the
Redis/Upstash agent-state store dropping a queued command, not the LLM
provider's network. It was mapped to `ProviderNetworkError`, which
misattributed our own infra failures to upstream providers.

- `agentRuntime.ts` — new `StateStorePersistError` (sibling of
  `DatabasePersistError`: DB layer vs state-store layer).
- `specs.ts` — E7005 under 7xxx Stream/Runtime (harness), countAsFailure true.
- `patterns.ts` — repoint `Command aborted due to connection close` to
  StateStorePersistError, and add the other Upstash state-store signatures
  (`max request size exceeded`, `database has been suspended`).
- `match.test.ts` + `modelRuntime.ts` — test + en-US locale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(model-runtime): add ContextEnginePipelineError + harness JS-crash patterns

Classify the harness-side crashes that were landing as `unknown`:

- `ContextEnginePipelineError` (E7006, 7xxx Stream/Runtime, harness) — the
  context-engine pipeline processor crash, surfaced as "Processor [<name>]
  execution failed". The context-engine throws `PipelineError` (its
  `error.name`), so a CODE_ALIASES entry resolves `PipelineError` →
  ContextEnginePipelineError for stored / live records.
- patterns: `Processor [` → ContextEnginePipelineError, placed before the
  generic JS-crash fallbacks so a processor crash with a nested TypeError is
  attributed to the pipeline, not the bare `Cannot read properties` rule.
- patterns: bare V8 crashes (`is not a function`, `Cannot read properties of`,
  `Maximum call stack size exceeded`) → AgentRuntimeError, kept LAST so
  specific provider/harness patterns win first.
- test + en-US locale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ♻️ refactor(model-runtime): reattribute ConversationParentMissing to user

The broken conversation chain (`parent_id` no longer exists) is usually the
user deleting the topic / parent message mid-operation — an expected race,
not a harness bug. Flip attribution harness → user, countAsFailure
true → false (so it drops out of failure metrics), severity error → warning.

numericId 7003 / category `stream` stay put (append-only); attribution and
category are orthogonal, so a stream-bucket code can be user-attributed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(model-runtime): classify "[object Object]" messages as AgentRuntimeError

A message of literally "[object Object]" means the harness stringified an
error object instead of extracting its message — a harness serialization bug.
Add it to the JS-crash fallbacks (last, lowest priority) so it resolves to
AgentRuntimeError instead of staying unknown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 13:21:07 +08:00
Arvin Xu 9cc5f9e1a0 feat(model-runtime): classify Cloud-only error codes via numericId tier digit (#15278)
The three Cloud-only `ChatErrorType` codes (`FreePlanLimit`,
`InsufficientBudgetForModel`, `LobeHubModelDeprecated`) were emitted by the
managed gateway but had no spec, so they showed up unclassified on the
operation dashboards.

Rather than add a 10th `ErrorCategory` (the single-digit category prefix
1-9 is exhausted, and a 10th would break the 4-digit numericId scheme +
its validation tests), encode the OSS-vs-Cloud distinction in the
**second digit** of `numericId`: `0` = open-source runtime, `9` = Cloud-only.
Every existing code already has tier digit 0, so this is purely additive —
the category leading-digit invariant, 4-digit range, and `E####` regex all
hold unchanged.

- `taxonomy.ts` — document the tier digit, add `CLOUD_TIER_DIGIT = 9`.
- `specs.ts` — widen the spec key/`code` type to `SpecErrorCode`
  (`ILobeAgentRuntimeErrorType | CloudErrorCode`); add the three entries
  under their semantic categories with tier-9 ids: `FreePlanLimit` E2901 &
  `InsufficientBudgetForModel` E2902 (quota), `LobeHubModelDeprecated` E4901
  (request). All `attribution: user`, `countAsFailure: false`.
- `match.test.ts` — assert every spec's tier digit is 0 or 9, and the three
  Cloud codes resolve under the cloud tier.

Locale keys (`response.<code>`) for all three already exist. The
agent-gateway mirror is updated separately.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:28:50 +08:00
AnotiaWang bcf97d9487 💄 style: add DeepSeek V4 Pro to SiliconCloud model list (#15267)
*  feat(model-bank): add DeepSeek V4 Pro to SiliconCloud model list

Co-authored-by: AnotiaWang <AnotiaWang@users.noreply.github.com>

* 💰 pricing(siliconcloud): add cache hit price for DeepSeek V4 Flash

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: AnotiaWang <AnotiaWang@users.noreply.github.com>
2026-05-28 11:07:20 +08:00
Arvin Xu 3e4b81d2cc chore(channel): register iMessage platform with coming-soon UI gate (#15276)
 feat(channel): register iMessage platform with coming-soon UI gate

Activate the server-side iMessage registration that was previously
landed but un-registered, and let coming-soon entries take precedence
over server platforms with the same id so the platform stays hidden
until the desktop bridge UI ships.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:54:20 +08:00
Arvin Xu 651d1a203a Revert " feat(desktop): support cloud desktop builds (#14498)"
This reverts commit 0c5ccc8770.
2026-05-28 10:40:59 +08:00
Arvin Xu 4c29515e4c ♻️ refactor(locales): split model-runtime errors into modelRuntime namespace (#15269)
Until now, every runtime error code (InvalidProviderAPIKey, ProviderBizError,
ExceededContextWindow, …) lived under `error.response.<X>` — mixed in the
same file with HTTP statuses, Plugin*, Cloud business errors, and
GoogleAIBlockReason subkeys. The `response.` prefix is a lobehub-specific
convention that has nothing to do with the underlying ErrorCode, which
made it awkward for external consumers and noisy for maintainers.

This change carves out a dedicated `modelRuntime` i18next namespace:

- `src/locales/default/modelRuntime.ts` — 34 keys, one per
  `AgentRuntimeErrorType` (or deprecated alias `QuotaLimitReached`).
  Key = the bare ErrorCode (no `response.` prefix).
- `src/locales/default/error.ts` — runtime keys removed. The file keeps
  HTTP statuses (response.400 - response.524), Plugin*, Cloud-only
  business errors (FreePlanLimit, SubscriptionPlanLimit, etc.),
  GoogleAIBlockReason.*, and the various UI-flow strings.
- Registered `modelRuntime` in `src/locales/default/index.ts` so the
  namespace appears in the typed resources map.
- Generated `locales/en-US/modelRuntime.json` + updated
  `locales/en-US/error.json` — other languages need `pnpm i18n`.

New helper `src/utils/locale/runtimeErrorMessage.ts`:

```ts
getRuntimeErrorMessage(t, code, vars)
```

Routes via `getErrorCodeSpec(code)`: returns `t('modelRuntime:<code>')`
when the code is in `ERROR_CODE_SPECS`, otherwise falls back to
`t('response.<code>')`. Callers add `'modelRuntime'` to their
`useTranslation()` namespace list.

UI consumer migrations (5 dynamic lookup sites):

- `features/Conversation/Messages/AssistantGroup/Tool/Detail/ErrorResponse.tsx`
- `features/Conversation/Error/index.tsx`
- `routes/(main)/settings/provider/features/ProviderConfig/Checker.tsx`
  (incl. the static `t('response.ConnectionCheckFailed')` call)
- `routes/(main)/(create)/video/features/GenerationFeed/VideoErrorItem.tsx`
- `routes/(main)/(create)/image/features/GenerationFeed/GenerationItem/ErrorState.tsx`

`Description.tsx` (HTTP status renderer) stays on `response.<X>` since
its inputs are always HTTP status numbers, never runtime ErrorCodes.

Stacks on top of #15262 (the unified errors PR introduces
`getErrorCodeSpec` / `ERROR_CODE_SPECS`); base this PR there until
#15262 merges, then it auto-rebases onto canary.

Tests: lobehub type-check clean; model-runtime 3908 pass / 1 skip / 164 files.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:04:07 +08:00
Innei b4b1205ee9 ♻️ refactor(modal): migrate confirm modals to @lobehub/ui/base-ui (Phase 1) (#15259)
* ♻️ refactor(modal): migrate confirm modals to @lobehub/ui/base-ui

Replace all `App.useApp().modal.confirm`, `Modal.confirm` and `AntModal.confirm`
call sites with the headless `confirmModal` from `@lobehub/ui/base-ui`, dropping
antd-only props (`centered`, `type`, `width`, `okButtonProps.type='primary'`,
`okButtonProps.loading`, `classNames.root`) that the base-ui imperative API does
not accept.

- 82 files touched; `modal.confirm`/`Modal.confirm` call sites now zero
- `PageEditor/store/action.ts`: drop `modal` arg from `handleDelete`
- `ResourceManager/useUploadFolder`: replace dynamic `import('antd').Modal`
- `Eval/DatasetsTab`: migrate `modal.success` to `confirmModal`

Part of LOBE-9645 Phase 1.

* ♻️ refactor(ui): migrate select/modal call sites to @lobehub/ui/base-ui

- Convert imperative-modal factories (createXxxModal + Content split) for apikey,
  creds (Create/Edit/View), provider (CreateNewProvider), and messenger LinkModal.
- Switch Select usages to base-ui Select (Messenger AgentSelect, provider sdkType).
- Restructure CreateNewProvider form to vertical layout with manual section titles
  for tighter spacing; drop FormModal/Form group nesting.
- Standardize small ActionIcon sizing via DESKTOP_HEADER_ICON_SMALL_SIZE
  (WideScreenButton, ToggleRightPanelButton, ContextDropdown, AddNewProvider).
- Fix missing title on ResourceManager delete confirm modal so the header
  (title + close X) renders.
- Update react skill and AGENTS.md to require base-ui priority over root @lobehub/ui
  / antd; expand component table and Common Mistakes with explicit base-ui rules.

* ♻️ refactor(ui): swap antd Select to base-ui Select and migrate createStyles to createStaticStyles

*  test: update test mocks for base-ui confirmModal migration

*  test(e2e): switch delete confirm selector to base-ui dialog role
2026-05-28 02:46:27 +08:00
Arvin Xu 8c0e66b633 feat(agent-runtime): persist ERROR_CODE_SPECS classification on operation errors (#15273)
*  feat(agent-runtime): persist ERROR_CODE_SPECS classification on operation errors

Look up the runtime error's spec in `ERROR_CODE_SPECS` at the single catch
chokepoint and merge `attribution` / `category` / `severity` / `httpStatus`
/ `retryable` / `countAsFailure` / `numericId` onto the normalized
`ChatMessageError`. The enriched object flows through to all three
downstream sinks — `agent_operations.error` JSONB, S3 trace snapshot,
and the agent-gateway WS push — without each consumer having to re-run
pattern matching.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(agent-runtime): enrich inner-step error path too

Model-runtime failures caught inside `runtime.step()` resolve normally with
`newState.status = 'error'` instead of throwing, so the prior commit's outer
`executeStep` catch never sees common provider errors like
`InvalidProviderAPIKey` / `InsufficientQuota`. Those were reaching
`agent_operations.error` JSONB and the success-path trace snapshot raw —
without `attribution` / `category` / `severity` / …

Run `formatErrorForState` on `stepResult.newState.error` immediately after
`runtime.step()` returns, before the state is saved to Redis, hooks are
dispatched, or the trace is finalized. Made the helper idempotent (recognizes
already-normalized `ChatMessageError` shape) so a second pass through the
outer catch can't collapse it back to `AgentRuntimeError`. Success-path
`traceRecorder.finalize` now forwards the classification fields too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 02:25:16 +08:00
Innei 1ae8498fc7 feat(agent-document): render non-markdown docs as readonly highlight (#15272)
* 🐛 fix(tool-archive): use .txt extension for archived tool results

Tool result content is raw output (logs, JSON, stack traces) rather than
structured markdown. Saving as .md misrepresents the format and triggers
markdown parsing downstream. Switch the archive filename to .txt to match
the actual content type.

*  feat(agent-document): render non-markdown docs as readonly highlight

Agent documents whose filename does not resolve to markdown (e.g. archived
tool results saved as .txt, future .json / .yaml entries) are now rendered
through @lobehub/ui Highlighter with the inferred language, replacing the
markdown editor surface that misinterpreted raw text as syntax.

- Extract the filename→language map from FileViewer Code renderer into a
  shared util so the document viewer reuses the same mapping.
- Introduce getDocumentRenderMode: SKILL.md and .md keep the editor; all
  other extensions resolve to a Highlighter, which is naturally readonly.
- Hide the auto-save hint in Header when the document is rendered as a
  Highlighter (no editor, nothing to save).

* 🐛 fix(agent-document): render notebook documents as editor when filename is absent

Notebook documents store the markdown signal in `fileType` + `title` and never set a
`filename`. `getDocumentRenderMode` was falling back to `title` for language
inference, which resolved free-form titles like "Meeting notes" to `txt` and routed
them into the readonly Highlighter (also hiding the autosave hint).

Treat filename-absent documents as editor mode directly; filename remains the only
source for code-language inference.
2026-05-28 01:37:58 +08:00
Arvin Xu c4b147554b ♻️ refactor(model-runtime): unify error codes into spec + pattern registry (#15262)
*  feat(model-runtime): unify error codes into spec + pattern registry

Add a single source of truth for runtime error classification under
`packages/model-runtime/src/errors/`:

- `taxonomy.ts` — category / severity / attribution dimensions
- `specs.ts` — ERROR_CODE_SPECS: per-code httpStatus / retryable /
  countAsFailure / attribution (user | provider | harness | system)
- `patterns.ts` — ERROR_PATTERNS: substring/regex registry consolidating
  the 5 separate isXxxError lists and the upstream provider message
  patterns previously kept only in agent-gateway
- `match.ts` — matchErrorPattern() + isUserSideError()

Wire-up:
- Add 8 codes to AgentRuntimeErrorType (ProviderServiceUnavailable,
  ProviderNetworkError, NoAvailableChannel, ContentModeration,
  CapabilityNotSupported, InvalidRequestFormat, UserConfigError,
  OperationInactivityTimeout) plus their en-US locale keys
- Rewrite isExceededContextWindow / isQuotaLimit / isInsufficientQuota /
  isAccountDeactivated as one-line wrappers around matchErrorPattern
- errorResponse.ts getStatus() now reads ERROR_CODE_SPECS, removing the
  hardcoded switch

Tests: 167 model-runtime test files (3916 pass / 1 skip) including 13
new match.test.ts cases and all 42 isXxxError snapshots unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(model-runtime): add numericId (E1001) + ErrorClassifier namespace

Numeric reference codes for external surfaces (open-source consumers, docs
anchors, support tickets):

- `ErrorCodeSpec.numericId` (required, 4-digit). Append-only contract: once
  assigned, a (code, numericId) pair never changes even if the string `code`
  is renamed.
- Format: `E<numericId>` (e.g. `E1001` InvalidProviderAPIKey, `E3001`
  QuotaLimitReached, `E7002` OperationInactivityTimeout).
- First digit encodes category via `CATEGORY_NUMERIC_PREFIX`:
  1=auth, 2=quota, 3=capacity, 4=request, 5=safety, 6=network, 7=stream,
  8=provider, 9=config.
- Helpers: `formatErrorRef(code) → 'E1001'`, `parseErrorRef('E1001') → code`.
- Test guards: numericId is unique across specs; leading digit matches the
  declared category for every entry.

Consolidate classification predicates:

- New `ErrorClassifier` namespace bundles `isExceededContextWindow` /
  `isInsufficientQuota` / `isQuotaLimitReached` / `isAccountDeactivated`
  behind a single discoverable import.
- The 4 scattered `is*Error.ts` utilities are now `@deprecated`; kept as
  shims for callers that aren't migrated yet.
- Parity test asserts ErrorClassifier and the legacy utils return the same
  boolean on a curated sample set.

Tests: 168 files / 3928 pass / 1 skip. +12 new tests for numericId contract,
ref formatting, and classifier parity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ♻️ refactor(model-runtime): rename QuotaLimitReached → RateLimitExceeded

The legacy name conflated two distinct semantics: short-window rate limit
(429-class, transient, retryable, provider-attributed) vs. long-term
account-level quota exhaustion (`InsufficientQuota`, user-attributed).
Surface code readers hit this confusion the moment they look at the spec
table — the name reads like a 2xxx quota code but the spec sits in 3xxx
capacity.

- Add `AgentRuntimeErrorType.RateLimitExceeded` as the canonical name.
- Keep `AgentRuntimeErrorType.QuotaLimitReached` as a `@deprecated` alias
  (same string value preserved for legacy stored data on the dashboard
  side) — `CODE_ALIASES` map in `specs.ts` ensures `getErrorCodeSpec` /
  `isUserSideError` resolve both old and new strings to the canonical
  E3001 spec.
- `ErrorClassifier`: new `isRateLimitExceeded` is canonical;
  `isQuotaLimitReached` kept as deprecated alias.
- Refresh patterns.ts (~24 entries) + isQuotaLimitError util.
- Locale: add `response.RateLimitExceeded` next to the kept legacy
  `response.QuotaLimitReached`.
- Match.ts now reads via `getErrorCodeSpec` so alias resolution flows
  through one place.

Tests: 3930 model-runtime tests pass (+2 explicit alias-resolution cases).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 🐛 fix(AgentRuntime): wire classifyLLMError to ERROR_CODE_SPECS

The runtime retry loop's STOP_ERROR_TYPES was a hardcoded set that didn't
move with the unified error scheme. New codes added in #15262
(ContentModeration, InvalidRequestFormat, UserConfigError, NoAvailableChannel,
OperationInactivityTimeout, CapabilityNotSupported, LocationNotSupportError,
ExceededToolLimit, …) all carry `retryable: false` in the spec, but an
error arriving with one of these `errorType` values **and no HTTP status**
(e.g. a gateway-classified moderation message like "Content Exists Risk")
fell through to the classifier's default `retry` branch, producing pointless
retry storms for requests the spec says should stop.

Fix:

- Derive `STOP_ERROR_TYPES` / `RETRY_ERROR_TYPES` from `ERROR_CODE_SPECS` at
  module load. Future codes added to the spec table now classify
  automatically — no second source of truth.
- Keep a tight `RETRY_OVERRIDES` set for the 4 legacy codes
  (`AgentRuntimeError` / `OllamaServiceUnavailable` / `ProviderBizError` /
  `StreamChunkError`) that the runtime intentionally retries even though
  the spec marks them non-retryable; these are catch-all / harness-level
  failures often transient in practice.
- Resolve through `getErrorCodeSpec` before set lookup so the deprecated
  `QuotaLimitReached` alias classifies the same as its canonical
  `RateLimitExceeded`.
- Export the `errors/` module from `@lobechat/model-runtime` root barrel.

Tests: 31 cases (+12) including `it.each` coverage of all 8 newly-stop
codes and 3 newly-retry codes, plus explicit guards for the legacy retry
overrides and the QuotaLimitReached → RateLimitExceeded alias.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ♻️ refactor(model-runtime): consolidate isXxxError utils into ErrorClassifier

Three structural cleanups on top of the unified error scheme:

1. **Reorder `ERROR_CODE_SPECS` strictly by `numericId`.** Previously the
   spec table followed the original loose category groupings, which left
   stragglers like `InvalidOllamaArgs` (E9001, config) wedged into the
   1xxx auth section. Now entries appear in 1001 → 9005 order with
   numeric-prefix section dividers. Added `it('spec entries appear in
   source order sorted by numericId')` as a lint guard so future
   additions stay sorted (JS preserves object-literal insertion order).

2. **Migrate all production callers from `isXxxError` utils to
   `ErrorClassifier` namespace.** Touched 4 files, 13 call sites:
   - `core/anthropicCompatibleFactory/index.ts` (6)
   - `core/openaiCompatibleFactory/index.ts` (4)
   - `providers/bedrock/index.ts` (1)
   - `utils/googleErrorParser.ts` (2)

3. **Delete the 4 deprecated util files + their tests.** With no
   production callers left, the shim layer is dead code. Classifier
   tests now stand on their own (no parity comparison against the
   deleted utils).

Also mirror the spec ordering to `agent-gateway/src/errors/specs.ts`
(separate commit on that repo).

Tests: 164 files / 3908 pass / 1 skip (was 168 / 3930 — the delta is the
4 removed `isXxxError.test.ts` files, ~42 tests, net of new classifier
coverage).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 🐛 fix(AgentRuntime): stub ERROR_CODE_SPECS in test mocks of @lobechat/model-runtime

`classifyLLMError` now reads `ERROR_CODE_SPECS` + `getErrorCodeSpec` at
module-load time to derive the STOP / RETRY sets. Two test suites mock
`@lobechat/model-runtime` sparsely (only `consumeStreamUntilDone` or
`getModelPropertyWithFallback`), so those new exports were undefined and
the module-eval crashed with `No "ERROR_CODE_SPECS" export is defined on
the "@lobechat/model-runtime" mock`.

Fix: add the two symbols to the mocks. Used empty stubs rather than
`importOriginal` so the mocks stay small and don't transitively pull
the entire model-runtime package (which would then expect every other
mocked package — e.g. `model-bank.AiModelTypeSchema` — to be complete).

Neither suite exercises the runtime retry classifier, so empty
`ERROR_CODE_SPECS` and `getErrorCodeSpec` returning `undefined` are
behaviorally equivalent to the pre-PR baseline.

Verified locally:
- `bunx vitest run src/server/modules/AgentRuntime/__tests__/RuntimeExecutors.test.ts` — 102 tests pass
- `bunx vitest run src/server/services/agentRuntime/AgentRuntimeService.test.ts` — 60 tests pass

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:54:59 +08:00
Hardy 5fb1f339a7 feat(opencode-go): align model configs with models.dev API, add DeepSeek V4 Flash/Pro, improve reasoning runtime (#15031) 2026-05-28 00:52:27 +08:00
Rdmclin2 81fc1aaf7f 🐛 fix: telegram messager attachments (#15268)
* fix: telegram messager installation

* fix: lint error

* fix: telegram resolve Credentials first
2026-05-27 23:31:47 +07:00
LobeHub Bot b14f1dba5c 🌐 chore: translate non-English comments to English in openapi-types-common (#15255)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:19:31 +08:00
Arvin Xu 1d2b32bafc 🔖 chore(cli): bump @lobehub/cli to 0.0.22 (#15254)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:18:37 +08:00
Innei 347e2eec0c 💄 style(desktop/overlay): replace native select with @base-ui/react primitives (#15266)
* 💄 style(desktop/overlay): replace native select with @base-ui/react primitives

The overlay window's agent/model pickers use native `<select>` elements,
which render poorly on Windows. Switch to `@base-ui/react/select` primitives
directly, styled with the existing overlay vanilla-extract tokens.

The overlay is a bare-React tree (no SPA provider stack) intentionally
optimised for cold-start, so we cannot afford to mount `@lobehub/ui`'s
`ThemeProvider` just to use its `Select` wrapper — that path adds ~250ms
of bundle parse + ~117ms of React mount in dev mode. Using the underlying
primitive instead keeps the increase to ~119ms over native.

Mirror the overlay theme CSS variables onto `document.documentElement` so
the portaled popup (rendered outside the panel subtree) inherits them.

Also add a small gated benchmark utility (`perfMark.ts`, enabled via
`localStorage.lobe-overlay-bench=1` or `?bench`, zero overhead otherwise)
for measuring overlay cold-start segments. Call `__OVERLAY_BENCH__()`
in DevTools to dump the timeline.

* 🔥 chore(desktop/overlay): drop bench instrumentation, lower popup z-index

- Remove perfMark utility and its call sites — benchmarking is done, no
  need to ship the bench harness.
- Drop popup z-index from int32-max to 114514 (sufficient on its own
  stacking context; saner number).
2026-05-27 22:01:11 +08:00
Arvin Xu e8275a93ff 🐛 fix(hetero-agent): hide device switcher in regular agent chat input (#15257)
The HeteroDeviceSwitcher is meant for heterogeneous agents only and is
already rendered by HeterogeneousChatInput/WorkingDirectoryBar. Remove
it from the regular RuntimeConfig so it no longer appears for normal
agents.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:54:42 +08:00
Innei 49d191d2a7 🐛 fix: unify TypeScript peer resolution on 6.x (#15263) v0.0.0-nightly.pr15241.13821 2026-05-27 19:22:35 +08:00
Arvin Xu 35052416cc chore: clean up LOBE-XXX code annotations (#15249) 2026-05-27 18:09:06 +08:00
Innei 0c5ccc8770 feat(desktop): support cloud desktop builds (#14498)
*  feat(desktop): support cloud desktop builds

* 🐛 fix: open payment navigations externally in desktop
2026-05-27 16:22:48 +08:00
Innei c8ff3ac43d feat: gate agent document floating chat panel (#15260) 2026-05-27 14:02:14 +08:00
Innei 718096e306 💄 style(settings): unify select width and migrate to base-ui Select on service-model (#15248)
* 💄 style(settings): unify select width and migrate to base-ui Select on service-model

- Migrate `Select` from deprecated `@lobehub/ui` (antd-based) to `@lobehub/ui/base-ui` on STT/OpenAI/const
- Fix inconsistent select widths on service-model page: all selects now fixed at 448px
- Pull Switch out of width-constrained Flexbox in optional features row so the inner ModelSelect stays at 448px
- Drop per-item `minWidth: undefined` overrides and let Form-level `itemMinWidth={undefined}` make control col fit-content

* 💄 style(settings): move enable Switch before Select in optional features

Putting Switch in front of the Select aligns all selects on the page at the
same right edge — previously Switch trailing the Select pushed its right edge
56px to the left of other rows.
2026-05-27 12:44:35 +08:00
LiJian f0eded2941 feat(onboarding): skip redirect when landing on agent/inbox with message param (#15256)
*  feat(onboarding): skip redirect when landing on agent inbox with message param

New users arriving via /agent/inbox?message=... (e.g. Skills Marketplace
"Try in LobeHub" links) were being redirected to /onboarding before their
message could be sent, breaking the intended flow.

When the user lands on /agent/inbox with a message param, skip the onboarding
redirect so MessageFromUrl can immediately deliver the message. The user will
be prompted to complete onboarding on their next regular visit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* 🐛 fix(onboarding): broaden agent inbox guard to cover AgentIdSync slug rewrite

The previous guard matched only /agent/inbox, but AgentIdSync rewrites the
builtin slug to the resolved real agent ID (/agent/{uuid}) before the
useInitUserState callback fires — so pathname.startsWith('/agent/inbox')
was false by the time the check ran.

Widen the guard to any /agent/* path with a message param. The message
query param is the "send immediately" signal so the guard remains narrow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:58:59 +08:00
LiJian 1f6d350dca 🐛 fix(copy): unescape markdown escapes when copying user messages (#15253)
* 🐛 fix(copy): unescape markdown escapes when copying user messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* 🔨 refactor(copy): extract unescapeMarkdown util and skip code spans

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:09:51 +08:00
LiJian 5eee6d21e3 🐛 fix(hetero-agent): hide sandbox selector when device switcher is visible and sync runtimeMode (#15252)
* 🐛 fix(hetero-agent): hide runtimeMode selector when device switcher is visible and sync runtimeMode on target change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* 🐛 fix(hetero-agent): persist executionTarget and runtimeMode atomically to avoid abort-signal race

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:05:06 +08:00
Arvin Xu bcc31ca331 feat(bot): add hidden iMessage backend foundation (#15227)
*  feat(bot): add hidden iMessage backend foundation

* 🐛 fix(bot): align iMessage search totals and attachment timeout

* ♻️ refactor(bot): derive gateway runtime user from provider

*  feat(device): add message API calls
2026-05-27 02:21:43 +08:00
Innei 72d34046c0 🐛 fix(tabbar): debounce route meta publish to avoid tab item flicker (#15238)
* 🐛 fix(tabbar): debounce route meta publish to avoid tab item flicker

Desktop tab title and avatar could switch multiple times during page
navigation as agent/chat store data settled. Two coordinated fixes:

- Short-circuit `setCurrentRouteMeta` on shallow-equal meta + url so
  repeated identical writes never trigger downstream re-renders.
- Wrap the publish in a trailing 80ms debounce inside `RouteMetaBridge`
  and cancel it on route change/unmount so stale resolves from the
  previous route cannot overwrite the new one. Local `setDynamic`
  (driving document.title) stays synchronous.

* 🐛 fix(route-meta): keep previous dynamic meta during in-route navigation to stop title flicker

Dynamic state was keyed by `routeId + currentUrl`, so switching between
two topics (same route, different params) immediately invalidated the
previously resolved meta. The title fell back to the static `titleKey`
for one render before the new resolution arrived, producing an "A →
Chat → B" flash.

Key the cached meta by `routeId` alone. While navigating within the
same route family, the previous title persists until the new dynamic
resolution overwrites it; navigating to a different route still clears
correctly via the existing reset effect.
2026-05-26 22:09:23 +08:00
Innei 60f08f58e4 🐛 fix(electron-tab): update inactive tab title when topic is auto-named (#15244)
Run DynamicMetaRunner for every tab via TabCacheBridges so background
tabs receive auto-named topic titles instead of staying on "Default Topic".

Fixes LOBE-9492
2026-05-26 21:42:23 +08:00
Innei 202f062a0d feat(portal): embed thread chat in document preview portal (#15216)
*  feat(portal): embed thread chat in document preview portal

Embed FloatingChatPanel at the bottom of the Document preview portal so
users can converse with the agent about the document they are viewing
without leaving the portal.

Key changes:

- Remove the unused `/agent/:aid/:topicId/page/:docId` route and its
  supporting modules (TopicCanvas, Page, PageRedirect, topicPageRouteMeta,
  `agent/page` redirect). The route had no remaining UI entry point.
- Revive FloatingChatPanel as a thread-scoped side chat. Replace the
  hardcoded MainChatInput with `@/features/Conversation`'s ChatInput so
  the embedded composer no longer fights the main-page input for the
  global `mainInputEditor` slot.
- Default the panel's context to `scope='thread' + isNew: true` so a
  fresh ephemeral thread can be created on first send.
- Thread an `agentDocumentId` field through ConversationContext,
  ExecAgentAppContext, the Document portal payload, `openDocument` and
  callers (AgentDocumentsGroup, DocumentExplorerTree,
  AgentSignalReceiptList) so the in-portal chat always knows the
  agent_documents row id for the document in view.
- Rewrite the server `activeTopicDocument` resolver to use a single
  indexed `findRowByDocumentId(agentId, documentId)` lookup. This
  validates any caller-supplied row id and recovers the row when one
  was not provided, fixing cross-topic documents (skills, web docs)
  whose row id was previously missing — preventing the LLM from passing
  a `documents.id` into `readDocument({ id })` and triggering a failed
  query against `agent_documents.id`.

*  feat(portal): persist document portal chats as real threads

Anchor the in-portal `FloatingChatPanel` on the topic's last main-scope
message so the first send goes through `conversationLifecycle.ts`'s
`newThread` branch and the server actually creates a thread row. The
resulting thread now shows up in the left sidebar's `ThreadList` under
the parent topic.

- Read `sourceMessageId` from the latest non-thread message in
  `dbMessagesMap[messageMapKey({ agentId, topicId })]`; pair it with
  `ThreadType.Standalone` in the conversation context when `isNew`.
- Track the active thread in panel-local state. On
  `onAfterMessageCreate({ createdThreadId })` we refresh threads /
  messages and pivot the context from `isNew` to the persisted
  `threadId` in place — without calling `openThreadInPortal`, which
  would push a Thread view onto the portal stack and cover the document
  the user is reading.
- When the topic has no messages yet (no anchor), fall back to the
  previous ephemeral behavior (still leaks to main on first send;
  needed for empty-topic scenarios).

*  feat(portal): isolate document portal thread chat from main topic

Make the Document portal's `FloatingChatPanel` a truly doc-anchored side
conversation — independent of the main topic history and surviving the
mid-send pivot from `_new` → persisted thread key without the AI stream
disappearing.

- Subscribe to `chatStore.portalThreadId` instead of a panel-local
  `internalThreadId`. `lifecycle.ts:syncThreadInPortal` writes the new
  thread id into the portal slice *before* stream chunks arrive, so this
  panel's chatKey pivots in time to render the streaming response — the
  old `onAfterMessageCreate` hook only fired after the stream resolved,
  leaving the panel blank for the whole turn.
- Clear any stale `portalThreadId` left by a sibling portal on mount so a
  fresh `(agentId, topicId, documentId)` opens in `isNew` state.
- Pass `skipFetch` + a filtered `messages` prop to ConversationProvider.
  Without `skipFetch` the provider's own `useFetchMessages` pulled the
  main-topic history into this panel; with the doc-anchored A-mode we
  show only rows whose `threadId` matches the active thread (or nothing
  before the first send).
- Split `openThreadInPortal` into two actions: keep the original (push
  Thread view + sync state) for the main-page "create subtopic" flow,
  and add `syncThreadInPortal` that only mutates the portal slice.
  `lifecycle.ts` now picks one based on the current portal view type so a
  panel-hosted ConversationProvider in the Document portal no longer
  triggers a Thread view that covers the document.
- Add `key={agentId:topicId:documentId}` on `FloatingChatPanel` inside
  `Portal/Document/Body.tsx` so panel-local state (snap point, open,
  etc.) resets when conversation coordinates change.
- Anchor new threads on the topic's last main-scope message, paired with
  `ThreadType.Standalone`, so first send actually creates a thread row
  rather than leaking into the main topic.
2026-05-26 20:58:23 +08:00
LiJian be81c35e94 🐛 fix(exec-agent): gate CREDS_LIST/KLAVIS substitution on manifestMap instead of enabledToolIds (#15240)
* 🐛 fix(exec-agent): gate CREDS_LIST fetch on manifestMap instead of enabledToolIds

In execAgent mode, lobe-creds is added to toolManifestMap for activator
discovery but never into enabledToolIds, so the previous check
`resolved.enabledToolIds.includes(CredsIdentifier)` was always false
while the system role (containing {{CREDS_LIST}}) was already injected.
Gating on manifestMap presence aligns the variable substitution with the
actual system-role injection condition.

Also applies the same fix to {{KLAVIS_SERVICES_LIST}} which shares the
same isCredsEnabled gate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* 🐛 fix(exec-agent): gate KLAVIS_SERVICES_LIST substitution on KLAVIS_API_KEY presence

When KLAVIS_API_KEY is not configured the Klavis API client throws and
none of the advertised services are actually usable. Populate
{{KLAVIS_SERVICES_LIST}} only when the key is present, mirroring the
client-side enableKlavis check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.0.0-nightly.pr15241.13723
2026-05-26 18:11:24 +08:00
LiJian 14357a3f51 🐛 fix(create-platform-agent): wrap long version string in capability status (#15237)
🐛 fix(create-platform-agent): wrap long version string in capability status tag

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:17:19 +08:00
LiJian 0561a1d7eb 🐛 fix(hetero-agent): skip LOADING_FLAT placeholder when restoring accumulatedContent (#15236)
* 🐛 fix(hetero-agent): skip LOADING_FLAT placeholder when restoring accumulatedContent

When the cloud/IM Claude Code path cold-starts (Vercel serverless), it reads
the pre-created assistant message from DB to restore accumulatedContent.  That
message initially holds LOADING_FLAT ('...'), which was being treated as real
text — causing every first-turn response to start with '...'.

Subsequent turns were unaffected because handleStepStart (triggered by
--resume's newStep:true) always resets accumulatedContent to '' and creates a
fresh message with empty content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* 🐛 fix(hetero-agent): also strip LOADING_FLAT in ingest() DB refresh path

The previous commit guarded loadOrCreateState but the ingest() method
re-reads the assistant row from DB immediately after and adopts the DB
value when it is longer than in-memory.  On a cold-start first turn the
DB still holds LOADING_FLAT ('...', length 3) while in-memory was just
reset to '' (length 0), so the "adopt if longer" branch overwrote the
fix and put '...' back into accumulatedContent.

Apply the same LOADING_FLAT → '' normalisation to the refresh read so
both paths are consistent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:01:21 +08:00
Rylan Cai 3e0a396968 ♻️ refactor: + run command observation timeout (#15195)
*  add command observation timeout handling

*  hide shell observation timeout from model

* 🐛 restore shell observation compatibility

* 🐛 clear device proxy caller timeout timer

* 🐛 separate shell observation and caller timeouts

* 📝 clarify shell output wait semantics

* 🐛 align shell manifest timeout semantics

* 🐛 restore incremental shell output semantics

* 🐛 restore get command output manifest wording

* 🐛 restore get command output prompt wording

* 🐛 remove running state from command output

* 🐛 restore local system server runtime passthrough

* 🐛 restore device proxy timeout passthrough

* ♻️ tighten shell observation implementation

* ♻️ defer completed shell cleanup policy

* ♻️ simplify shell observation wait

* ♻️ read shell exit code from child process

* ♻️ tighten shell output exit code handling

* ♻️ clarify shell observation wait race

* 🐛 add device gateway HTTP call timeout

* 📝 clarify shell command session prompts

*  use incremental shell session ids

*  pass execution timeout through local system tool chain

* 🚑 fix local system timeout CI coverage

*  fix desktop shell controller tests
2026-05-26 14:53:34 +08:00
Arvin Xu 5f27cd8f26 💄 polish(agent-topic-manager): lighter bulk-bar shadow, transparent tool-auth alert, preserve sub-route (#15224)
* 💄 polish(agent-topic-manager): lighter bulk-bar shadow, transparent tool-auth alert, preserve sub-route on agent switch

- BulkActionBar: tone down the floating pill shadow from a heavy 24%/16%
  stack to a softer 8%/6% pair so it stops competing with the list rows.
- ToolAuthAlert: drop the secondary-tint fill (`background: transparent`)
  so the panel reads as a calm hint, not a warning. Reword the hint copy
  to "技能未授权或未配置时,相关技能无法使用,可能导致助理能力受限或报错" /
  matching EN.
- Sidebar agent switcher: clicking Lobe AI (Inbox) from `/agent/X/topics`
  now lands on `/agent/inbox/topics` instead of dropping back to the
  default chat URL. Extracts the existing `AgentItem` preservation logic
  into a `usePreservedAgentUrl` hook so both items share it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* 💄 polish(bulk-bar): use cssVar.boxShadowSecondary token

Replace the hand-tuned `box-shadow` stack with the existing
`boxShadowSecondary` design token — matches the floating-overlay
pattern used by Notification, CommandMenu, etc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

*  feat(inspector): add X (Twitter) inspector

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 polish(linear-inspector): use secondary text color in chips

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 polish(linear-inspector): only dim the Linear wordmark, keep chip text primary

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 polish(twitter-inspector): only dim the X (Twitter) wordmark, keep chip text primary

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:36:43 +08:00
YuTengjing 1c80146a07 🐛 fix(auth): prompt account selection for Google OAuth (#15234) 2026-05-26 12:54:47 +08:00