mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
fix/topic-reference-personal-fallback
3577 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e13ad41733 |
✨ feat(server): serve tool results as render-facing view models on the v2 read path (#19666)
* ✨ feat(server): add a tool view-model layer to the UI message read path Tool messages carry one blob that serves two audiences: the model replays the whole thing into the LLM context, while the screen picks a handful of fields out of it. Measured on production, the screen's share is tiny — a crawlSinglePage render reads title/url/description and `content.length`, and never the 37 kB page body sitting next to them. This adds the seam that lets the two be separated: a pure, dependency-free projector per `identifier/apiName`, applied over `UIChatMessage[]` on the way out of `MessageService.queryMessages`. The split is a layer boundary rather than a query flag. The UI read path goes through the service; the LLM context read calls `MessageModel.query` directly and so cannot reach a projector even by accident — no caller has a parameter to forget. The registry ships empty, so this commit changes no bytes on the wire. Projectors land per tool in follow-ups; a tool without one keeps today's behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ✨ feat(tools): project the four heaviest tool payloads to what the UI renders Registers projectors for the tools production measurements put at the top of the tool payload, and moves the surfaces that need the stored bytes onto an on-demand fetch. What each projector keeps, from an audit of the components that consume it: - crawlSinglePage / crawlMultiPages — the card reads title, url, description, crawler and `content.length`; the 37 kB page body it never touches becomes a 200-char preview, and the tool message body (a second copy, for the model) goes entirely. `length` is pinned before truncating so the character count stays honest. - runCommand — the output is stored three times (message body, `state.stdout`, legacy `state.output`) and the render reads one. The other two go. The surviving copy is NOT truncated; it renders inline in a scrollable block. - readDocument — the only surface is an inspector chip showing `state.title`. `state.content` and `state.xml`, two representations of the same document, go with the body. Nothing is lost, only moved. `message.getToolResultPayload` returns the stored content and plugin state for one message, and three surfaces call it when they are opened — never on mount, which would pull everything back down on load: - the crawl detail portal, when its local body is shorter than the pinned length - the raw/debug panel, which only mounts once toggled on - the fallback renderer, behind an explicit control, since an empty body there is ambiguous between "returned nothing" and "projected away" Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(server): only project tool payloads for the WebSocket v2 cohort The projected read path was applied to every caller of `MessageService.queryMessages`, on the assumption that the LLM context is assembled server-side and so can never see it. That holds on the server — the context read goes through `MessageModel.query` directly — but not in the browser: a client-mode run builds its context from `dbMessagesMap` (`executeClientAgent({ messages: displayMessages })`), which is filled by the very read being projected. Tool results would have gone missing from the model's context, silently. Gate on the `gatewayMux` lab opt-in, read server-side from `users.preference`. That cohort is exactly the one whose runs always execute server-side, so the browser there never needs the stored payload for anything but rendering. The preference is memoized per service instance — a gateway run queries once per step — and any failure to read it falls back to the whole payload. Turning the lab off does not invalidate already-cached projected lists. Left alone on purpose: the mux is on its way to being the only runtime, and the gate goes with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(server): project the conversation read, and name the load control Two defects a live run surfaced that the unit tests could not. `message.getMessages` builds its own `MessageModel` — it passes different query options than `MessageService.queryMessages` — so the conversation load never reached the projector at all. Measured against a seeded topic, the response was byte-identical to the unprojected one (193,619 bytes) while the WS snapshot path was already projecting. The projection step is now public on the service and the router applies it explicitly; the shared-topic branch stays unprojected, since an anonymous visitor has no authenticated way to fetch the stored payload back. The fallback renderer's on-demand control reused `debug.response`, so it rendered as a button labelled "Response" — a section heading, not an action, giving no hint that it loads what the read path left behind. It gets its own key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 📝 docs(acceptance): record two worktree bootstrap traps L-S23: a hand-built node_modules symlink farm passes vitest and tsgo but cannot start the dev server, and a fresh install cannot be the fallback in a repo that commits no lockfile. L-S24: the dev port is allocated per run and `/` answers 302 to /signin, so a remembered port and a 200 predicate both read as "never came up". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 💄 style(chat): load a projected tool result on expand, not on a button Expanding a tool row is already the user asking to see its result, so a second click to fetch what the read path left behind is a step that earns nothing. The fallback renderer now fetches as it mounts and shows a skeleton under the Response heading until the body lands. Mounting is the right trigger rather than a render-time effect: this component is the Accordion item's child and only mounts once the row opens. Verified in the app — expanding the assistant group fires no request, expanding one row fires exactly one. The control's dedicated string goes with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ✨ feat(tools): move the command output out of the conversation too Review asked why `runCommand` still shipped ~10 kB of state after projection. Because the projector kept `stdout`: the card renders the output inline, so it looked like the card needed it up front. It doesn't — the card lives inside the row's detail, which only mounts when the row is expanded, the same property that made load-on-expand safe for the fallback renderer. So the output goes with the other two copies, and what stays is the settled metadata the collapsed row reads: exit code, success, background flag, command id, stderr, produced files. Measured on the same seeded topic, that state went from 10,040 to 112 bytes and the whole response from 17,494 to 7,580 — 25.5x against the unprojected 193,619. Which surface has to fetch the payload back is now something the projector declares (`storedPayloadNeededBy`), carried to the client on the existing `payloadOmitted` field as `'detail' | 'render'`, rather than a second registry the projectors could drift from. `ToolRender` hydrates the `'render'` tools as it mounts; `'detail'` tools keep a self-sufficient card and only their detail portal fetches. Measured per step: loading the conversation, expanding the assistant group, and expanding the crawl row each fetch nothing; expanding the command row and the document row fetch exactly once each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ✨ feat(server): stop pushing the whole AgentState on agent_runtime_init `agent_runtime_init` shipped the raw `initialState` to every non-visitor subscriber: the entire `AgentState`, which carries the LLM context in `messages` plus the tool-set maps that `stripStateForStream` already treats as the size problem on `finalState`. It reached the wire unstripped because `publishAgentRuntimeInit` pushes directly rather than through `publishStreamEvent`, and because the existing chokepoint only looks for a `finalState` key. Nothing reads it. The web handler logs the event and breaks, the CLI prints a fixed line, and the gateway transport does not handle the type at all. Shared- agent visitors have been receiving `{ status }` alone for exactly this reason; every run now gets the same shape. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(server): close three gaps Codex review found in the projected read path **Recovery RPC skipped the conversation guard.** `getToolResultPayload` returned a tool's stored content and state after only the model's ownership scoping, which in a workspace is workspace-wide. A member holding a message id — including one kept after access was revoked — could read output from a conversation they cannot open. It now runs `assertCanViewMessageTargets` first, the same way the list read guards with `assertCanUseTopicTargets`. **Share visitors could never recover a projected payload.** `queryUiMessages` runs under the CREATOR's identity, so the gate read the creator's lab preference and pushed the visitor a projected snapshot; the recovery RPC then runs as the VISITOR against ownership-scoped reads that cannot see a creator-owned row, so the crawl portal, the raw viewer and the fallback had no way to fill it back in. Visitor snapshots now keep their payloads whole. **A projected tool read as still running.** The completion test was `!!result.content`, which an emptied body fails, so while the assistant message was busy a finished tool was swapped back to its loading placeholder. The stored length now rides `ChatToolResult` and one shared `hasToolResultBody` answers the question for both the group's completion check and the status indicator — the acceptance round missed this because its fixture was a settled conversation. Codex's fourth finding no longer applies: it described the projector deleting `output` while keeping an empty `stdout`, and that branch is gone — both keys are dropped together now and the card hydrates on expand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ✅ test(server): cover tool projection options in runtime snapshots * ♻️ refactor: slim ws2 step message transport --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9fd05abfed |
♻️ refactor(task): migrate subtasks tree to base-ui Tree and animate detail sections (#19567)
* ♻️ refactor(task): migrate subtasks tree to base-ui Tree and animate detail sections Claude-Session: https://claude.ai/code/session_01NS6gTHyi2Deg87BH4jox28 * ⬆️ chore(deps): bump @lobehub/ui to 5.47.0 Claude-Session: https://claude.ai/code/session_01NS6gTHyi2Deg87BH4jox28 |
||
|
|
41ce61b35d | ⚡️ perf(search): skip ineligible message indexing with versioned capture (#19537) | ||
|
|
d6cfa75d93 |
♻️ refactor: migrate Collapse and legacy Accordion to base-ui Accordion (#19532)
* ♻️ refactor: migrate Collapse and legacy Accordion to base-ui Accordion Replace antd `Collapse` and the legacy root `@lobehub/ui` `Accordion`/`AccordionItem` with `Accordion` (items API) and its atoms from `@lobehub/ui/base-ui` across sidebars, conversation messages, settings, eval, community detail and MCP detail. The root Accordion only styled the header; the base-ui one wraps the whole list, which is the intended visual. `@lobehub/ui/eslint` bans the root export going forward. Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * 💄 style(mcp): rework plugin detail schema section Tool / Prompt / Resource lists become one outlined base-ui Accordion each (mono name, one-line desc, param count), section spacing 64→40, mode Tabs pinned right, dashed one-line empty state. Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * 💄 style: use inline Accordion indicator to keep the compact sidebar look Bump @lobehub/ui to 5.45.0 and set `indicatorPlacement="inline"` wherever the legacy Accordion relied on its default (small arrow after the title, no panel indent). Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * ⬆️ chore: bump @lobehub/ui to 5.45.1 inline Accordion indicator now matches the legacy header metrics. Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * ⬆️ chore: bump @lobehub/ui to 5.45.2 AccordionRoot multiple by default, 32px inline headers with actions, lucide arrow. Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * 💄 style: inline Accordion indicator for tool cards and remaining panels AssistantGroup Tool, ModelDetailPanel, AcceptanceListPanel, ArtworkStudio, AgentSidebar Task and RunCreateModal still used the default start placement (chevron before the title next to the tool icon). Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * ⬆️ chore: bump @lobehub/ui to 5.46.0 7px inline arrow, auto-height panel motion with content fade, outlined header/panel spacing split. Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * 💄 style: let tool titles truncate by width instead of a 32-char cap Keywords were sliced at 32 characters regardless of available space; the title row already ellipsizes, so drop the cap and let the label keep its width while the keyword shrinks. Claude-Session: https://claude.ai/code/session_01SnPUiLfRXgaeBU3jSRZXHc * 🐛 fix: keep collapsed Accordion form fields mounted so saves don't drop them Claude-Session: https://claude.ai/code/session_01AZXinQXZWhf6P1rMrjLPsi * ✅ test: mock the base-ui Accordion in WorkflowCollapse test Claude-Session: https://claude.ai/code/session_01AZXinQXZWhf6P1rMrjLPsi * ✅ test: drop the dead Collapse mock from QuotaMenu test Claude-Session: https://claude.ai/code/session_01AZXinQXZWhf6P1rMrjLPsi |
||
|
|
1e8767eb2a |
♻️ refactor(mecha): extract agent config resolution into a shared package (#19476)
There are two Mecha layers doing the same job against different data sources. The browser assembles an agent's effective run config in `src/services/chat/mecha` (~2300 lines including its context builder); the server assembles the same thing in `apps/server/src/modules/Mecha` plus an 851-line `serverCallLlmContextBuilder` that inlines the resolution alongside reads of six database models. Both wrap the same shared `MessagesEngine`, and the primitives underneath (`getActivePluginIds`, `ToolResolver`, `resolveTopicReferences`) were extracted long ago — only the orchestration above them was left duplicated. Spot-checked rather than inferred from size: both sides independently compute `enabledToolIds`, `enabledManifests` and the merged `systemRole` from an agent config, producing the same shape from different sources. This moves the browser's resolution rules into `@lobechat/mecha` unchanged, behind an `AgentConfigSnapshot` of everything they previously read from Zustand. The browser now gathers that snapshot — 76 lines — and delegates. Nothing else changes: the barrel re-exports the same names, so no call site moved, and `getTargetAgentId` stays behind because it reads the active agent. The package takes the name both hosts already use for this layer, because the rest of it follows: context engineering, tool composition and model-parameter resolution are duplicated the same way and belong in the same home. A resolver is the first inhabitant, not the whole tenancy. It is a package of its own rather than part of `@lobechat/agent-runtime` because the runtime keeps exactly three runtime dependencies — everything heavy is type-only — and that is what let the CLI adopt it. These rules need `builtin-agents`, which alone pulls in eight builtin-tool packages. Folding them in would invert the property that makes the runtime portable. The runtime also never resolves config; it reads `state.metadata.agentConfig` as an opaque cast for a single field, so resolution belongs to the host that starts it. `canManage` is an input rather than something the rules compute. The browser answers it from the cache its picker populates and the server from `isResourceAuthorOrAdmin`, which is asynchronous — a seam that only works if the decision arrives already made. Behaviour is pinned by the existing suite: all 77 of `agentConfigResolver.test.ts` pass untouched, and they mock the stores, so they exercise the new gathering and the extracted rules end to end. The package also has its own suite that builds the snapshot by hand and never mocks a store, so a store read creeping back in fails there even while the client tests still pass. The server and a device adopt this in later PRs — one host at a time, so a regression stays attributable. For the device the payoff is the point: its context port stops being an 851-line problem and becomes a snapshot to fill. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0f526c7fc9 |
⬆️ chore(deps): bump @lobehub/ui to 5.43.0 for the global keyboard focus ring (#19483)
Claude-Session: https://claude.ai/code/session_01F6CQCZ4qgVppo4NeNxXA1b |
||
|
|
189bd0abe5 |
🚀 release: sync main branch to canary (#19435)
Co-authored-by: Innei <i@innei.in> Co-authored-by: YuTengjing <ytj2713151713@gmail.com> Co-authored-by: AmAzing- <115673583+AmAzing129@users.noreply.github.com> |
||
|
|
0b39ca88f3 |
💄 style: drop ScrollArea content typography workarounds after @lobehub/ui 5.42.7 (#19463)
@lobehub/ui 5.42.7 stops forcing 12px font, fixed line-height and a 16px flex gap on ScrollArea content, which is what shrank the nav panel. Bump and remove the per-callsite inherit/gap:0 overrides that only existed to undo that. Claude-Session: https://claude.ai/code/session_01FYDWLFnDqY7ngaqrECg49A |
||
|
|
d2f17fe2bf |
🐛 fix(chat-input): stop serializing a composer that is being torn down (#19425)
* 🐛 fix(chat-input): stop serializing a composer that is being torn down Navigating away from a chat page unmounts the composer while its editor still has debounced work in flight: onChange is 100ms debounced and the draft save 500ms. When one of those lands, the kernel it reaches may have no data sources left — they are registered when the root mounts and dropped on destroy — and getDocument/setDocument throw: DataSource for type "markdown" is not registered. at getMarkdownContent → updateMarkdownContent → onChange Worse than the throw is what a swallowed read would do: persistDraftFor reads an unserializable composer as an empty one, and an empty read during a draftKey transition removes the draft the user actually typed. Route every composer read/write through editorDocument, which skips the work when the kernel can no longer serialize instead of reporting empty content, and bail out of the draft persist, restore and key-transition paths on the same condition. Claude-Session: https://claude.ai/code/session_01FQvhSDp3pfnpYKbYt2mbS2 * ⬆️ chore(deps): bump @lobehub/editor to ^4.27.3 4.27.3 carries lobehub/lobe-editor#215, which cancels useEditorState's two debounced toolbar updates on cleanup. Without it a trailing call still reads the torn-down editor and throws "Cannot read properties of null (reading 'getEditorState')" — the second half of the crash this branch fixes. Claude-Session: https://claude.ai/code/session_01FQvhSDp3pfnpYKbYt2mbS2 |
||
|
|
f6d3b1cd96 | 🔨 chore: upgrade pnpm to 12.4.1 (#19437) | ||
|
|
3bb887ca4c |
🐛 fix(ci): restore release metadata sync and dispatch on main (#19424)
Co-authored-by: lobehubbot <i@lobehub.com> |
||
|
|
08cbe3236b |
♻️ refactor(html-artifact): extract the workspace HTML publish core into a package (#19356)
* ♻️ refactor(html-artifact): extract the workspace HTML publish core into a package `src/features/Portal/LocalFile` held the whole publish pipeline — relative-path analysis, the CSS/JS dependency crawl, the inline/sidecar packer, the path canonicaliser — inside a React portal directory. All of it is pure and has no UI dependency, and the CLI needs the same logic. Moved as-is to `@lobechat/html-artifact` (git sees renames, no rewrites). The one real split: `readWorkspaceAsset.ts` kept its browser/sandbox transport in `src` but its size limits, content-type resolution and result types move to the package, because importing that module for a constant would drag the browser services into a Node bundle. `getFileExtension` moves too, dropping one of the two copies that existed in `src`. Also fixes a silent drop in the gatherer. `addRemotes` only recorded `remote` skips, and only for the entry HTML — a reference rejected by the extension allowlist vanished with no record, and an out-of-root reference inside a nested stylesheet was never reported either. Both now flow through one `collectSkipped` that buckets every skip reason at any depth, with `unsupported` surfaced in the publish confirmation alongside the existing lists. Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT * 🐛 fix(html-artifact): keep the moved module paths importable for the business overlay The share micro-app build failed on `verify`: the closed overlay's `PublishHtmlArtifactButton.tsx`, `useWorkspaceHtmlArtifactPublish.ts` and `publishRunAction.ts` import `@/features/Portal/LocalFile/workspaceHtmlPath`, `…/workspaceHtmlArtifact` and `…/publishWorkspaceHtmlArtifact`, which this branch moved into the package. Nothing in the open repo imports them any more, so neither type-check nor the OSS build could see the break — only the overlay build does. Re-export exactly what each module used to export, so the move is backwards-compatible and this can merge without the overlay changing first. The overlay switches to `@lobechat/html-artifact` after its submodule bump, and these three files are deleted then. Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT * 🐛 fix(html-artifact): strip CSS comments by scanning instead of a backtracking regex CodeQL flagged `js/polynomial-redos` on the lazy `/\*[\s\S]*?\*\//g` used to strip comments before collecting `url()` references: on a stylesheet full of unterminated `/*` the match restarts from every opener, so cost grows with the square of the input. Measured on the old implementation — 20k openers 251ms, 40k 997ms; the scanner returns in 0ms for both, and the stripped output is identical for ordinary input. This matters here because the CSS is read from files the user names — a page in a workspace, or any local file handed to `lh artifact publish`. Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT |
||
|
|
aba9e8150c |
👷 build: migrate the test suite to vitest 5 (#19355)
* 👷 build: upgrade vitest to 5.0.0 Bump vitest and @vitest/coverage-v8 to 5.0.0 and migrate the configs for Vitest 4/5: drop the removed coverage.all option and replace poolOptions.forks.singleFork with maxWorkers/isolate. * 👷 build: shim jest-dom matcher types for vitest 5 Vitest 5 exposes Assertion<R, T> and no longer reads the jest.Matchers augmentation that @testing-library/jest-dom ships, so its matchers vanish from expect(). Re-declare them on the Vitest interface, and replace the removed describe.sequential with { concurrent: false }. * 👷 build: migrate the test suite to vitest 5 Vitest 5 (and @vitest/coverage-v8) replaces the esbuild dep optimizer with Rolldown, removed environmentMatchGlobs/coverage.all/poolOptions, forwards `new` to mock implementations, requires top-level vi.mock, and no longer resets automocks in restoreAllMocks. Migrate the root config to app/server projects with Rolldown optimizer options, shim the jest-dom matchers on Assertion<R, T>, and update the affected tests (constructor mocks, hoisted mocks, awaited assertions, explicit mock types, per-test mock defaults). * 👷 build: make the openapi knowledge-base FileService mock constructable tests/ was outside the per-area sweep; vitest 5 forwards `new` to the mock implementation, so the arrow implementation threw "is not a constructor". * 👷 build: point the CI blob-report artifacts at the vitest 5 output dir Vitest 5 writes blob reports to .vitest/blob instead of .vitest-reports, so the sharded jobs uploaded nothing and the merge jobs failed with ENOENT. Track the new directory in .gitignore too. * 👷 build: refresh the desktop lockfile for vitest 5 apps/desktop/pnpm-lock.yaml is committed and the desktop build action installs with --frozen-lockfile, so the vitest/override change has to be reflected in it. * 👷 build: keep the pre-vitest-4 coverage exclusions explicit Vitest 4+ ships an empty coverageConfigDefaults.exclude, so configs that spread it silently lost the default exclusions (node_modules, dist, *.config.*) and counted more files. Inline the previous list. * 🎨 style: format the migrated vitest configs * 👷 build: keep canary's new class mocks constructable under vitest 5 exploreGraph/dispatchAuthorization/scopedDevices (canary #19375, #19352) mock classes with arrow implementations; vitest 5 constructs the implementation when the mock is newed, so the arrows throw 'is not a constructor'. |
||
|
|
84b089450c |
♻️ refactor(ui): move DraggablePanel to the antd-free base-ui build (#19365)
* ♻️ refactor(ui): move DraggablePanel to the antd-free base-ui build @lobehub/ui@5.41.0 ships a headless DraggablePanel under `@lobehub/ui/base-ui`, rewritten on motion values so a drag never re-renders React. - switch all 10 import sites to `@lobehub/ui/base-ui` - drop `stableLayout`: the new panel holds the content size while the box animates, so the behaviour is unconditional - drop ChatTerminal's `smoothResize` workaround and align its unmount delay with the panel's own 250ms collapse Claude-Session: https://claude.ai/code/session_016Yc2kk25CBo6DXmeGJo1k9 * ♻️ refactor(share): move the share portal to the antd-free DraggablePanel The original sweep only covered `src/`, so this render site in the share app kept importing the antd component. Claude-Session: https://claude.ai/code/session_016Yc2kk25CBo6DXmeGJo1k9 * ⬆️ chore(deps): bump @lobehub/ui to 5.42.1 5.42.1 carries the fix that makes `collapseThreshold` work again behind `RightPanel`'s `expandable={false}`, which this migration depends on. Claude-Session: https://claude.ai/code/session_016Yc2kk25CBo6DXmeGJo1k9 * ⬆️ chore(deps): bump @lobehub/ui to 5.42.2 Picks up the drag-collapse clipping fix (lobehub/ui#651). Claude-Session: https://claude.ai/code/session_012BkEgtdLyHGbfKsHAL4Z5U * 🔧 chore: exclude the whole @lobehub scope from minimumReleaseAge Every release of ours is one we cut ourselves, so the release-age window only costs a version-pinned exclude entry on each bump. Claude-Session: https://claude.ai/code/session_012BkEgtdLyHGbfKsHAL4Z5U |
||
|
|
b796bb7e15 |
✨ feat(desktop): font fallback stacks and title-bar-safe floating popups (#19332)
The Appearance → Font page now edits `fontFamily` / `terminalFontFamily` as an ordered stack: the primary Select is unchanged, and a new "Fallback Fonts" row under each lets the user add up to three fallbacks, drag to reorder, and remove. The stored value stays a comma-joined `font-family` string, so nothing changes in the store or the CSS generation; fonts missing on this device keep their "unavailable" label instead of being dropped. Bumps `@lobehub/ui` to 5.40.3, which clamps Select popups to the viewport (the search field used to be pushed off screen when the popup flipped upward) and exposes `setFloatingCollisionPadding`. The desktop and popup entries now reserve `TITLE_BAR_HEIGHT` so every base-ui floating layer stays below the Electron title bar. Claude-Session: https://claude.ai/code/session_016gCNG3jtPnmQdrKTY7r7Jg |
||
|
|
b65aae96b6 |
👷 build: migrate workspace to pnpm 12 (#19324)
* 👷 build: migrate workspace to pnpm 12 pnpm 12 no longer reads the package.json "pnpm" field, only honours auth and registry keys from .npmrc, and requires allowBuilds instead of onlyBuiltDependencies. Move every project setting into pnpm-workspace.yaml and keep the previously effective overrides and build allowlist. * 👷 build: resolve desktop and cli workspaces on pnpm 12 pnpm 12 no longer accepts './'-prefixed workspace package globs, so the desktop stub path is now relative without the prefix. Migrate the remaining package.json "pnpm" fields into the desktop and cli pnpm-workspace.yaml files. * 👷 build: list protobufjs in the desktop build allowlist pnpm 12 fails the install with ERR_PNPM_IGNORED_BUILDS when a script-bearing dependency is missing from allowBuilds. protobufjs only appears in the desktop graph on Linux, so it was absent from the previous allowlist. * 👷 build: relink root workspace members before the desktop typecheck pnpm 12 writes broken relative symlinks for workspace members that live outside the desktop workspace root (../../packages/*), so tsgo cannot resolve sibling packages from source. Installing the root workspace after the desktop install rewrites those links correctly. |
||
|
|
6329f97e14 |
🐛 fix(vite): stop assuming node_modules sits at the repo root (#19325)
* 🐛 fix(vite): resolve the editor provider without assuming node_modules layout GlobalProvider/Editor.tsx reached @lobehub/editor's provider through a relative `../../../node_modules/...` path, which assumes node_modules sits at the repo root. Business-overlay desktop builds mount this repo as the overlay root's `lobehub/` submodule, so the install hoists to the parent directory and the path does not exist — every desktop platform failed with UNRESOLVED_IMPORT. The provider subpath is not in the package's export map, so it still cannot be imported on its own; the dev-only resolver now runs in builds too and derives the provider file from the resolved package entry, wherever it lives. Claude-Session: https://claude.ai/code/session_01LNsSw6SDv3UEGNfBEwPa8S * 🐛 fix(vite): resolve the @lobehub/ui barrel through the package The barrel parser reached the package's `es` directory through `../../../node_modules/@lobehub/ui/es`, which assumes node_modules sits at the repo root — the same assumption that broke the desktop build. Business-overlay share builds hoist the install above this checkout, where that path does not exist and `realpathSync` throws before the plugin can load. Resolving the package entry instead finds it wherever it is installed. The plugin is not loaded on the desktop platform, so this never surfaced there. Claude-Session: https://claude.ai/code/session_01LNsSw6SDv3UEGNfBEwPa8S * ♻️ refactor(vite): take the editor provider entry @lobehub/editor now exports @lobehub/editor 4.27.0 exports `./react/EditorProvider`, so the provider no longer has to be reached by file path and the build-time resolver and its ambient shim can go. The dev redirect stays: Vite prebundles the provider entry separately from `@lobehub/editor/react`, which the editors use, and the app would end up with two EditorContexts. Claude-Session: https://claude.ai/code/session_01LNsSw6SDv3UEGNfBEwPa8S * 🐛 fix(desktop): let pnpm find the business-const stub pnpm does not match the `./stubs/business-const` workspace entry, so an OSS install from apps/desktop fails to resolve `@lobechat/business-const` — the package the stub exists to provide — and the desktop build cannot be run locally at all. Cloud builds resolve the real package from the overlay, and the renderer redirects the module to `@cloud/business-const` either way. Claude-Session: https://claude.ai/code/session_01LNsSw6SDv3UEGNfBEwPa8S |
||
|
|
29acee69f6 |
✨ feat(desktop): added Computer Use with auv (#19051)
|
||
|
|
42f6dd299d |
✨ feat(home): merge workspace quota into the usage widget (#19272)
* ✨ feat(home): merge workspace quota into the usage widget - Rename the home usage widget to "Usage & Quota" and add the `userPanel.quota` copy so workspace subscription credits, top-up credits and member seats can live in the right-side widget (LOBE-13903) - Sort library hierarchy rows newest-first within folders so a just-created file shows up at the top, and expose a per-row menu button (LOBE-13814) - Keep the caret out of Lexical's root node around block images in comment editors via an opt-in `blockImageCaretGuard` (LOBE-13882) - Honour user-resized image widths in document comments and freeze the editor height while resizing (LOBE-13874) - Derive plugin display meta from marketplace-shaped manifests so community MCP rows never render an empty label - Bump `@lobehub/editor` to ^4.26.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TxoeVh86uEY7oEyzuhyU6y * 🌐 style(i18n): translate usage widget quota copy Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TxoeVh86uEY7oEyzuhyU6y * 🐛 fix(comments): match resized images whose width is the first inline declaration `LexicalRenderer` only emits `max-width` when the node carries `maxWidth`, so a resized image can serialize as `style="width: 320px"`. The leading-space selector missed that form and reapplied the thumbnail `width: auto` cap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TxoeVh86uEY7oEyzuhyU6y --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
8d852279db |
✨ feat(provider): add Unsloth provider support (#18339)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8ea955ddc5 |
✨ feat(model-runtime): add Meta AI provider with Muse Spark 1.3 (#19152)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
645ebf4998 | 🔨 chore: prepare v2.2.16 release ancestry | ||
|
|
a2890d4903 |
chore(sandbox): bump @lobehub/market-sdk to 0.41.0 (WAF false-positive fix) (#19130)
🔧 chore(sandbox): bump @lobehub/market-sdk to 0.41.0 runCommand/executeCode payloads sent to Market's run-buildin-tools endpoint naturally contain shell metacharacters (; curl, backticks, pipes), which Cloudflare's managed command-injection WAF rule was mistaking for an attack when they hit the wire as literal JSON text. Root-caused on topic tpc_rQ2lXneGO59w: tool calls came back with a bare "Forbidden", which the model misread as an auth problem and burned ~30min retrying credential checks. 0.41.0 makes PluginsService.runBuildInTool send params base64-encoded (paramsB64/paramsEncoding) instead of literal JSON. No code change needed on this side — MarketSandboxProvider (apps/server/src/services/ sandbox/providers/market.ts) already just forwards toolName/params straight into sdk.plugins.runBuildInTool. Rollout safety: Market's run-buildin-tools endpoint accepts both the old plain-`params` body and the new base64 form simultaneously, so a multi-instance/rolling Vercel deploy where old and new server instances briefly run side by side is not a compatibility hazard here either way. Claude-Session: https://claude.ai/code/session_01H7U5raw93D3toG4gCcLKYE Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9c81af8ad2 | ♻️ refactor(skeleton): migrate to the base-ui Skeleton and make the route skeleton the only loading layer | ||
|
|
522d001ee3 |
🐛 fix(bot): always verify Telegram webhooks and pin Chat SDK to ~4.38.0 (#18930)
* 🐛 fix(bot): always verify Telegram webhooks with a derived secret and pin Chat SDK to ~4.38.0 * 🐛 fix(bot): await webhook re-registration and share its cooldown via Redis * ✅ test: type test fixtures against chat 4.38 typings |
||
|
|
3ba4e60221 |
🐛 fix(inspector): shimmer the label span instead of the whole row (#18978)
* 🐛 fix(inspector): shimmer the label span instead of the whole row textStyles.shiny clips to text via mask-clip: text, and that mask applies to the whole subtree — icons, chips and status glyphs inside a shiny row were erased. Move the class onto the pure-text label span so the mask stays on the compositor path and the rest of the row paints normally. TodoInspectorSummary takes a shiny prop for the rows that delegate their label to it. * ♻️ refactor(inspector): unwrap the redundant nested cx call * ✨ feat(inspector): give inspector rows the shiny sweep coordinate space The row now holds position and hands its spans --shiny-origin: static, so several shimmering spans in one row share a single row-wide sweep instead of each playing its own. Rows that keep a plain-text tail after an avatar shimmer that tail with the label again. * ⬆️ chore(deps): bump @lobehub/ui to ^5.37.1 * 🐛 fix(inspector): shimmer the Vent label span instead of the whole row * ⬆️ chore(deps): bump @lobehub/ui to ^5.38.0 and use its shinyGroup |
||
|
|
9fc8a23881 |
🐛 fix(acceptance): center reject modal instead of pinning it left (#18951)
* 🐛 fix(acceptance): center reject modal instead of pinning it left createModal maps styles.popup onto the full-screen overlay (position:fixed; inset:0). Sizing that overlay with height/maxWidth makes left:0 win when max-width underconstrains, so the dialog sticks to the left. Apply panel size to the inner dialog instead. * 💄 style(acceptance): align reject modal title and body on one inset The dialog header uses 16px padding-inline while the body kept the library's padding-inline plus a 20px footer, so the title sat left of the form. Zero the content logical padding and share 16px. * ♻️ refactor(acceptance): inherit modal content padding for reject dialog The shell was zeroing content padding and re-applying 16px on the header and footer. Title and body already share the upstream padding-inline, so stop restyling the inset. * 💄 style(acceptance): flush reject-modal attach with textarea Bump @lobehub/ui to 5.37.0 for Button outdent, let the attach text button cancel its padding-inline, and make antd Upload display:contents so the negative margin shifts the row. Match AcceptanceCheck portal header padding with the 24px body. * 💄 style(acceptance): align portal header title with body Match Acceptance portal chrome padding to the 24px viewer body so the title lines up with the page heading. * ✅ test(acceptance): assert reject modal style keys safely |
||
|
|
d8722c7a84 |
✨ feat(goal): plan the exploration structure before executing (#18942)
* 📌 fix(deps): unpin @ant-design/icons now that 6.3.4 is fixed Upstream shipped @ant-design/icons@6.3.4 with a matching @ant-design/icons-svg@4.6.0, so the MetaFilled resolve failure that forced the 6.3.2 pin (#18923) is gone. Drop both override entries and float the direct dependency from ^6.3.4 — starting the range at the fixed release so a stale registry mirror can never resolve the broken 6.3.3. Verified locally: fresh install resolves 6.3.4 + icons-svg 4.6.0, es/asn/MetaFilled exists, and `bun run build:spa:raw` exits 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): size the inline exploration map to its content - The inline canvas was a fixed 560px frame, so a two-node graph rendered as a small cluster adrift in empty margin (fitView shrinks nodes to fill the frame). Size the frame from the layout bounds instead; 560px stays the ceiling for large graphs. - Move each card's state chip (and the @ human marker) from the title row's trailing edge to the card's bottom-left: a task folds them into its metric strip, other kinds get a light footer, and the title keeps its full width. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ✨ feat(goal): plan the exploration structure before executing Creating a goal used to copy the raw ask verbatim into exactly one problem node + one task and dispatch it immediately: the problem node's description was the full requirement (acceptance boilerplate included), the single task mirrored the whole request, and its acceptance objective concatenated the entire goal contract again. No decomposition ever happened. - New `plan_decomposition` coordinator branch: a goal with no work is a planning cue, not a dead end. On first advance an LLM plans the core problem statement plus 1–5 independent work directions (chainGoalDecompose, traced as goal_decompose); planner failure degrades to a single work seeded from the requirement so the goal never stalls. - Callers stop seeding the mirror task; they pass `problemDescription` (the user's own words) for the problem node instead of the requirement blob. - A Work's acceptance objective no longer embeds the full goal requirement — each direction is judged on its own outcome; the complete contract stays with the terminal acceptance task. - `GoalGraphModel.updateNodeDescription` lets the planner replace the seeded description with its own problem statement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): use the task family's running color on graph node chips The running chip was a lone colorInfo blue while every other running signal (frontier glyph, task rows) draws from TASK_STATUS_VISUALS — the odd color read as a different state. Reference the shared visual instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): icon-only verifier metric; CLI passes problemDescription - With the state chip sharing the metric strip, the verifier label no longer fit the card width and wrapped mid-word; the tooltip already carries the full meaning, so keep the shield icon only. - `lh goal create` gains `-i/--instruction` so the CLI can seed the problem node with the user's own words like the modal and the /goal tool do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): move node state chips to the bottom-right (review feedback) Bottom-left read poorly in review; the state now sits on the card's bottom-right — a task folds it into the right edge of its metric strip (running clock rides along), other kinds right-align their footer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): status reads first — its own top row with the running clock Neither bottom corner sat well in review. The state chip now opens the card as its own left-aligned top row, with the elapsed clock right behind it when the task runs; the title keeps the full width below, and with the chip out of the metric strip the verifier label fits again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): every task state reads from the top status row "Not dispatched" was still hiding in the metric strip while other states had moved to the top row. A task now always carries a status chip — queued, waiting, unassigned included — and the metric strip keeps only verifier, attempts and artifacts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): status chips draw the task family's glyphs, not dots A colored dot said less than it could: the status row now renders the same glyphs the task surfaces use — the animated running ring, the dashed circle for not-yet-dispatched, CircleX for lost, CircleCheck for done — so a node's state reads identically everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
192f60157d | ✨ feat(devdock): integrate Mesurer inspector (#18955) | ||
|
|
de5e0d52fd |
📌 fix(deps): pin @ant-design/icons to 6.3.2 (#18923)
📌 fix(deps): pin @ant-design/icons to 6.3.2 (#18922) * 📌 fix(deps): pin @ant-design/icons to 6.3.2 @ant-design/icons 6.3.3 (published 2026-08-31 06:53 UTC) ships a MetaFilled icon that imports "@ant-design/icons-svg/es/asn/MetaFilled", which does not exist in the latest @ant-design/icons-svg (4.5.0). Any fresh install resolving the caret range breaks the SPA build: Error: [vite]: Rolldown failed to resolve import "@ant-design/icons-svg/es/asn/MetaFilled" from ".../@ant-design/icons/es/icons/MetaFilled.js" Every CI run since the release fails (Test Web App + Vercel). Pin to the last good 6.3.2 until upstream ships a matching icons-svg. * 📌 fix(deps): pin the direct @ant-design/icons dependency to match the override npm requires an override spec to be identical to the direct dependency spec (EOVERRIDE otherwise), and Vercel's `npx pnpm install` runs that validation. Follow the repo's existing pin pattern (antd, better-auth, lexical): exact version in both dependencies and overrides. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a4c4814d45 |
Revert "📌 fix(deps): pin @ant-design/icons to 6.3.2 (#18922)"
This reverts commit
|
||
|
|
65c35a1b93 |
📌 fix(deps): pin @ant-design/icons to 6.3.2 (#18922)
* 📌 fix(deps): pin @ant-design/icons to 6.3.2 @ant-design/icons 6.3.3 (published 2026-08-31 06:53 UTC) ships a MetaFilled icon that imports "@ant-design/icons-svg/es/asn/MetaFilled", which does not exist in the latest @ant-design/icons-svg (4.5.0). Any fresh install resolving the caret range breaks the SPA build: Error: [vite]: Rolldown failed to resolve import "@ant-design/icons-svg/es/asn/MetaFilled" from ".../@ant-design/icons/es/icons/MetaFilled.js" Every CI run since the release fails (Test Web App + Vercel). Pin to the last good 6.3.2 until upstream ships a matching icons-svg. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 📌 fix(deps): pin the direct @ant-design/icons dependency to match the override npm requires an override spec to be identical to the direct dependency spec (EOVERRIDE otherwise), and Vercel's `npx pnpm install` runs that validation. Follow the repo's existing pin pattern (antd, better-auth, lexical): exact version in both dependencies and overrides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bf5a10fd57 |
💄 style(skill-store): drop dead modal header overrides (#18920)
* 💄 style(skill-store): drop dead modal header overrides These three modals pass no `title`, so `createModal`'s StackItem never renders a header — `styles: { header: { display: 'none' } }` hid nothing. Removing it lets them pick up base-ui's header-less body inset instead of relying on the shared content padding. Also add ConfigProvider and motion/react to the design-prototype runtime: base-ui Button throws without a MotionProvider, which made any prototype using it fail to mount. * 🔧 chore(deps): pin @ant-design/icons to 6.3.2 6.3.3 imports @ant-design/icons-svg/lib/asn/MetaFilled, which does not exist in icons-svg 4.5.0 (latest). |
||
|
|
321bfe8623 | ⚡ perf: accelerate and harden Elasticsearch reindex recovery (#18902) | ||
|
|
d74e897191 | ✨ feat: add Elasticsearch full-text search provider (#18807) | ||
|
|
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> |
||
|
|
fc519ef74f |
⚡️ perf(styles): reuse the lobe-ui shiny text style (#18850)
* ⚡️ perf(styles): reuse the lobe-ui shiny text style Both local shinyText copies animated background-position, which repaints every glyph each frame. @lobehub/ui now ships the mask + transform version as textStyles.shiny, so drop the duplicates and point at it. Claude-Session: https://claude.ai/code/session_012Pjd1gcsbxQmrVkuRkPYdu * ⬆️ chore(deps): bump @lobehub/ui to ^5.36.0 Ships textStyles.shiny, which this branch now depends on. Claude-Session: https://claude.ai/code/session_012Pjd1gcsbxQmrVkuRkPYdu |
||
|
|
5cb99eecff |
🚀 release: 20260827 (#18747)
# 🚀 LobeHub Release (20260827) **Release Date:** August 27, 2026 **Since v2.2.14:** 235 merged PRs · 13 contributors > This cycle makes long-running Agent work easier to direct and recover, expands provider-backed heterogeneous Agents, delivers safer restart-free Desktop updates, and deepens Artifact, Workspace, and acceptance workflows. --- ## ✨ Highlights - **Provider-backed heterogeneous Agents** — Claude Code and Codex gain deployment-backed model options, while Kimi Code and Pi add new provider paths across web and Desktop. (#18468, #18558, #18609, #18658, #18720, #18721) - **Restart-free Desktop updates** — Signed renderer updates apply in place with compatibility checks, compressed delta delivery, release-scoped artifacts, and automatic rollback. (#18648, #18694, #18716, #18724) - **Agent approval and intervention flows** — Agent approvals now align across surfaces, and generic interventions persist so long-running work can resume with the right user decision. (#18417, #18645) - **Agent self-learning** — A Labs-gated growth portrait shows learned domains, rules, and practice history while operation-scoped expertise improves future Work. (#18195, #18394, #18420) - **Public Artifacts and Workbench** — Local HTML and site assets can be packaged into versioned Artifacts, while acceptance and verification run in a standalone SSR Workbench. (#18424, #18473, #18569, #18728) - **Workspace collaboration** — Per-member Resource grants, transfer requests, and a redesigned Notification Center make shared work easier to control and act on. (#18389, #18432, #18536, #18545) - **More adaptable everyday UI** — Global interface fonts, consolidated Home promotions, clearer model availability, and refined recommendation visuals make the app easier to personalize and navigate. (#18712, #18722, #18730, #18741) --- ## 🏗️ Core Agent & Architecture ### Goals, Tasks & Acceptance - Goal state moves to a durable graph with dependency-aware scheduling, CLI controls, autonomous recovery, and terminal Acceptance Work. (#18574, #18597, #18635) - Scheduled Tasks get a dedicated view, successful subtasks complete automatically, and Kanban grouping matches the list view with clearer loading states. (#18586, #18591, #18631, #18707) - Acceptance deliveries group by Project and can be merged without losing checks, evidence, or prior review decisions; the review round now always reports its outcome. (#18448, #18537, #18548, #18672) - Acceptance features now share one consistent product boundary, while abandoned Goal Work and stale scoped tool calls recover instead of silently ending the run. (#18656, #18664, #18703) ### Agent Learning & Identity - Self-learning gains dedicated domain creation, editable anchors, operation-scoped Context, and safer lesson history management. (#18418, #18420, #18517, #18556) - Agent profiles gain a character sheet and reusable full-body artwork that retains its style and direction. (#18446, #18464) - Agent authors can restrict Topic sharing, while Agent transfers preserve accumulated expertise. (#18445, #18557) - Approval workflows align with web behavior, and generic interventions persist across Agent runs. (#18417, #18645) --- ## 📱 Platforms & Integrations ### Heterogeneous Agents - Claude Code supports local Provider bindings, while supported Claude Code and Codex runs can use deployment-backed models with operation-scoped authorization. (#18468, #18558, #18595, #18609, #18621) - Kimi Code joins heterogeneous Agent provider bindings, and Pi gains a dedicated Desktop provider path. (#18720, #18721) - Each Topic keeps its own heterogeneous model choice instead of rewriting the Agent default. (#18658) - Cursor moves to a bidirectional ACP session with permission prompts, questions, approvals, and safe resume recovery. (#18475) - OpenClaw and Hermes detection now covers official install paths, refreshed shell environments, and Windows launchers. (#18646) ### Desktop, Messaging & Gateway - Desktop applies verified renderer updates without restarting, keeps compact rollback deltas, and scopes OTA artifacts to the matching release. (#18648, #18694, #18716, #18724) - macOS communication notifications include Agent avatars, Workspace windows retain their bounds, and the terminal adds links and split panes. (#18430, #18433, #18441, #18500) - Gateways can pull the connection assignments they should hold, while message routing can still split platforms across deployments without changing the Integration protocol. (#18628, #18680) - Linked chat platforms receive Resource files and attachments with per-platform budgets, while scheduled Task results and rich Agent pushes appear in the Notification Center. (#18391, #18467, #18511, #18535, #18561, #18608) --- ## 🖥️ User Experience - Realtime voice dictation adds finalized speech to the editable draft without auto-sending or disturbing existing text and attachments, and its entry point now sits with the left-side chat actions. (#18132, #18578, #18709) - Home restores its marketing hierarchy, consolidates promotional surfaces, sharpens recommendation visuals, and brings back richer model shortcuts and the Apps catalog. (#18438, #18528, #18553, #18562, #18580, #18722, #18741) - A global interface font setting and locale-aware font stacks make reading preferences consistent across the app. (#18712) - GLM-5.3-Flash is available, disabled models can be re-enabled from chat input, and model prices survive OAuth redirects. (#18723, #18727, #18730) - Dead Topic locks no longer swallow sent messages, and completed gateway runs clear stale loading state in the sidebar. (#18497, #18682) - Agent document edits recover malformed persisted content and reject operations that would unexpectedly clear a non-empty document. (#18643) - Settings search covers more destinations and matches user queries more reliably. (#18390) --- ## 🔧 Tooling - Agent Chaos adds deterministic timeout, cleanup, tracing, and failure-injection contracts for long-running Agent tests. (#18667) - Evaluation Run Topics restore Test Case history and environment Context, and tool calls can be forwarded to mock servers for end-to-end verification. (#18516, #18604) - Workbench CI checks Worker bundle size and publishes PR previews after assets reach R2. (#18492, #18592) - Desktop release automation dispatches renderer OTA before native releases, and shared UI primitives continue moving to the base component layer. (#18716, #18729) - Obsolete Orb repository setup scripts are removed from the development path. (#18740) --- ## 🔒 Security & Reliability - **Security:** Resource checks stay scoped to the active Workspace and support per-member grants without bypassing privacy or role ceilings. (#18396, #18406, #18525, #18536, #18545) - **Security:** Server-default heterogeneous operations bind authorization to the user, operation, Workspace, and allowed capabilities. (#18558) - **Reliability:** Context compression avoids repeat work, removes compressed messages from queued payloads, and settles in-flight tool calls when interrupted. (#18506, #18587, #18610, #18626) - **Reliability:** Completed heterogeneous child Agents resume their parked parent, while RouterRuntime preserves request Context and retries the next configured channel on insufficient Provider balance. (#18596, #18668) - **Reliability:** Multi-tab settings changes no longer overwrite newer tool approval choices with stale state. (#18705) - **Performance:** Message-plugin and heterogeneous tool-call lookups avoid broad scans, and Task lists use a leaner database path. (#18457, #18519, #18618) --- ## 👥 Contributors Huge thanks to **13 contributors** who shipped **235 merged PRs** this cycle. @ONLY-yours · @orangeboyChen · @jalenzz · @NickShee · @BittuBarnwal7479 · @arvinxx · @Innei · @AmAzing129 · @rdmclin2 · @tjx666 · @sudongyuer · @cy948 · @nekomeowww --- **Full Changelog**: https://github.com/lobehub/lobehub/compare/v2.2.14...release/weekly-20260827 |
||
|
|
4dc26d48ef |
✨ feat(goal): add process control UI and server-driven execution (#18762)
* ✨ feat(goal): add the process-control surface to the Goal detail page Move the prototyped process-control UI into the product: the frontier list (what can move now), the exploration map on react-flow, the findings list and a per-node activity feed. Data comes from one `goal.graph` read. The server has no frontier projection, no per-node attempt roll-up and no artifact hydration, so the client reproduces the coordinator's own selection rule and derives the attempt ledger from the `goal_events` trail. Anything that cannot be derived honestly is omitted rather than invented. A Goal Graph goal has no carrier task, so the detail route falls back to a graph-only page when the identifier resolves to no task. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ♻️ refactor(goal): make the Goal Graph the only goal model A goal used to be a `goals` row bound to one carrier task, driven by an outer loop in the verify settle path. That was the early attempt; the Goal Graph supersedes it — the graph owns the decomposition and dispatches a Task per Work node, each with its own acceptance contract, and stops on a decision gate when a Work runs out of attempts. Server - `GoalModel.list` is goal-centric: it reads `goals` and derives the roll-up (work done/total, findings, pending gates, cost) from the graph, so a goal without a carrier task is no longer invisible. - Delete the task-carried outer loop: `verify/goalLoop`, `verify/goalBudget`, the goal branches in `settle` / `acceptanceService` / `planInstantiation`, and the goal binding in `TaskService.createTask` and `task.create`. - `/goal` now creates a Goal Graph and advances it once; the coordinator creates the responsible task and its verifier. - New `goal.delete`; `task.deleteGoal`, `hasGoal` filters and `TaskItem.goal` are gone with the flow that needed them. Client - Goal list, detail, home rail and the conversation card read the graph. - The goal detail route is keyed by the `goals` row id. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): read the attempt ledger from real attempt boundaries Found while driving a goal end to end: the frontier row of a running task showed no clock, and a finished task's attempts were labelled from a bookkeeping event. `goal_events` writes `updated` for housekeeping too ("Attached Work version …"), and every `updated` was closing the open attempt. That ended the live attempt one event early — so `startedAt` was never set and the running clock disappeared — and stamped the housekeeping reason onto the previous attempt. Only `activated` now opens an attempt, and only a boundary closes it: the next `activated` (the coordinator started another attempt, so this one did not deliver) or a terminal event. A Work parked at a decision gate leaves its attempt open, since the gate is written as `updated`, so the node's own status closes it. A gate row also showed an empty ledger, because attempts live on the Work and the row is the decision node. The gate now carries the Work it was opened for and renders that Work's ledger — which is the case the user is deciding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 💄 style(goal): quiet the exploration map and make one press run the goal Review feedback on the acceptance round, item by item. The map was shouting the same thing in three places at once — a pill floating on the card's outline, an emphasis border, and a drop shadow — which left no room for the edges routed around it: - cards go flat; state becomes a dot plus a word on the header's trailing edge, where the "you took part" mark now sits too; - edges become bezier curves instead of right-angled steps; - the frontier emphasis border is gone. It was drawing a heavy outline around a *completed* card, which is the wrong signal entirely; selection keeps a single light border; - inline, the map is a picture: no zoom controls, no pan, no zoom. Fullscreen is where it becomes navigable. The advance button ran a single coordinator tick. That is an implementation unit, not one a person cares about — dispatching a work takes two ticks, so one press usually looked like it did nothing. It now advances until the goal is achieved, needs a decision, or has nothing ready, the way `lh goal run` does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ✨ feat(goal): drive goals from the server instead of a client tick loop A Goal Graph only moved while something kept calling `goal.tick` — a browser loop or `lh goal run`. Close the tab and a long-horizon goal stopped, which is the opposite of what "long-horizon" is supposed to mean. Goals now advance on their own, event-first with a sweep behind it: - `advanceGoal` runs the coordinator until it is waiting on something that is not another tick. `waiting_external` is a stop, not a poll: a Work Task is executing, and that task's own settle is the next event. - Something that may have unblocked a goal queues an advance — creating it, resolving a decision gate, resuming it, raising a budget, and a Work Task settling in the verify path. - `/api/workflows/goal/sweep` is the safety net a purely event-driven chain needs: it picks up goals with no fresh active Work and no pending gate, so a dropped queue message, a runner that died mid-dispatch, or a Work that outlived its operation lease cannot strand a goal forever. - The scheduler mirrors `taskScheduler`: QStash in queue mode, in-process timers for dev and desktop, so both exercise the same runner. The page's advance button hands off to the server in one call rather than holding a tick loop open, and `goal.advance` is the nudge for "don't wait for the next event". The loop that used to live in the browser now lives where it can keep running without one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): actually queue the advance when a goal is created The create procedure was the one enqueue site that never landed, so a goal sat in `planning` until something else poked it — the exact failure the automatic driver exists to remove. Caught by running a goal end to end with no client attached: creation published nothing to the queue. A dispatch failure is now logged loudly rather than only through `debug`. This is the only thing that makes a goal move on its own; when it breaks, the symptom is goals that quietly stop, which is close to impossible to attribute from the outside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ♻️ refactor(goal): import Text/Tag/ActionIcon from base-ui The base-ui migration (#18729) landed on canary while this branch was open, so the Goal process-control components still pulled the deprecated antd wrappers. Move them over and drop the acceptance imports the Goal detail page no longer uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): close the gaps the review found in the graph-only migration Seven problems, all from making the Goal Graph the only goal model: The create modal still showed a visibility control whose value went nowhere, so a goal marked private dispatched Work Tasks that fell back to the assignee agent's visibility and published its output to the workspace. Visibility now rides on the goal's `config` — no new column — and the coordinator stamps every task it creates. Goals from the earlier task-carried flow have no `goal_nodes`, and the new goal-centric list returned them alongside graph goals: a goal page with no work, no frontier and no way forward. They stay out of both the list and the sweep's work list until something backfills them. `task.create` no longer accepts a `goal`, but Zod was stripping the field rather than rejecting it, so a released client would report that a goal started when only an ordinary task existed. It now fails with a message that says to reload. The sweep is the safety net for an event-driven chain, and self-hosted deployments only ever provisioned the task dispatcher schedule, so nothing called it. `startServer.js` now registers both. Two advances can overlap — an event hook, a manual nudge, the sweep — and `runTask` decides whether a run is in flight by reading topics before creating one, so both could dispatch the same Work and bill it twice. The coordinator claims the task with a conditional status update first, and releases it if the dispatch throws. Deleting a goal cascaded its graph away while leaving its Work Tasks and their agent operations running, spending budget with nothing left on screen to stop them. Deletion now cancels running topics and pauses those tasks first. Project goal cards had no `aid` in the route, so every card on the project Goals page rendered as a link that did nothing. They fall back to the goal's own agent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): stop a public goal on a private agent from wedging its Work Two problems with the visibility the previous commit added. `GoalService.create` still declared its own narrower config type, so the shared `GoalConfig` field did not type-check on the server — which the CI type-check caught and the runtime tests could not. Forwarding an explicit `public` to `createTask` also buys nothing: it is what the service derives anyway, and stating it outright trips the "a public task can never run on a private agent" invariant, leaving the Work throwing on every tick. Only `private` — the value that actually has to be preserved — is passed through now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): keep the page live, claim recovery retries, close terminal goals Three more from the review, all consequences of the server now driving goals. The graph snapshot only revalidated on focus. That was fine when the client ticked the goal itself and refreshed after every step; now the server advances it, so an open page sat on its first snapshot — a spinner still turning on a goal that had already finished. It polls while the goal is in a status the server can move, and stops for `review`, `paused` and the terminal ones, where the next change comes from a person and their action refreshes it anyway. The dispatch claim added in the previous commit only covered starting a backlog Work. `WorkRecoveryCoordinator.recover` spawns its own run for the two automatic-retry paths, so overlapping advances could still each pay for an attempt. The claim now lives in `recover`, where the spawn is. Both callers hand it a task row read before they paused it, so it compares against what the row says now — and treats an already-`running` task as someone else's claim rather than its own. The goal page still offered Advance and Add Task on an achieved, failed or canceled goal. The coordinator returns immediately for those, so Advance reported nothing happened and a Work added there stayed `proposed` with no way to run it. Those controls are gone once a goal is closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): stop the client racing the queued driver, free orphaned claims Three more from the review. Creating a goal queued an advance on the server and then fired a single tick from the client. A tick is one step: the one that binds the Work's task, not the one that starts it. If that tick won the node claim, the queued driver saw the claim, reported `waiting_external` and stopped — leaving the task in backlog with nobody to start it until the sweep noticed. Both creation paths now run the full driver, so whichever one claims the Work carries it through to starting it and the loser stops harmlessly. The dispatch claim is taken just before `runTask` creates the task's topic. A worker that died in that sliver left the task `running` with no operation behind it, and `recoverAbandonedWork` returned immediately because there was nothing to reclaim — so every later advance reported `waiting_external` and the Work never ran again. A claim with no run behind it is now handed back once it is older than the operation lease, which is also long enough that a run about to start is never mistaken for an abandoned one. The Advance tooltip still told users graph goals have no server-side driver and only move while a client advances them — the opposite of what this branch implements, and an invitation to sit on the page pressing the button. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): scope private goals, and stop attempts capping the whole graph Five more from the review. A goal marked private only made its Work Tasks private. The goal row itself has no visibility column, so workspace scoping alone left its requirement, its graph, its findings and its whole event log readable by every member — the one thing the private toggle exists to prevent. `buildGoalScopeWhere` now enforces the stored `config.visibility` on every goal and graph read. A real column that `buildWorkspaceWhere` can take directly is the better long-term shape; this holds the guarantee without a migration. The creation form's only iteration field means "attempts on one Work", and it was also being sent as `maxRounds` — which the coordinator counts across every Work in the graph. With the default of three, three finished tasks exhausted the budget and a fourth never started despite having run nothing. The two limits are separate now, and the same conflation is gone from both `/goal` paths. `null` in that field is the manifest's documented "no user-specified cap", but it was folded into the default of three, so clearing it quietly capped the goal anyway. It now leaves the setting off and lets the recovery policy default apply. Raising the budget of a goal the budget had stopped left it `paused`, and `tick` refuses to move a paused goal — so the advance this mutation queues returned immediately and the user had to find Resume as a second gesture. A goal whose budget was actually binding is reopened; one somebody paused deliberately is left alone. Goals created from a project page have no responsible agent, so the earlier `goal.agentId` fallback was still empty and every card there was a dead link. They route to a bare `/goal/:goalId`, mirroring how tasks already fall back to `/task/:taskId`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🔒 fix(goal): gate goal deletion and inherit a private agent's visibility Four more from the review. Deleting a goal only checked that the caller holds `agent:update` in the workspace — which says a member may change goals, not whose. Any member could delete a colleague's goal and cascade its whole graph away. It now asserts the same creator-or-workspace-owner rule that task deletion already applies. `/goal` runs as an agent and never states a visibility, so a goal created by a private agent was workspace-public even though the task it dispatched was inferred private. `GoalService.create` derives it from the responsible agent the way `createTask` does, which covers both the client executor and the server runtime instead of asking each to remember. Deleting a goal cancelled its running work but swallowed a cancellation failure. If the runtime or a device gateway was unreachable the graph was deleted anyway, leaving an operation spending with nothing left that could stop it. That failure now aborts the deletion and says why. The bare `/goal/:goalId` route rendered only a title, and the actions menu it omitted is the only place a goal can be deleted — so a goal created from a project could never be removed. The menu is no longer nested under the agent breadcrumb, and both it and the post-delete destination fall back to the project scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): park unmovable goals and keep a failed kickoff from looking unsaved Two more from the review. A goal with no Work — or whose every remaining Work is blocked with nothing running to unblock it — can only ever report `no_progress`, but it stayed `running`. The sweep takes the newest stalled goals up to a limit, so enough of these permanently occupy that window and every older stalled goal stops being swept at all. They are now parked the same way a goal waiting on a decision gate is: the row says what it is, and the scan moves on to goals that can actually move. Creating a goal through `/goal` reported a kickoff failure as `GoalCreateFailed` even though the goal was already committed — so the agent, told nothing was created, would create a second one and both would do the same paid work. Once creation succeeds the goal id is always reported; a failure to start it now says so and points out that the server will pick it up. Both the client executor and the server runtime had the same boundary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): queue the advance for goals created by the server runtime The previous commit stopped reporting a failed kickoff as a failed creation, telling the agent the server would pick the goal up instead. That is true for `/goal` from a client, which goes through the `goal.create` route, but the server-side tool runtime calls `GoalService.create` directly and nothing scheduled anything. Outside queue mode there is no local timer for the goal and no recurring sweep either, so a kickoff failure left it stranded in `planning` while the agent had been told not to create it again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ⚡️ perf(goal): stop every goal card refetching the whole graph each second The card's timer drove both the elapsed clock and a full graph revalidation, from when the client had to keep a goal moving itself. The store already polls the snapshot while the goal is one the server can move, so this was a second, five-times-faster copy of that: ten cards in a conversation pulled the whole node and event history six hundred times a minute. Worse, a card whose fetch had failed had no status to read, resolved as unsettled, and kept asking forever. The timer now only ticks the clock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ⏪️ revert(goal): drop the goal visibility control Goals do not carry a privacy promise for now, so the whole mechanism comes out rather than sitting half-wired: the `config.visibility` field, the creation input that set it, the agent-derived default, the stamp the coordinator put on each Work Task, and the `buildGoalScopeWhere` predicate that enforced it on reads. Goal and graph reads are back to plain workspace scoping. The chooser goes with it. Leaving the control on the creation form while nothing acts on it is exactly the state this branch was reported for: a user marks a goal private and it is not. Deleting a goal still requires being its creator or a workspace owner — that is about who may destroy someone else's work, not about who may read it, and it matches what tasks already enforce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvin.x@lobehub.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fad16ebb1d |
🐛 fix(chat-adapter): adapt to chat@4.39.0 fetchData Buffer | ArrayBuffer contract (#18790)
Claude-Session: https://claude.ai/code/session_01523f6A6kqKpJQSM6HmgzyE Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
76fecbc714 |
🐛 fix(chat): download chat images through the file proxy (#18784)
The image viewer downloads by reading the source as a blob, which the file proxy URL can never satisfy: `/f/:id` redirects to a presigned URL on an origin that sends no CORS headers, and a cross-origin redirect strips the Origin header to `null`, so no bucket rule can allow it either. Chat image previews now resolve the proxy's `download=1` response instead, which carries a Content-Disposition and saves the file under its stored name. Other sources keep the blob path. Requires @lobehub/ui 5.35.0 for the `preview.onDownload` option. |
||
|
|
8ad5b8bfd7 |
🔨 chore: sync release/weekly-20260827 with latest canary (#18786)
* ✨ feat(heterogeneous-agent): support Grok provider bindings (#18737)
* ✨ feat(db): add workspace document comment schema (#18726)
* ✨ feat(db): add workspace document comment schema
* ♻️ refactor(db): use UUIDs for document comments
* 🐛 fix(db): sync comments during document transfers
* 🐛 fix(db): harden document comment transfers
* 🔨 chore(db): regenerate document comment migration
* 🐛 fix: align chat output cost estimate ratio (#18748)
* ♻️ refactor(desktop): migrate renderer OTA to V2 packs (#18745)
* ♻️ refactor(desktop): migrate renderer OTA to V2 packs
* ⚡️ perf(desktop): decode renderer packs asynchronously
* 💄 style(toast): align toast radius and offsets with desktop layout container (#18755)
* 👷 ci(desktop): read the overlay repository from a variable instead of hardcoding (#18759)
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* 👷 ci(share): add PR-time verify workflow for the share micro app (#18757)
* 👷 ci(share): add PR-time verify workflow for the share micro app
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* 👷 ci(share): build same-repo PR previews with the cloud overlay
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* 📝 docs(skill): record dual-repo share verify and skeleton layout parity
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* 👷 ci(share): keep overlay naming neutral in public workflow
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* 👷 ci(share): reuse the existing overlay token secret
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* ♻️ refactor: migrate lobe-cron to lobe-task (#18753)
* 💄 style(share): center skeleton hero to match hydrated layout (#18756)
Claude-Session: https://claude.ai/code/session_01TQcoZnDo12NGCxWZDJpciZ
* 💄 style: register dedicated skeletons for all lazy routes (#18752)
* 💄 style: add dedicated home route skeleton
* 💄 style: register route skeletons for all lazy routes
* ✅ test: align unit test mocks with base-ui component migration
* ✅ test: complete base-ui mock alignment for migrated components
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* 📌 chore: pin @lobehub/ui to 5.33.1 until base-ui migration completes
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* ♻️ refactor: finish ActionIcon, Avatar, Tag and Text base-ui migration
Completes #18729: migrates the 45 remaining call sites (plus type-only
imports) to @lobehub/ui/base-ui and drops the temporary 5.33.1 pin now
that the repo is clean under 5.33.3's expanded deprecation list.
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* ♻️ refactor: migrate direct antd Tag and Avatar imports to base-ui
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* ✅ test: stub base-ui ActionIcon in input banner queue case (#18760)
* ✅ test: stub base-ui ActionIcon in input banner queue case
The real base-ui ActionIcon requires the app-level MotionProvider, which
the unit env does not set up.
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* ✅ test: render real base-ui components via a global MotionProvider stub
Redirect @lobehub/ui's internal MotionProvider module to a static stub in
vitest config so base-ui components render in unit tests without the
app-level ConfigProvider, and add composable canonical base-ui stubs
(~base-ui-stubs) for tests that want simplified DOM.
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* ✅ test: unblock closed antd-style factories against migrated base-ui imports
Six antd-style mocks were closed factories missing createStaticStyles /
keyframes / createGlobalStyle that real base-ui style modules now need;
compose them over importOriginal. PluginTag and GroupItem retarget their
UI stubs to the base-ui import path the components migrated to.
Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ
* ✨ feat: support workspace document comments (#18750)
* ✨ feat: support workspace document comments
* ✨ feat: enhance document comments with mentions and attachments
* 🐛 fix: synchronize document comment activity
* 🐛 fix: guard document comment attachment uploads
* 🐛 fix: preserve comment synchronization metadata
* 🐛 fix: scope document comment API routes
* ✅ test: align base-ui component mocks
* ✅ test: complete base-ui mock migration
* ✅ test: clean up rebased component mock
* ♻️ refactor: align comment imports with base-ui
* ✅ test: align mocks with latest canary
* 🐛 fix(connect-agent): prevent device loading state flash (#18768)
* 🐛 fix(connector): canonicalize connector ownership (#18763)
* 🐛 fix(eval): rm eval hints in run topic titles (#18771)
* 🐛 fix(eval): isolate run topic titles
* 🐛 fix(eval): keep ordinal topic titles
* 🐛 fix(eval): retain run topic input
* 🐛 fix(eval): remove fallback case label
* 🐛 fix(agent): scope heterogeneous default names to shared workspaces (#18770)
* 👷 ci: upgrade pnpm setup action to v6 (#18777)
* 🐛 fix(queue): encode QStash deduplication IDs (#18778)
* 🐛 fix(agent): recover sub-agent bridge answer from isolation thread for hetero children (#18781)
* 🐛 fix(agent): recover the sub-agent bridge's answer from its isolation thread for hetero children
callAgent-invoked heterogeneous (CLI-driven) sub-agents backfill their
parent's placeholder tool message with "Sub-agent completed without a
textual answer." even when the CLI produced a real reply — always, in
production's queue mode (AGENT_RUNTIME_MODE=queue), not intermittently.
completeSubAgentBridge/completeGroupActionMember resolve content from
params.finalState, falling back to coordinator.loadAgentState(operationId)
(the standard runtime's Redis-backed step state) when finalState isn't
passed in-process. Both createSubAgentBridgeHook and
createGroupActionMemberBridgeHook's webhook config deliberately strip
finalState/lastAssistantContent from the QStash payload via `eventFields:
['operationId', 'reason', 'status']` (to keep the payload lean), so the
queue-mode callback always arrives with finalState undefined — and hetero
children never write into the coordinator's runtime state at all (confirmed
zero `saveAgentState` calls under services/heterogeneousAgent/), so the
fallback is structurally dead for them. heteroFinish already resolves the
real answer server-side (via topics.metadata.heteroCurrentMsgId ->
messageModel.findById) before dispatching, but that resolution never
reaches the callback.
Add resolveLastAssistantContentFromThread(threadId): queries the child's own
isolation thread directly (thread ids are globally unique, no agentId/topicId
needed to disambiguate) — the same source heteroFinish itself reads. Used as
a fallback in both completeSubAgentBridge and completeGroupActionMember
(isolated mode) when the existing finalState/coordinator resolution comes up
empty, before falling through to the "no textual answer" stub.
Two regression tests added (one per bridge method), reproducing the actual
hetero shape (loadAgentState resolves null, not a message-stripped state) —
verified to fail against the pre-fix code and pass with it. Full
AgentRuntimeService.test.ts suite (114 tests) still passes.
Fixes LOBE-13586
* 🐛 fix(agent): gate the thread content fallback on missing finalState
Address a P2 flagged by Codex review on
|
||
|
|
7da5da7052 |
💄 style: register dedicated skeletons for all lazy routes (#18752)
* 💄 style: add dedicated home route skeleton * 💄 style: register route skeletons for all lazy routes * ✅ test: align unit test mocks with base-ui component migration * ✅ test: complete base-ui mock alignment for migrated components Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ * 📌 chore: pin @lobehub/ui to 5.33.1 until base-ui migration completes Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ * ♻️ refactor: finish ActionIcon, Avatar, Tag and Text base-ui migration Completes #18729: migrates the 45 remaining call sites (plus type-only imports) to @lobehub/ui/base-ui and drops the temporary 5.33.1 pin now that the repo is clean under 5.33.3's expanded deprecation list. Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ * ♻️ refactor: migrate direct antd Tag and Avatar imports to base-ui Claude-Session: https://claude.ai/code/session_01UTDivfaszDuBjpPn7PV7gQ |
||
|
|
f32a4eb9c5 |
♻️ refactor: migrate ActionIcon, Avatar, Tag and Text to @lobehub/ui/base-ui (#18729)
The antd-based wrappers are deprecated since @lobehub/ui@5.33.0. This moves every call site to the base-ui subpath. Beyond the import path, the migration required: - ActionIcon now renders a native <button> instead of a div: dropped the hardcoded 'nativeButton: false' in ActionDropdown's trigger props (it overrode the library's own inference, so Base UI applied role/aria-disabled to a real button), and retyped the dictation control refs to HTMLButtonElement - Avatar is no longer antd's: replaced src/icon with avatar, dropped gap (no equivalent) - Tag is no longer antd's: dropped bordered={false}, which antd v6 already ignored whenever variant was set — and the wrapper always set it - prop types (ActionIconProps, AvatarProps) now come from the base-ui subpath, since the legacy ones carry antd props and a div ref - seven files that still imported Button from @lobehub/ui or antd are migrated too, since touching them surfaced the existing no-restricted-imports error: variant=filled -> type=fill, variant=outlined + color=default -> type=default Verified: type-check clean, 503 tests pass across ChatInput/NavPanel/User, and the migrated surfaces render with no Base UI warnings. |
||
|
|
e1bc7e1bd6 |
🐛 fix: harden Goal acceptance runtime (#18670)
* 🧪 test: bootstrap local device gateway * 🐛 fix: preserve goal acceptance contracts * ♻️ refactor: keep goal criteria domain neutral * 🐛 fix: collect hetero acceptance evidence server-side * 🐛 fix: detect hetero evidence by provider * 🐛 fix: require self-contained goal evidence * 📝 docs: bound long-running health probes * 🐛 fix: pin installable UI dependency * 🐛 fix: pin installable UI in desktop workspace * ⬆️ chore: unpin repaired UI dependency * 🐛 fix: charge stale work before recovery --------- Co-authored-by: Arvin Xu <arvin.x@lobehub.com> |
||
|
|
e41b6cb0de | ✨ feat(goal): gate work tasks with acceptance (#18623) | ||
|
|
2df9829a5e |
✨ feat(share): extract share pages into a standalone SSR micro app (#18496)
* ✨ feat(share): extract share pages into a standalone SSR micro app `/share/t/:id` and `/share/page/:id` were client-only routes inside the main SPA, so a shared link opened on a blank shell and link previews carried the generic app card. They now live in `apps/share`, built the same way as `apps/workbench`: a React Router framework-mode app that server-renders on a Cloudflare Worker for cloud, plus a plain SPA build that Next serves for self-hosted deployments. What the server renders is the shell, the hero and a complete meta set — title, description and the OG/Twitter card — resolved from the route loader through a per-request tRPC client. The conversation and page-viewer stacks stay on the client: `clientOnly()` gates them and, in the SSR environment, a resolver stub cuts every `*.client` module out of the worker graph entirely. Gating the render alone does not help, because the dynamic import still pulls the graph into the bundle. Keeping that worker deployable took three more cuts, each found by tracing the module graph rather than guessing: - `@/store/user` is stubbed for SSR — `GroupAvatar` reads the viewer profile, and the store drags the model catalog and every provider behind it (39MB→19MB) - `shiki` and `shiki/wasm` are stubbed for SSR — the `@lobehub/ui` barrel reads the bundled-language catalog at module scope, registering ~400 grammars for a highlighter the server never runs (19MB→7.6MB, 1.78MB gzip) - SSR i18n resolves only the namespaces the shell renders; the generic loader would register every locale x namespace pair as a worker chunk The conversation list keeps its `headerSlot` while it shows the skeleton. It returned early before, so mounting the list to fetch blanked a title the server had already rendered. `/share` is removed from the desktop and mobile routers and rewritten by the Next middleware to `/spa-share/<locale>/…`, mirroring how `/verify` reaches workbench. The cloud worker is published from lobehub-cloud, which builds this same app against its business overlay, so this repo ships no deploy workflow for it. * 🐛 fix(share): preload the error namespace so the boundary renders translated The root ErrorBoundary reaches for the `error` namespace, which SSR did not bundle, so a failed share page rendered `error.title error.desc error.retry` verbatim. Fetching it on demand does not fix that case: a chunk-load failure is exactly when the client can never load the dictionary to correct itself. * 🐛 fix(share): drop the dev template fetch that served the main SPA The handler asked the main Vite server for `/index.share.html`, which does not exist at the repo root. Vite answers that with the HTML fallback — 200, main SPA shell — so `/share/*` in `bun run dev` silently booted an app that no longer has those routes. Share is developed against its own Vite server, so the branch goes rather than the file being added. `spaDevShells.test.ts` guards both directions: every remaining dev template must exist at the root and point at its own app entry, and share must not reintroduce one. * 📝 docs(skill): record the cloud-overlay split in split-micro-app Share is the second micro app and the first whose rendering code lives in the lobehub-cloud business overlay, so the skill needed the ownership rules that decides which repo builds and deploys, and the Vite 8 tsconfig-nearest-importer landmine that makes an overlay build ship the open-source fallback with a green build. Also folds in what the extraction turned up: the full Docker chain, the dev shell that answers 200 with the wrong app, shiki and @lobehub/ui bundle cuts, the error namespace, hydration and virtualized-prefill rules, the asset prefix convention, and the prod promote gate. |
||
|
|
93b20ef2b6 |
✨ feat(terminal): clickable links, split panes and Ghostty command-key bindings (#18500)
✨ feat(terminal): clickable links, split panes and Ghostty ⌘ keybindings
Links: xterm 5.5 already parses OSC 8, so a linkHandler plus
@xterm/addon-web-links covers explicit hyperlinks and bare URLs alike, both
on a single click. The renderer gates on http(s) before handing the URL to
shell.openExternal, which does no checking of its own — terminal output is
attacker-reachable.
Split: a tab now holds a list of panes (one PTY session each) with relative
widths, rendered side by side with a draggable divider. Closing the last pane
of a tab closes the tab, so the existing collapse-on-empty flow still holds.
Keybindings: xterm bails on metaKey for arrow keys, leaving ⌘ to the embedder,
so ⌘←/→/⌫ sent nothing at all. Wired to Ghostty 1.3.1's defaults, and the
handled keys are cancelled so Chromium can't read ⌘← as history-back.
|
||
|
|
1001a58a22 |
✨ feat(workbench): React Router framework mode SSR on Cloudflare Workers (#18473)
* ♻️ refactor(spa): extract verify/acceptance into apps/workbench Move the Workbench SPA into apps/workbench and serve /verify and /acceptance from that runtime for every web UA. The main SPA no longer registers those page routes, so they drop out of its bundle. * 🔧 chore(docker): wire the workbench workspace into image builds Copy apps/workbench/package.json before pnpm i so Docker can resolve the extracted SPA. Keep generateSpaTemplates optional for Cloud, and require the HTML only when WORKBENCH_REQUIRED=1 (build:docker). * ♻️ refactor(workbench): hard-navigate only from markdown entity links Keep Link and useWorkspaceAwareNavigate in-router. Markdown verify, acceptance, and mobile agent-doc links document-navigate. Drop the __WORKBENCH__ Vite define; Electron still skips hard-nav for portals. * ♻️ refactor(verify): slot-inject origin conversation and decouple shared components * 🐛 fix(i18n): make vite glob loaders independent of the vite root * ✨ feat(workbench): React Router framework mode SSR on Cloudflare Workers * 🐛 fix(verify): render viewer-timezone timestamps only after hydration * 💚 ci(workbench): deploy with affected detection and PR build verification * 📝 docs(skills): micro-app split and gateway routing playbook * 🐛 fix(workbench): track app/lib swallowed by the root lib/ ignore rules * ✨ feat(workbench): signed-in acceptance list sidebar on detail pages * 🐛 fix(workbench): survive shared-origin gateway quirks in client navigation * 💄 style(workbench): surface error detail on the boundary page * 💄 style(workbench): adopt the shared ErrorCapture page for the root boundary * 💄 style(verify): drop the app-home back arrow from the acceptance list panel * 🔧 chore(workbench): persist only error logs in wrangler observability * 📝 docs(skills): record Docker vs Cloud hosting for micro apps OSS Docker still builds and serves the SPA in-image. Cloud Next must not build, rewrite, or CDN-upload it — gateway routes to the Worker. * 💄 style(workbench): pin the acceptance logo while the list panel slides * 🐛 fix(workbench): only show the acceptance sidebar when signed in * 🐛 fix(verify): drop namespace prefix on bound verify translations |
||
|
|
3c551a9dfc |
♻️ refactor(verify): unify acceptance criterion UI and remove lobe-delivery-checker (#18376)
* ♻️ refactor(verify): unify acceptance criterion UI and remove lobe-delivery-checker - Add shared CriterionList / CriterionRow / CriterionRequiredChip and a single CriterionEditor + openCriterionEditModal under src/features/Verify/CriterionList - Migrate the /goal intervention card, CreateGoalModal, TaskVerifyConfig, PendingAcceptanceCheckList, TaskAcceptance and GoalDetailPage to the shared row/editor, deleting the two duplicated criterion edit modals - Unify the required chip to a blue info tag labelled 必选 (verify:criterion.*) and drop the divergent 必要/必需/必选 key trio across chat/plugin namespaces - Delete the unused builtin-tool-lobe-delivery-checker package with its server runtime, registry entries, i18n keys and the orphaned createPlanFromCriteria Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 📝 docs(acceptance): note stale-cache fingerprint of a cross-wired browser session Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ♻️ refactor(verify): apply acceptance review feedback on criterion editing - Drop the pending-status icon and C{seq} anchor from goal-creation draft rows — those belong to the post-creation check list, not authoring - Remove TaskVerifyConfig's edit mode (SortableList reorder, side drawer, Done button): every row now edits itself through the shared criterion modal, matching the goal-creation interaction; regenerate moves into the header overflow and add/save-as-template become always-visible footer actions - Simplify the criterion edit modal title — no C{seq} interpolation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 📝 docs(acceptance): record ingest-side identifier/internal-id gap in probe patterns Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ♻️ refactor(verify): apply round-2 acceptance review feedback - Keep the C{seq} anchor on goal-creation draft rows; only the pending status circle stays out of authoring - Drop the at-rest delete button from task criterion rows — removal lives inside the editor modal - Move save-as-template from the footer into the header overflow menu - Show the verifier tag and required chip on goal-detail check rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ♻️ refactor(verify): fold task criterion row edit and delete into a per-row overflow Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |