Commit Graph

93 Commits

Author SHA1 Message Date
Rdmclin2 9c377eecf0 ✨ feat(page-editor): anchor document comments to a text selection (#19602)
* ✨ feat(page-editor): anchor document comments to a text selection

Let a reader create a comment from a selected run of the document body.
The comment stores a text-quote anchor (quote + prefix/suffix + offsets)
that re-locates itself against the current body on every load, and the
editor paints an inline highlight and thread badge for each anchored
root comment. Replies inherit the thread's anchor.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 🐛 fix(page-editor): paint every anchored thread and keep selection comments on locked pages

Body highlights now come from a document-wide listAnchors endpoint instead
of the paginated thread cards, so an anchored comment beyond the loaded
pages is still discoverable from the body; picking such a run pins its
thread the way a deep link does.

The Comment selection action moves to a selection-only floating toolbar
that stays registered while the editor is read-only, so a reader can still
comment on a selection while another collaborator holds the edit lock.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 🐛 fix(page-editor): pin a body-picked thread without scrolling to the list

A focus target now carries whether landing scrolls the card into view: a
notification deep link does, a pick made in the document body does not,
so pointing at a sentence never throws the reader down to the comments.

Also drops the "badge" wording from the anchor contract — the body paints
a highlight for each anchored thread, nothing more.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ⚡️ perf(page-editor): resolve comment anchors once per distinct quote and body text

Anchor resolution moves into a pure resolveAnchors step that reuses every
result while the flattened body text is unchanged and scans once per
distinct anchor, so a formatting-only update no longer re-runs a full
indexOf for each orphaned quote.

Also stops marking the composer's quote row aria-disabled: only a jump
target whose run is gone is disabled, not a row holding an operable
Remove control.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:51:07 +08:00
Rdmclin2 4bf05e2499 🐛 fix(bot): surface budget scope and headroom in bot failure replies (#19336)
* 🐛 fix(bot): surface budget scope and headroom in bot failure replies

Carry the budget context of an InsufficientBudgetForModel error from the
runtime lifecycle event through the hook payload into the bot reply, and
render a scope-aware message (member allowance vs. workspace pool) with
the remaining and required credits instead of the generic
"Agent Execution Failed".

LOBE-13726

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp

* 🌐 style: update i18n

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp

* 🔒 fix(bot): stop quoting the billed allowance in budget replies

Runs mentioned from a shared channel are billed to the bot owner, so a
reply that quoted availableCredits / requiredCredits published the
owner's balance to every channel member. Keep the scope-aware copy and
drop the amounts line; a regression test asserts no figure is rendered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp

* 💬 fix(bot): address the bot owner and split plan-limit copy in budget replies

Runs are billed to the bot owner, not to whoever mentioned the bot, so the
budget copy no longer calls the reader the payer ("your budget") and
points at the bot owner / workspace admin instead. FreePlanLimit and
SubscriptionPlanLimit get their own copy (the allowance itself is
exhausted, so the fix is the plan, not a cheaper model), and the
budgetTypeAtError scope refinement now applies to all three admission
codes since the workspace gate throws SubscriptionPlanLimit when a run
can't be priced upfront.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp

* 💬 fix(bot): cover both meanings of SubscriptionPlanLimit in the reply

Per the error taxonomy the code fires when the paid-plan allowance is
reached OR when the plan tier does not cover the requested model, so the
copy no longer claims the credits are spent and offers upgrade / top-up /
custom API as the remedies.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp

* 👷 build: serialize build scripts so editor postinstalls stop racing on lexical

Three peer-variant copies of @lobehub/editor patch the same hoisted
lexical files in postinstall; run concurrently they read each other's
half-written output and fail with "Patch context mismatch". Only the
allowBuilds packages run scripts, so childConcurrency: 1 costs seconds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 23:30:38 +08:00
Rdmclin2 3412626de9 🐛 fix: keep the resource tree and connector list in sync with their scope (#19156)
* 🐛 fix: keep the resource tree and connector list in sync with their scope

Three scope/staleness fixes that share the same shape — a client store
holding on to rows the current context no longer owns.

- LOBE-13804: `useFileItemDropdown` refreshed the tree with the explorer's
  current folder, but the sidebar navigates into a folder on click, so
  "open it, then delete it" refreshed the deleted folder while the parent
  list kept the row until a reload. Tree store gains `dropNodes`, which
  drops the rows where they actually live, purges the removed subtree's
  caches, and revalidates only the parents that were hit. Explorer bulk
  delete now syncs the tree too, and deleting the folder the explorer sits
  in steps out to the parent instead of stranding a dead route.
- T-392: connector fetches wrote their result without checking the scope
  they were issued in, so a personal `list` still in flight could land
  after the workspace switch and pin the wrong list for the session.
  Capture `getActiveWorkspaceId()` before the request and discard a stale
  response, mirroring the builtin slice.
- LOBE-13805: drop the permanently disabled workspace settings reset entry
  and its locale keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwD8Ev5t7y3xDiBTc9m8K

* 🌐 chore(i18n): run the translation workflow

Regenerated after the rebase. Backfills keys other iterations left
untranslated and drops entries whose source keys are gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwD8Ev5t7y3xDiBTc9m8K

* 🐛 fix: step out of a deleted folder's whole subtree, not just the folder

Review follow-up on the sidebar delete fix.

`handleDeleted` compared the deleted row against the explorer's selection
directly, so it only fired when the explorer was parked in the folder being
deleted. Deleting an *ancestor* — the sidebar renders the whole path, so
that is one click away — left the explorer exactly where the guard exists to
prevent: an empty list under a breadcrumb naming a folder that no longer
exists. Opening a document inside the deleted folder had the same shape.

Adds `hierarchySubtreeHoldsSelection`, which walks the loaded subtree and
answers whether the selection sits anywhere inside it. A selection under a
folder the sidebar never expanded is invisible to the walk and the explorer
stays put, matching how the rest of the tree treats folders it has not
fetched.

`onDeleted` now fires before `dropNodes` rather than after: the purge forgets
the subtree synchronously, so a caller that has to inspect what is about to
disappear could not see it. The fallback parent key is captured before the
callback runs, so the ordering carries no other meaning.

Also covers the two behaviours that shipped untested — the explorer's bulk
delete syncing the tree, and the folder-refresh path when the deleted set is
only known to the server — and documents why a discarded connector response
deliberately leaves the init flag alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwD8Ev5t7y3xDiBTc9m8K

* 🐛 fix: don't yank the user back when a slow delete finally lands

Codex review follow-up (P2).

`handleDeleted` read the selection and library from the closure the context
menu captured when it was rendered. The delete is async, so a user who opens
another folder — or another library — while the request is still in flight
was navigated back to the deleted folder's old parent once it resolved,
after they had already moved on.

The decision now moves into `resolveDeletedFolderRedirect`, a pure function
over a snapshot the caller reads at call time: the component keeps the live
library and selection in a ref updated each commit, and skips entirely when
the row has unmounted, which is itself proof the user navigated away.

Making it pure also lets the delayed-delete case be tested directly, which
the component-level logic could not be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHwD8Ev5t7y3xDiBTc9m8K

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 21:27:06 +08:00
Rdmclin2 35471de251 🐛 fix(task): load the full task list and virtualize the Tasks page (#19108)
* 🐛 fix(task): load the full task list and virtualize the Tasks page

The Tasks page only ever received the first server page (50 newest by
creation) and had no pagination, so a workspace past 50 tasks silently
dropped its older rows — e.g. a private task assigned to the viewer showed
under "Private" but not under "All" (LOBE-13779).

- `useFetchTaskList` gains `complete`: fetch every page (100/page, merged
  and de-duplicated by id) up to a 1000-row ceiling; the Tasks page opts in,
  embedded overviews keep the single page.
- `TaskList` renders through `react-virtuoso` on the page's own scroller:
  group headers and rows are flattened into one virtual list, collapse
  state lives in the list, and a truncation notice covers the ceiling.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJXLhkz7QpDyRVFTjLt4JJ

* 🌐 style: update i18n

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJXLhkz7QpDyRVFTjLt4JJ

* 🐛 fix(task): stable page order, gate the full fetch to the ordinary tab

Codex review round 1:
- `seq` breaks `createdAt` / `updatedAt` ties in every task list ordering
  (list, group prefetch, ranked group windows) so consecutive offset pages
  never repeat or skip a row; regression test pages through five rows that
  share a timestamp.
- The Tasks page only runs the full `complete` fetch on the ordinary tab;
  the scheduled and "My tasks" tabs render their own paginated collections.
- Restore the hand-authored zh-CN goal-acceptance copy the i18n sweep
  rewrote.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJXLhkz7QpDyRVFTjLt4JJ

* ♻️ refactor(task): walk the full task list with a keyset cursor

Codex review round 2: fixed-offset pages can repeat or skip a row when a
task is deleted (or created) between two page reads. `task.list` now takes
an `after: { at, seq }` cursor — rows strictly after that position in the
list's `(orderBy, seq)` order — and the Tasks page walks pages sequentially
from the last row it holds, so concurrent changes shift nothing.
Regression tests cover a delete between pages (model + store) and a cursor
on the `updatedAt` ordering.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJXLhkz7QpDyRVFTjLt4JJ

* ♻️ refactor(task): walk the full list only in the list view

Codex review round 3: the kanban view fetches its own server groups, so the
ordinary tab now walks every page only in list mode and keeps the single
page the empty-hero decision needs otherwise. `complete` joins the SWR key
and the store's query-scope guard, so the walked list and the single page
never serve each other from a shared entry or the shared store field.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJXLhkz7QpDyRVFTjLt4JJ

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 23:08:45 +08:00
Rdmclin2 14b711e783 🐛 fix(page-editor): publish lock ownerId reliably and self-heal CONFLICT saves (#18848)
* 🐛 fix(page-editor): publish lock ownerId reliably and self-heal CONFLICT saves

The lockOwnerId dispatch in useDocumentLock lands before the document row
exists in the store, so the reducer drops it and every save goes out with an
undefined ownerId — the server then rejects the write as a CONFLICT against
the user's own lease. Republish the ownerId once the document is loaded,
retry a CONFLICT save once after reclaiming the same-user lease, and stop
surfacing save failures with the file-library network error copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🌐 chore(i18n): translate pending locale keys

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🐛 fix(page-editor): guard the CONFLICT retry against a collaborator's newer version

A conflicting lease can belong to another member who saved and released it
before the recovery ran; reclaiming the now-free lease and resending the
stale payload would overwrite their save. Compare the server copy with the
session's last-known content and retry only on an exact match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🐛 fix(page-editor): make the CONFLICT retry an atomic server-side version check

The client-side content comparison left a TOCTOU window between the fetch
and the reclaim, and ignored editorData-only collaborator changes. The
recovery now compares content AND editorData against the session's
last-known version and pins the retried save to that verified version via a
new optional expectedUpdatedAt predicate, which updateDocument re-checks
under FOR UPDATE inside the write transaction — an interleaved collaborator
save now fails the retry with CONFLICT instead of being overwritten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🐛 fix(page-editor): hand back the reclaimed lease when the CONFLICT retry fails

A failed CAS retry left this session holding the lease it had just
reclaimed, so the lock recovery read the tab as the legitimate holder,
cleared the save block, and reopened a stale editor. Release the lease
before rethrowing so the lock state reflects reality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🐛 fix(page-editor): add expectedUpdatedAt to the manual UpdateDocumentInput type

The tsgo lint step failed: the manually-maintained UpdateDocumentInput
interface lagged the zod schema, and the retry helper's version check did
not narrow `latest` for TypeScript.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🐛 fix(page-editor): bind conflict recovery to the failed save's base version

The recovery compared the fetched server copy against the LIVE store
snapshot, so when an overlapping newer save from the same tab completed
while the older request was in flight, the check matched the newer version
and the retry replayed the older payload over it. Compare against the
immutable store snapshot captured when the failed save started instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🐛 fix(page-editor): self-heal only plain content saves

A save carrying title/emoji or a history restore replays fields the
recovery's content+editorData comparison cannot vouch for — a
collaborator's metadata-only change would be silently overwritten by the
retry. Those saves keep the plain CONFLICT flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

* 🔧 chore(test): pin happy-dom below 20.12 to unblock CI

happy-dom 20.12.0 (released 2026-08-29T16:12Z) changed Web Animations
behavior in a way that makes motion-dom animation teardown reject with
unhandled 'AbortError: The animation was canceled' — every Test App shard
run after the release fails at exit despite all tests passing (canary flaps
identically). Pin to ~20.11.15 until happy-dom/motion settle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011X64zT9QD5FHwJkekYAQgE

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:46:01 +08:00
Rdmclin2 b8aa38dce1 ✨ feat: support workspace member resource transfer requests (#18389)
* ✨ feat: support resource transfer requests

* 🐛 fix: keep resolved transfer actions locked
2026-08-17 17:54:40 +08:00
Rdmclin2 26dc379af9 🐛 fix(agent-group): keep group membership lifecycle intact on transfer & delete (#18126)
* 🐛 fix(agent-group): keep group membership lifecycle intact on transfer & delete

A chat group's roster mixes two kinds of member: agents the group itself
built (its synthetic supervisor, plus members made in the group builder)
and standalone agents merely linked in. Only `duplicateGroup` knew the
difference; delete, roster removal, group transfer and agent transfer
each acted on every member alike, which cost:

- deleting a group stranded its own supervisor/members forever (`agents`
  has no FK to `chat_groups`, and `virtual: true` hides them from every
  list), and the cleanup ran outside the delete's transaction
- transferring an agent silently tore it out of every group, and for a
  supervisor left the group headless — the next read minted a blank
  replacement and the configured systemRole/model were gone
- transferring a group dragged referenced members into another scope,
  and evicted them from every OTHER group they belonged to

Put the judgement in one place (`resolveGroupMembershipType`) and have
all four paths read it. A group transfer now leaves referenced members
where they are and takes a clone instead, remapping the group's own
topics/threads/messages onto it — `agent_id` is ON DELETE CASCADE, so a
stale pointer would have taken the moved history with it. The async
backfill applies the same remap on the same rows it rewrites.

Also surfaced up front rather than after the fact: two read endpoints
(`agent.getGroupMembershipImpact`, `group.listReferencedMembers`) so the
UI can say what a move will do, and `role` is now a closed enum instead
of free text.

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

* 🐛 fix(test): use a role the closed set actually contains

`moderator` predates `GROUP_MEMBER_ROLES` and is not a member of it, so
these two cases stopped type-checking once `role` became an enum.
`assistant` is the valid non-default role, so they still prove a
non-default role round-trips rather than just re-asserting the default.

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

* 🐛 fix(agent-group): gate referenced-member details on member visibility

`listReferencedMembers` scoped only on the GROUP, so a caller who could
see the group could read the title and avatar of a member whose owner
had since flipped it back to `private` — a member the roster itself
hides from them (`memberAgentVisibility`). The pre-transfer warning
would have been the one surface that handed it out.

Apply `agentOwnership()` as well, the same predicate
`checkAgentsBeforeRemoval` and the roster reads use. A hidden member is
now under-reported rather than exposed, which is the right direction:
it is invisible to this caller everywhere else too.

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

* 🐛 fix(agent-group): guard left-behind members for the drain's duration

A slow-path group transfer registers the target-scope roster — members
that travelled plus the clones — as the job's covered agents. The
referenced members it left behind are not covered, and deliberately so:
that junction is what the migration badge reads, and an agent that never
moved should not advertise a migration.

But being uncovered also left them unguarded. Until the drain reaches a
topic, that topic's rows still carry `messages.agent_id = sourceAgentId`,
and the column is ON DELETE CASCADE — so the owner deleting their own
agent mid-drain silently took the moved group history with it, the very
rows the remap was about to rescue.

Guard the delete without touching the badge:
`hasPendingRemapForSourceAgents` reads `payload.agentIdRemap` of pending
transfer jobs, and `AgentModel.delete` now refuses on it.

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

* 🐛 fix(agent-group): refuse to clone a member the caller cannot see

A group transfer leaves referenced members behind and clones them into
the target scope — title, systemRole, model, params and plugins — via
`buildCopiedAgent`. When a member's owner had since flipped it back to
`private`, that clone handed the caller a full copy of an agent the
roster hides from them (`memberAgentVisibility`), and which
`listReferencedMembers` had just been fixed to withhold. Moving the
group was the way around both.

Pre-existing rather than new — the old path MOVED that agent into the
target scope outright, which is strictly worse — and only narrowed, not
closed, by the owner-only guard: `buildWorkspaceWhere` constrains
private rows to their author, so a workspace owner cannot see such a
member either.

Refuse, rather than clone a redacted shell (a member answering with a
blank systemRole is a silent behavior change) or drop the member
(leaving moved history pointed at an agent in the source scope, which
`messages.agent_id`'s cascade turns back into data loss). The error
names nobody: which member is hidden is the part being withheld.

`copyToWorkspace` duplicates members the same way and gets the same
guard. `duplicate` needs none — it re-links referenced members instead
of copying them, so no config crosses a scope.

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

* 🔒 fix(agent-group): check member visibility on locked rows, not a snapshot

Both guards ran against a read taken before the row lock, so an owner
committing `visibility = 'private'` in between was checked as public and
then cloned anyway — from that same stale snapshot. A narrow window, but
it defeated the guard entirely when it hit.

- transfer: the check moves BELOW the existing `FOR UPDATE` on the member
  agents, and the clone now reads the locked row rather than `memberRows`
- copy: the check moves INSIDE the transaction and takes its own
  `FOR UPDATE` on the referenced members first, since it had no lock at
  all on them

Once those rows are locked, a concurrent visibility update blocks until
this transaction ends, so what is checked is what gets cloned.

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

* 🔒 fix(agent-group): enforce single-group ownership, withhold hidden group ids

Two guards that were resting on something weaker than they looked.

`addAgentsToGroup` validated visibility but never rejected a `virtual`
agent, so the "an owned member belongs to exactly one group" invariant
that `resolveGroupMembershipType` leans on was enforced by the member
picker's query rather than by the write. Sending a known group-built
member or supervisor id would link it to a second group — and then
deleting either group deletes an agent the other still uses. Reject it
at the write.

`getGroupMembershipImpact` set `groupVisible: false` for a group the
caller cannot see, and withheld its title and avatar, but still returned
its id — so the preflight disclosed the existence and identifier of
another member's private group. Withhold the id with the rest. Done
where the refs are built, so the transfer error's `groups` payload stops
carrying them too.

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

* 🐛 fix(agent-group): the invariant is one group, not zero

The previous commit rejected every `virtual` agent at `addAgentsToGroup`,
which breaks the group agent builder: it creates `virtual: true` and adds
it through exactly this path, as that agent's first and only membership.

Reject only a virtual agent already on ANOTHER group's roster — which is
the actual invariant `resolveGroupMembershipType` needs, and the only
case where deleting one group would destroy an agent another still
lists.

Adds the test that should have caught it.

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

* 🔒 fix(agent-group): keep copy and transfer taking locks in the same order

My own TOCTOU fix put the referenced-member `FOR UPDATE` at the top of
`copyToWorkspace`'s transaction — ahead of the source-group lock. That
inverted the order `transferToWorkspace` uses (group, then member
agents), so a concurrent copy and transfer of the same group could each
hold what the other wanted and deadlock, instead of meeting the
pending-job guards that exist to serialize them.

Move the agent lock below the group lock. Same protection, one order.

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

* 🔒 fix(agent-group): keep builtin agents off group rosters

Builtins (Inbox, the agent builders) are provisioned per user and carry
`virtual: true`, exactly like a group's own members — so the membership
rules classify one as group-OWNED the moment it lands on a roster. And
`removeAgentsFromGroup` deletes owned members with no reserved-slug
protection, unlike the group-delete path. Adding your Inbox to a group
and then leaving the group deleted the Inbox.

Narrowing the previous virtual-agent rejection to "already in another
group" is what let a builtin through the door, since a builtin normally
sits on no roster at all.

Two layers, matching how `ChatGroupModel.delete` already handles this:
- `addAgentsToGroup` refuses a reserved builtin slug outright
- the removal delete gains the same reserved-slug backstop, for a row
  that reached the roster some other way

Both regression tests verified failing first: without the backstop the
Inbox row is gone; without the door check the add resolves.

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

* 🔒 fix(agent-group): a builtin is never a group-owned member

The previous commit closed the two paths that DELETE an owned member.
It left the one that rehomes them: `transferToWorkspace` rewrites
`userId`/`workspaceId` for every owned member, so a builtin on a roster
would have someone's Inbox moved into another scope.

Rather than a third call-site backstop, put the carve-out in the shared
judgement — which is what this whole change is about. A reserved builtin
slug now resolves to `referenced` ahead of every other rule, including
`supervisor`, in both the TS resolver and its SQL twin. Every owned path
inherits it: delete, roster removal, group transfer, agent transfer.

Call sites that partition a roster now pass `slug`.

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

* 🔨 chore(agent-group): drop the now-unused `not` import

Left over from the roster-eviction delete this branch removed.

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

* 🐛 fix(agent-group): classify referenced members with the same inputs everywhere

`listReferencedMembers` fed `resolveGroupMembershipType` a row without
`slug`, so a builtin on a roster read as merely `virtual` — owned — and
dropped out of the warning. The transfer path does pass `slug`, treats
the same row as referenced, and clones it. The warning therefore omitted
the one row the move was about to act on.

Carry `slug` here too. The shared judgement only helps if every caller
hands it the same facts.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 22:19:41 -07:00
Rdmclin2 e08bf97fbb ✨ feat: support agent copy (#18030)
Extends the async agent-history-job pipeline to duplicate an agent's chat
history: per-topic backfill, the same progressive gray-out UX as transfer,
and guards so a source agent cannot be transferred or deleted out from
under a pending copy.

Copies carry `metadata.copied` and keep their own token/cost figures —
consumption reports filter on the marker, while display and context-window
math keep reading the numbers.

The `payload` column this builds on ships separately in #18043.
2026-08-07 21:27:59 -07:00
Rdmclin2 d365a2e283 ✨ feat: support async agent transfer (#17997)
* ✨ feat: support async agent transfer

* 🔒 fix: scope transfer-migration endpoints, dedupe polling, resume jobs at boot

* ♻️ refactor: bound transfer status polls to visible topics, keep group transfers sync

* 🐛 fix: reserve the active topic in capped transfer status polls

* 🐛 fix: guard OpenAPI user deletes during pending transfers, tighten topic gating

* 🔒 fix: require bounded topic candidates on transfer status polls
2026-08-07 01:49:52 -07:00
Rdmclin2 6593420f80 ✨ feat: support messenger DM and polish workspace interactions (#17771)
* ✨ feat: support messenger DM and polish workspace interactions

Squashed from: support messenger DM, update i18n files, resource permission
switch fix, token tag & add-menu-group modal fix, market import tweak.

* 🐛 fix: return actual visibility in setGeneralAccess response

* ✅ test: cover setGeneralAccess visibility in response

* 🐛 fix: forward private visibility through prompted create flows
2026-07-31 03:28:50 -07:00
Rdmclin2 9f03ee6792 ✨ feat: support private agent switch (#17417) 2026-07-21 23:23:53 +08:00
Rdmclin2 0e6d1a759d ✨ feat: support workspace private resources with transfer and copyTo (#16729)
* feat: support workspace private resources with transfer and copyTo

Squashed 3 commits:
- feat: support workspace private resources with transfer and copyTo
- fix: test case and lint error
- fix: file download

* chore: update i18n files

* fix: test case
2026-07-05 12:31:21 +08:00
Rdmclin2 ed10e2a8a5 ✨ feat: workspace member budget and audit log (#16530)
* feat: support workspace audit log and member budget control (squashed)

* 🐛 fix(business): add WorkspaceAuditLog open-source stub

The audit-log settings route imports `@/business/client/BusinessSettingPages/WorkspaceAuditLog`
but the open-source slot was missing, breaking the standalone build.

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

* fix: i18n keys

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-01 04:13:36 +08:00
Rdmclin2 3498537aae feat: workspace device member access (#16333)
* chore: adjust workspace limit copy

* fix: device enroll memebership

* feat: support device manage

* fix: navigation route

* chore: optimize device list

* feat: add workspace auth

* fix: workspace device role auth

* feat: support inactive workspace lock

* feat: support workspace unsubscription cancelled

* chore: solo to free plan

* chore: update downgrade i18n files

* fix: billing issue

* chore: update i18n files
2026-06-27 01:33:33 +08:00
Rdmclin2 a50b6a83e7 ✨ feat: workspace billing system (squashed) (#16308)
* feat: workspace billing system (squashed)

* fix: lint error
2026-06-26 11:42:32 +08:00
Rdmclin2 0fd3814ca3 ✨ feat: support workspace freeze (#16148)
Squashed feature work for the workspace-freeze flow:
- database: add workspace frozen columns
- feature: add workspace freeze to image and video
- swr: augment scoped mutate keys with active workspace id
- i18n: frozen banner titles/descriptions/click-for-details hint
- i18n: regenerate locale files

Squashed 10 commits prior to rebasing onto origin/canary.
2026-06-21 12:42:27 +08:00
Rdmclin2 09e6f02e45 🔨 chore: modify workspace sidebar (#15658)
* chore: change back to user style sidebar panel

* chore: optimize personal menu

* chore: update i18n files
2026-06-10 22:21:27 +08:00
YuTengjing 7641cda958 💄 style: update i18n locales (#15630) 2026-06-10 14:02:02 +08:00
YuTengjing c374892fea 🐛 fix: add temporary email auth error locale (#14564) 2026-05-09 18:50:32 +08:00
LobeHub Bot 61d432a991 🤖 style: update i18n (#14330)
💄 style: update i18n

Co-authored-by: canisminor1990 <17870709+canisminor1990@users.noreply.github.com>
2026-04-30 10:17:41 +08:00
Rdmclin2 346812ab88 🔨 chore: update i18n files & fix title skeleton (#14301)
* chore: update i18n files & fix title skeleton

* chore: update taskTemplate json

* chore: update i18n files
2026-04-29 13:23:26 +07:00
Neko 632a6383f0 ✨ feat(app): working panel, and many agent document feat (#13766) 2026-04-15 19:18:24 +08:00
YuTengjing 197a0cc8f1 🌐 chore: sync i18n locale translations (#13340) 2026-03-27 18:59:54 +08:00
Rdmclin2 942412155e ✨ feat: support skill activite switch back (#12970)
* feat: support skill activate mode

* feat: support skill panel search

* chore: update i18n files

* chore: update i18n files
2026-03-13 23:15:31 +08:00
YuTengjing 15a95156f3 💄 style: update i18n locales (#12809) 2026-03-08 13:25:46 +08:00
Innei 58fb45d251 🐛 fix: add unsaved changes guard to prevent data loss on navigation (#12332)
* 🐛 fix: add unsaved changes guard to prevent data loss on navigation

Migrate from BrowserRouter to createBrowserRouter (data router API) to enable
route-level navigation blocking. Add UnsavedChangesGuard component that uses
useBlocker to prevent leaving editor pages with unsaved changes, auto-saving
before navigation. Remove legacy renderRoutes/RouteConfig dead code.

Fixes LOBE-4973

* 🔧 chore: remove unused ESLint suppressions for welcome.ts

Cleaned up eslint-suppressions.json by removing suppressions related to sort-keys-fix and typescript-sort-keys for welcome.ts, as they are no longer needed.

Signed-off-by: Innei <tukon479@gmail.com>

* ⚡ perf: skip JSON snapshot on selection-only Lexical updates

Reintroduce dirtyElements/dirtyLeaves guard before editor.getDocument('json')
and deep-equality check, avoiding O(document-size) work on caret/selection
updates that do not mutate content.

* 🔧 test: update UnsavedChangesGuard tests to use message.destroy instead of message.success

Signed-off-by: Innei <tukon479@gmail.com>

* fix: dayjs init

- Moved dayjs plugin extensions (relativeTime, utc, isToday, isYesterday) to src/initialize.ts for centralized initialization.
- Removed redundant extensions from individual components to prevent duplicate calls.
- Updated locale loading logic in Locale.tsx to ensure correct dayjs locale handling.

This change improves performance by ensuring dayjs plugins are only extended once during application initialization.

Signed-off-by: Innei <tukon479@gmail.com>

* refactor: update router configuration to use RouteObject type

- Changed the type of desktopRoutes from RouteConfig[] to RouteObject[] for better compatibility with react-router-dom.
- Removed the RouteConfig interface from the router utility file to streamline the codebase.

This refactor enhances the router's integration with the latest routing library standards.

Signed-off-by: Innei <tukon479@gmail.com>

* feat: enhance Vite configuration and chunk management

- Added a function to suppress Vite's default URL print in the server configuration.
- Updated chunk file naming strategy in sharedRollupOutput to organize output files into specific directories based on chunk type (i18n, vendor, assets).
- Removed redundant dayjs chunk handling logic to streamline the manualChunks function.

These changes improve the clarity of the build output and enhance the server's configuration options.

Signed-off-by: Innei <tukon479@gmail.com>

* ✨ feat: add collapsible error stack with __CI__ default expand

- Add Collapse + Highlighter for error stack in Error component
- Define __CI__ in Vite (sharedRendererDefine) based on process.env.CI
- Add __CI__ to global.d.ts
- Add error.stack i18n to all 18 locales

Made-with: Cursor

* chore: update build:spa:copy script to handle multiple asset directories

- Modified the build:spa:copy script in package.json to iterate over multiple directories (assets, i18n, vendor) for both desktop and mobile builds, improving the asset copying process.

This change enhances the build process by ensuring all relevant directories are copied correctly.

Signed-off-by: Innei <tukon479@gmail.com>

* 🐛 fix: mark initialize.ts as sideEffects to prevent tree-shaking

sideEffects: false caused Rollup to drop the side-effect-only import
of initialize.ts, removing dayjs.extend(relativeTime) and enableMapSet()
from the production bundle.

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-03-03 18:50:57 +08:00
YuTengjing f26d0df93d 🐛 fix: show notification when file upload fails due to storage plan limit (#12176)
* 🐛 fix: show notification when file upload fails due to storage plan limit

Previously, when file storage exceeded the plan limit, the TRPC
middleware threw a FORBIDDEN error that was silently swallowed by
the upload components with no user feedback. Now `uploadWithProgress`
catches this specific error and displays a notification guiding
users to upgrade or free up space.

* 🌐 chore: run i18n for upload storage limit error message

* 🌍 i18n: update model descriptions across multiple languages

Added new model descriptions and improved existing ones in Arabic, Bulgarian, German, French, Italian, Japanese, and Korean locales. This update enhances the clarity and detail of model capabilities, ensuring better user understanding and accessibility.
2026-02-07 22:38:49 +08:00
YuTengjing 9b47ad20e4 🐛 fix: various bug fixes and cleanups (#11870) 2026-01-27 12:30:58 +08:00
YuTengjing 395595a2c8 ✨ feat: remove Clerk authentication code (#11711) 2026-01-23 14:41:22 +08:00
René Wang bcbdc88742 feat: Mention agent in CMDK (#11611)
* feat: mention agent in CMDK

* feat: Update translation

* fix: performance issue

* fix: lint error
2026-01-20 19:14:35 +08:00
arvinxx 80f511cd6e 🌐 style: rerun i18n 2025-12-31 15:20:11 +08:00
YuTengjing 8b67718158 docs: update subscription locale json 2025-12-29 16:48:39 +08:00
CanisMinor 024aeb2e4e 💄 style: Update i18n microcopy (#10905)
* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: update i18n translations and placeholders across multiple components

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update i18n translations for consistency in terminology across chat, onboarding, and settings components

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update i18n translations to replace 'assistant' with 'agent' and ensure consistency across all components

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update model descriptions in locales for clarity and accuracy

Signed-off-by: Innei <tukon479@gmail.com>

* feat: extract hard code string

Signed-off-by: Innei <tukon479@gmail.com>

* feat: enhance Chinese localization with new proxy and sync settings, update dialog messages for version checks and OAuth authorization

Signed-off-by: Innei <tukon479@gmail.com>

* chore: flatten

* chore: standardize localization keys by flattening nested structures across multiple languages

Signed-off-by: Innei <tukon479@gmail.com>

* chore: refine i18n documentation by consolidating key naming conventions and workflow guidelines for translation management

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update i18n

* feat: add chat title localization for improved user experience in Chinese

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
Co-authored-by: Innei <tukon479@gmail.com>
2025-12-24 12:55:12 +08:00
arvinxx 2e6fd07c19 🌐 style: update i18n 2025-12-24 12:54:42 +08:00
LobeHub Bot 1c9f0d9b72 🤖 style: update i18n (#10291)
💄 style: update i18n

Co-authored-by: canisminor1990 <17870709+canisminor1990@users.noreply.github.com>
2025-11-19 09:22:50 +08:00
LobeHub Bot 80b0999467 🤖 style: update i18n (#9832)
💄 style: update i18n

Co-authored-by: canisminor1990 <17870709+canisminor1990@users.noreply.github.com>
2025-10-22 11:05:01 +08:00
René Wang 03c28385e5 ✨ feat: support Group Chat, Mention, and Multi-Agent Orchestration with feature flag (#8976)
* feat: implement group chat functionality

* fix

* update

* revert pglite mock

* fix: Add missing test

* fix mention

* fix mention

* lint: Clear unused varibles

* fix: type check

* fix: Coverage

* build: Add missing test

* fix: add mention back

* fix: Add missing test

* fix: Add test for topic

* feat: Group chat fallback style

* fix: Revert unncessary files

* fix: circular deps

* feat: tool usage

* fix: Replace debug info

* feat: Update i18n

* opti: Better prompr

* fix claude

* feat: Filter model without function calling

* fix: DM reduction

* lint: Address build error

* fix: Test error

* feat: Store model info

* style: Clean up welcome messaeg

* feat: Use new welcome message

* fix: inbox not working

* fix: inbox not working

* fix: type error

* feat: Optimize prompt

* fix: Revert unintentional changes

* lint: Remove unused code

* fix: better test

* fix: Use debug

* refact: Move normalization postion

* opti: Better prompt

* opti: Better prompt

* opti: Better prompt

* lint: Clear console.log

* fix: Update test snap

* fix: test error

* fix: Unexpectly test fail

---------

Co-authored-by: arvinxx <arvinx@foxmail.com>
2025-10-16 22:16:40 +08:00
sxjeru 5d4ed11ad6 💄 style: Support Gemini URL context tool (#8731)
* feat: 添加 urlContext 支持到多个模型和配置接口

* feat: 在 googleChatModels 中添加 urlContext 参数支持

* feat: 调整 ControlsForm 中的最小宽度,更新 urlContext 标签样式,优化 LobeGoogleAI 中的工具调用逻辑

* feat: 调整 ControlsForm 中思维预算滑块和 urlContext 的最小宽度及样式

* fix

* ✨ feat: 添加 URL 上下文提取功能描述

* i18n

* Update chat.ts

* update i18n

---------

Co-authored-by: Arvin Xu <arvinx@foxmail.com>
2025-08-29 17:23:09 +08:00
YuTengjing 74d9bb5b6a ✨ feat: add gemini 2.5 flash image for vertex ai (#8943) 2025-08-27 15:38:19 +08:00
Arvin Xu 853a09af1b ✨ feat(plugin): support Streamable HTTP MCP Server Auth (#8425)
* ✨ feat: support http streamable auth and headers

* ✨ feat: support http streamable auth and headers

* improve

* improve token

* add i18n

* update i18n
2025-07-14 20:34:39 +08:00
YuTengjing 095de57675 ✨ feat: support AI Image (#8312)
Co-authored-by: canisminor1990 <i@canisminor.cc>
2025-07-14 20:29:57 +08:00
Arvin Xu 71683b77be 💄 style: improve mcp stdio custom field (#7632)
* add i18n

* update
2025-04-29 16:20:42 +08:00
Arvin Xu 57f18ff0c8 ✨ feat: support pglite and postgres data export (#5581)
* wip

* update

* fix server side import

* fix import

* fix test
2025-03-29 18:01:00 +08:00
Arvin Xu a4558a44c4 🐛 fix: update create message loading issue (#7116) 2025-03-24 18:59:03 +08:00
Arvin Xu f7a713848f 💄 style: add some error types (#6377)
* add some error type

* fix svix build
2025-02-21 03:10:48 +08:00
Arvin Xu 6667334496 🐛 fix: fix a feature flag (#6354) 2025-02-20 16:02:53 +08:00
Arvin Xu 6a35f55ebc 💄 style: improve error content and console error (#6305)
* update i18n

* add console error
2025-02-19 00:14:01 +08:00
Arvin Xu 352cb90eb6 💄 style: improve serveral error code (#6299)
* improve error code

* improve CHAT MASK

* improve CHAT MASK

* update i18n

* fix tests
2025-02-18 22:55:58 +08:00
Arvin Xu a0a95927d4 ✨ feat: support google vertex ai as a new provider (#4487)
* ✨ feat: add vertex ai model runtime

* update

* support vertex ai with api key

* update models

* fix lint

* fix

* fix again

* Create Dockerfile.pglite

* Create docker-pglite.yml

* Update docker-pglite.yml

* fix credentials

* Update docker-pglite.yml

* fix
2025-02-18 14:48:59 +08:00
QuentinHsu b361840cb2 🐛 fix: connection check logic (#4722) 2024-11-19 02:04:13 +08:00