mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
feat/cli-binary-release
11105 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d2f60c078c |
feat(cli): add binary release workflow and curl install script
- Add .github/workflows/release-cli.yml: builds standalone binaries via bun build --compile on ubuntu/macos runners and uploads to GitHub Release - Add apps/cli/install.sh: POSIX-compatible curl installer that detects OS/arch, installs to /usr/local/bin (or ~/.local/bin fallback), and creates lobe + lobehub symlinks pointing to lh |
||
|
|
96d19fe403 |
🐛 fix(skill): consolidate add-skill button into header dropdown
Move the standalone 'AddSkillButton' from SkillList sidebar into the header '+' dropdown, providing a unified entry point for all add-skill actions (import from URL/GitHub, upload zip, custom connector). Replace legacy 'Add Custom MCP' with the new Connector flow. |
||
|
|
5dd0f0c0c9 |
✨ feat: specialize Market auth modal copy per capability scene (#15569)
Introduce a MarketAuthScene ('default' | 'sandbox' | 'mcp' | 'publish') so the
Market authorization modal can show capability-specific copy instead of the
generic "Create Community Profile" wording, while falling back to the generic
copy for unknown scenes.
- Reactive (401) path: infer scene from the tRPC procedure path in the error
link and carry it on the market-unauthorized event.
- Proactive path: callers pass the scene to signIn() (publish buttons, MCP/skill
install, in-chat market tool auth).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
dfb70c1e87 |
🐛 fix(skills): inject pinned skill content into the system prompt (#15568)
* 🐛 fix(skills): inject pinned skill content into the system prompt Pinned skills (ids in agentConfig.plugins) were marked activated by SkillResolver but never carried their content, because resolveClientSkills dropped the `content` field when mapping store skills to metas. As a result SkillContextProvider's `s.activated && s.content` filter skipped them, so the agent had to call activateSkill to use a pinned skill instead of it being force-injected. - builtin skill content is already in the store: carry it through. - pinned DB skill content is fetched on demand (store cache first), only for pinned ids to avoid bulk network calls when auto mode exposes every skill; a failed fetch degrades gracefully to a content-less listing. - resolveClientSkills becomes async; contextEngineering awaits it. - add skillEngineering tests covering both paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(skills): mark pinned skills activated and fix test types The MessagesEngine path passes skillsConfig.enabledSkills straight to SkillContextProvider without running SkillResolver, so the metas must carry `activated` themselves — content alone is not enough (the provider only injects `s.activated && s.content`). Mark pinned skills activated in resolveClientSkills, guarded by content presence so a content-less pinned skill still falls back to the <available_skills> list instead of disappearing. Also widen the test helper's param type so `content`/`activated` are accessible (fixes TS2339 in CI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(skills): don't pre-activate ZIP-bundled pinned skills Server-side bundle mounting for execScript / readReference is keyed off stepContext.activatedSkills, which is populated only by the activateSkill tool call — operation-level pinning never seeds it. So pre-injecting the content of a ZIP-bundled DB skill would tell the model to run scripts from an unmounted bundle. Gate the content pre-injection on the absence of a zipFileHash: bundled skills stay in <available_skills> and are activated via the tool (which mounts the bundle), while pure-content skills (builtin Artifacts, bundle-free DB skills) are still force-injected when pinned. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7ad6e2aa25 |
🐛 fix(agent): make working-directory Clear actually clear legacy / default-sourced cwd (#15571)
* 🐛 fix(agent): make working-directory Clear actually clear legacy / default-sourced cwd The "Clear" action in the working-directory picker was a no-op whenever the shown directory came from a precedence level that clear() never touched: - clear() only removed the topic override and the agent's per-device choice (workingDirByDevice), but the button's visibility was gated on selectedDir, which also resolves from legacyAgentWorkingDirectory (pre-migration localStorage pick) and deviceDefaultCwd (device-wide default). When the cwd came from either, clear() deleted an already-empty higher level → nothing changed. Fixes: - useCommitWorkingDirectory: when clearing at the agent-default scope, also drop the legacy per-agent value (localStorage-only, no network round-trip). - WorkingDirectoryPicker: gate the Clear button on hasClearableSelection (topic / agent choice / legacy) instead of selectedDir, so it no longer renders as a dead button when the cwd comes solely from the device default (which isn't clearable from the agent picker). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(claude-code): slow token count-up animation to 2000ms Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3986223b25 |
🐛 fix(heterogeneous-agents): show real CLI model on remote-spawned Claude Code (#15572)
Remote/device-spawned CC runs persist via the server-side HeterogeneousPersistenceHandler (the executing device is not the viewing client), and the assistant placeholder was created with the agent's configured chat model/provider (e.g. deepseek-v4-pro). That value leaked into the model tag and was re-applied at terminal, so the model tag showed the wrong model instead of the real Claude Code model. - Create the hetero placeholder with `provider: heteroType` for ALL hetero agents (not just remote openclaw/hermes) and no model, mirroring the client path. The real model is reported by the CLI and backfilled. - Capture the CLI's authoritative model/provider from the first `stream_start` (CC system/init) and backfill the placeholder, so the real model lands from the first turn even without usage-bearing turn_metadata. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ea246d6e17 |
✨ feat(agent): list project skills over device RPC in the sidebar (#15566)
* ✨ feat(agent): list project skills over device RPC in the sidebar The right-sidebar 技能 (project skills) tab only read skills over local Electron IPC, so in device mode (working dir on a bound remote device, or the web client) the list was always empty — unlike the Files / Review tabs which already branch on `deviceId`. Add a `listProjectSkills` device RPC mirroring `getProjectFileIndex`: - types: `DeviceProjectSkillItem` / `DeviceListProjectSkillsResult` - `deviceGateway.listProjectSkills` via the generic `invokeRpc` relay - TRPC `device.listProjectSkills` + `GatewayConnectionCtr` dispatch to `WorkspaceCtr.listProjectSkills` - renderer chokepoint `projectSkillService` branches on `deviceId` - `useProjectSkills(dir, deviceId?)`; remote mode lists but doesn't open previews (parity with the Files tab) - thread `remoteDeviceId` through `SkillsGroup` No device-gateway repo change needed — the RPC relay is method-agnostic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): list project skills over device RPC for homogeneous agents too Thread `deviceId` through the homogeneous resources path (`AgentDocumentsGroup` → `ProjectLevelSkills`) so a device-bound homogeneous agent's 技能 tab populates over RPC, matching the heterogeneous `SkillsGroup`. `useProjectSkills` already accepts `deviceId`; this just wires it in and OR-s `deviceId` into the `showProjectSkills` gate. (The large AgentDocumentsGroup diff is prettier re-indentation from wrapping the outer memo() once the param list crossed the print width.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent): resolve per-device cwd in ResourcesSection so device-mode skills load ResourcesSection computed its working directory with the legacy `topicCwd || agentCwd` selector, which misses `workingDirByDevice[deviceId]` and `device.defaultCwd`. For a device-bound agent the cwd lives in that per-device map, so it resolved to `undefined` — the project-skills SWR key was null and the fetch never fired even though `deviceId` was set (the 技能 tab showed "暂无可用技能"). Switch to `useEffectiveWorkingDirectory`, the same resolver the runtime bar / WorkingSidebar use. Fixes both the hetero SkillsGroup and the homogeneous AgentDocumentsGroup paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(agent): show loading state for project skills while switching path On a working-directory switch the project-skills SWR key changes, so items go empty while the new scan is in flight. The homogeneous skills panel was flashing the empty placeholder instead of a loader. Surface `useProjectSkills().isLoading` and render NeuralNetworkLoading when project skills are the only source and still loading. (The hetero SkillsGroup already shows it via SkillSection's isLoading.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f5458e1ad9 |
♻️ chore: replace LOBE-XXX markers with inline migration context (#15567)
♻️ chore: replace LOBE-XXX markers with inline migration context in 0110 SQL
|
||
|
|
251e2ede5e |
✨ feat(sandbox): sync user-uploaded files into the cloud sandbox (#15550)
* ✨ feat(sandbox): sync user-uploaded files into the cloud sandbox Pre-load the files a user attached in a conversation (topic message files + session files) into the cloud sandbox the first time it is used, and tell the agent they are available. - FileModel.findFilesToInitInSandbox: merge messages_files (by topic) and files_to_sessions (by the topic's session), de-duped by file id - SandboxMiddlewareService.ensureFilesInitialized: on first tool call, presign download URLs and run an idempotent curl bootstrap into /mnt/data; guarded by an in-sandbox marker and a short-lived Redis hint, best-effort so it never blocks the actual tool call (caps: 50 files / 100MB / 120s) - Agent awareness via {{sandbox_uploaded_files}} in the cloud-sandbox systemRole, populated by both the server (RuntimeExecutors) and client (contextEngineering) placeholder generators Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(sandbox): make file sync work on all server runtimes & keep prompt consistent Address review feedback on the uploaded-files sync: 1. (high) The sync was a no-op on the cloudSandbox server runtime and the skills runtime because createSandboxService() was called without serverDB, so ensureFilesInitialized() returned early. Thread serverDB through both. (heterogeneous sandboxRunner is intentionally left out: it runs a coding agent in /workspace and does not use the cloud-sandbox systemRole.) 2. (medium) Drop the Redis "already initialized" hint. The in-sandbox marker is now the single source of truth for idempotency, so a recycled sandbox always re-syncs instead of being skipped by a stale 5-min Redis key. 3. (medium) Apply the 50-file / 100MB caps inside formatUploadedFilesPrompt (via the shared selectSandboxInitFiles), so the files the prompt advertises match exactly what the bootstrap downloads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
337e7f244c |
🐛 fix(market-auth): skip auth flow when LobeChat session is missing (#15532)
Guard `signIn()` and the market.* 401 handlers on `isSignedIn` so the Create Community Profile modal no longer pops up for unauthenticated users. Routing the user back to LobeChat sign-in is not MarketAuth's responsibility — callers handle that. |
||
|
|
eae47f527c |
✨ feat(markdown): render GitHub / Linear / external links as rich chips (#15561)
* ✨ feat(heterogeneous-agents): default Codex exec to bypass approvals/sandbox Switch the default Codex execution mode from --full-auto to --dangerously-bypass-approvals-and-sandbox, and share the execution-mode constants from @lobechat/heterogeneous-agents/spawn so the desktop driver and spawnAgent stay in sync. An explicit execution flag in extraArgs still wins. Also fix the Codex adapter step tracking so consecutive agent_message items stay in one step, stale tool completions don't start a new step, and turn completion drains pending tools before emitting stream_end + agent_runtime_end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(shared-tool-ui): unwrap shell-wrapper commands in RunCommand UI Codex execs commands wrapped as `/bin/zsh -lc '...'`; surface the inner command in the RunCommand inspector and render. Also switch Unix glob fallback from `find` to `fast-glob` to preserve globstar semantics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(markdown): render GitHub / Linear / external links as rich chips Add a markdown Link plugin that rewrites anchor elements into rich inline chips: GitHub repo/PR/issue/commit/user, Linear issues, npm packages, Figma files, mailto, and any other external link (favicon + full URL). Citation, footnote, anchor and relative links keep the default renderer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ⬆️ chore(deps): bump @lobehub/editor to 4.17.0 and @lobehub/ui to 5.15.10 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dfdf844761 |
🐛 fix(desktop): bump node-gyp to 12.x so Windows build finds Visual Studio 2026 (#15562)
GitHub redirects the `windows-2025` runner to the new `windows-2025-vs2026` image, which ships Visual Studio 2026. node-gyp 11.5.0 only recognizes VS 2019/2022, so `electron-builder install-app-deps` fails to rebuild the native `get-windows` module with "Could not find any Visual Studio installation". node-gyp 12.x adds VS 2026 detection. Override it in both the root workspace and the isolated apps/desktop install. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cca01451f9 |
✨ feat(heterogeneous-agents): default Codex exec to bypass approvals/sandbox (#15557)
* ✨ feat(heterogeneous-agents): default Codex exec to bypass approvals/sandbox Switch the default Codex execution mode from --full-auto to --dangerously-bypass-approvals-and-sandbox, and share the execution-mode constants from @lobechat/heterogeneous-agents/spawn so the desktop driver and spawnAgent stay in sync. An explicit execution flag in extraArgs still wins. Also fix the Codex adapter step tracking so consecutive agent_message items stay in one step, stale tool completions don't start a new step, and turn completion drains pending tools before emitting stream_end + agent_runtime_end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(shared-tool-ui): unwrap shell-wrapper commands in RunCommand UI Codex execs commands wrapped as `/bin/zsh -lc '...'`; surface the inner command in the RunCommand inspector and render. Also switch Unix glob fallback from `find` to `fast-glob` to preserve globstar semantics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d2cd9ef023 |
✨ feat(page-editor): enable block plugin with shared inline padding (#15556)
* ✨ feat(page-editor): enable block plugin with shared inline padding Mount `ReactBlockPlugin` on the page editor with `anchorPadding={0}` so the editor root no longer reserves its default 54 px gutters, and apply `DEFAULT_BLOCK_ANCHOR_PADDING` as `paddingInline` on the `Flexbox` wrapping `TitleSection` + `EditorCanvas`. This keeps the title and editor content aligned while leaving the same 54 px of room for the floating block menu / drag handle to render in. Requires `@lobehub/editor` with `anchorPadding` support and the exported `DEFAULT_BLOCK_ANCHOR_PADDING` constant. * 🐛 fix(page-editor): drop redundant overflowY on editor content wrapper `editorContent` previously declared `overflowY: 'auto'`, which created a second scroll container nested inside `.contentWrapper` (already `overflowY: 'auto'`). With the new inline padding from `DEFAULT_BLOCK_ANCHOR_PADDING`, the nested scroller clipped the floating block menu / drag handle that the editor renders in the inline-padding gutter. Let the outer wrapper own scrolling so the gutter overflow stays visible. |
||
|
|
ea3ae583d6 |
✨ feat(agent): unified per-device working directory + execution-device UI (#15543)
* ✨ feat(agent): unified per-device working directory + execution-device UI Client UI consuming the backend contract (#15542). User-facing — validate before merge. - New `src/store/device` (SWR fetch + cwd writes) — single source of device data; `deviceCwd` helper moves here from the chat-input feature layer. - One `WorkingDirectoryPicker` for local + remote (native dialog vs manual path). - Shared `WorkspaceControls` strip composed by both chat-input bars. - GitStatus reads remote git via `useDeviceGitInfo` (read-only). - Execution-device switcher graduates out of labs → writes only executionTarget. - One-time migration of legacy localStorage recents into device.workingDirs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): wire executionTarget→runtimeMode + workingDirByDevice cwd The runtime-decision wiring, kept out of the backend contract PR so it's reviewed/validated together with the UI that drives it. - `helpers/executionTarget`: resolveRuntimeMode / executionTarget resolvers. - server tool gate (AgentToolsEngine) derives runtimeMode from `agencyConfig.executionTarget`, with a no-regression fallback to the legacy per-platform runtimeMode. - server cwd precedence (aiAgent resolveWorkspaceInit + hetero dispatch) now consumes `workingDirByDevice[targetDeviceId]`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ test(agent): cover executionTarget + workingDir helpers; drop dead lab key - Unit-test resolveRuntimeMode / resolveExecutionTarget and the working-dir precedence (locks the web default→cloud graduation + legacy fallback) - Remove the now-unused `executionDeviceSwitcher` lab i18n keys (toggle deleted) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): guide web users to the desktop app in the device switcher On web with no remote device, replace the muted "no devices" dead-end with a prominent, clickable download-desktop card (and drop the now-duplicate header link). Desktop keeps the muted hint since local execution is already available. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): fix execution-device copy for desktop + web - Desktop "no devices" hint no longer tells an already-on-desktop user to "install the desktop app" — just points at `lh connect`. - Tighten the web download-card description to the desktop's real benefit (run on your computer with local file access). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): flatten the web download card to a plain row Drop the outer border/background so it reads as a normal menu row (like the sandbox option), and shorten the description to a single line so the row stops being taller than its neighbours. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): reword download-card desc to "access to your computer" Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): add "no device" execution target (plain chat, no run tools) Restores the option to run an agent with no execution environment, lost when the per-platform runtimeMode was unified into executionTarget. Adds `none` to HeteroExecutionTarget (→ runtimeMode `none`), surfaces it at the top of the switcher on both web + desktop, and flips the web default back to `none` so an unconfigured web agent is plain chat again (desktop still defaults to local). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): rename HeteroExecutionTarget→DeviceExecutionTarget, reorder switcher - Rename the type (it now carries `none`, so "device" target fits better than "hetero") across types + helpers + dispatcher + switcher. - Move "no device" to the bottom of the list (real targets first, opt-out last). - Reword the download card to "let agents connect directly to your computer". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): move "no device" back to top, restore EN download copy "No device" sits above the dynamic device rows; keep the EN download-card wording as "Run agents with access to your computer". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): swap switcher icons — MonitorOff for "no device", Box for sandbox Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): clarify execution-device info tooltip + "no device" desc - Info tooltip now explains the cloud sandbox is provided by the centralized LobeHub Marketplace, and that picking a device makes it the agent's runtime for reading/writing files and operating the computer. - "No device" description now conveys "no device enabled, can't operate a computer" instead of "plain chat". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): move info icon beside the title, shorten "no device" desc - Info tooltip trigger now sits next to the "Execution Device" title instead of right-aligned; the download link stays on the right. - "No device" description trimmed to just "No device enabled". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): zh tooltip wording — "提供服务" Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): reorder tooltip — device runtime first, marketplace last Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): trim tooltip — drop "设备"/devices and trailing period Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): tag the current machine's device row, drop duplicate "This device" When the desktop's own machine appears in the device list, badge that real row with a "This device" tag and hide the generic "This device" (local) option — no more two entries for the same machine. The local option still shows as a fallback when the machine isn't enrolled in the list yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(agent): hoist this-machine device above sandbox + auto-bind on first run Switcher-only (no routing/dispatch changes): - Order is now: no device → this device → cloud sandbox → other devices. - On desktop, when this machine is enrolled and online and the agent has no explicit target yet, default to it and persist the binding once. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): widen gap between execution-device rows Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): hide "Get Desktop App" link on desktop Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): capitalize "Cloud Sandbox" label Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(agent): web working-dir entry via "Add folder" modal instead of inline input The browser folder picker can't yield an absolute path (sandboxed handle), so on web / a remote device the working directory is entered manually. Replace the inline input with an "Add folder…" row that opens a modal for absolute-path entry; the local desktop machine still opens the native folder dialog. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(agent): split working-dir footer into local/remote row components Replace the scattered `isLocalDevice ?` forks (icon, label, handler) with one branch that picks between two self-contained rows: ChooseLocalFolderRow (native dialog) and AddRemoteFolderRow (absolute-path modal). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): use the device default cwd as the add-folder placeholder Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): validate manually-entered working dir via device statPath RPC Web / remote clients can't browse the target device's filesystem, so the "Add folder" modal now checks the typed path on the device before binding it. New `statPath` device RPC mirrors gitInfo end-to-end: - desktop WorkspaceCtr.statPath (fs.stat → exists / isDirectory) + RPC dispatch - server deviceGateway.statPath + device.statPath tRPC (invokeRpc relay) - modal blocks on a definitive negative (not found / not a directory); an unreachable device is treated as "can't verify" and allowed through Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(agent): route statPath through deviceService, not lambdaClient Components shouldn't import lambdaClient directly — add a thin deviceService wrapping device.statPath, and call it from the working-dir picker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(i18n): move working-directory strings from plugin to a device ns The working-directory / git control-bar strings (53 keys) were lumped under the `plugin` namespace. Move them to a dedicated `device` namespace and drop the now-redundant `localSystem.` prefix (`plugin:localSystem.workingDirectory.X` → `device:workingDirectory.X`). Updates the 4 consumer components; the `device` ns auto-registers via defaultResources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(agent): route all device TRPC calls through deviceService Components/hooks/stores shouldn't reach into lambdaClient.device.* directly. Expand deviceService with listDevices/updateDevice/listGitBranches/ checkoutGitBranch/checkCapability/getAgentProfile and migrate every imperative call site (device store, BranchSwitcher, CreatePlatformAgent, the remote-agent guard, RemoteAgentConfigCard) + the DeviceListItem type. lambdaQuery.device.* React-Query hooks are left as-is (a different pattern). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): pull/push a remote device's branch over RPC Wire git pull/push through the device's pullGitBranch/pushGitBranch RPC so the web/remote GitStatus bar can sync, not just the local desktop over IPC. Shows the pull/push affordances for remote devices too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(agent): route git pull/push through deviceService too Add pullGitBranch/pushGitBranch to deviceService and switch GitStatus off the direct lambdaClient.device.* calls, so no component reaches the device router directly anymore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent): detect repoType for manually-added working dirs A directory added via the "Add folder" modal committed without a repoType, so a GitHub repo showed a plain folder icon. statPath now also returns the git repo type (detected on the target device); the modal threads it into the committed entry. Collapses the modal's separate validate+submit into one onSubmit that validates and enriches in a single round-trip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(agent): create new branch via a modal instead of inline footer "Checkout new branch…" now opens a focused modal (branch-name input + create) rather than expanding an inline footer inside the branch dropdown. Always creates + checks out the branch — no checkout/overwrite options. Errors show inline in the modal; drops the dead inline-create state/styles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(agent): route all git ops through a unified gitService Pick Electron IPC vs device RPC inside the service so UI / store / hooks stay transport-agnostic. Replace the bundled `gitInfo` device RPC with granular reads (branch / linked PR / working-tree / ahead-behind) that mirror the local IPC methods one-to-one, and move the git read SWR hooks into the device store (useFetchGitInfo / WorkingTreeStatus / AheadBehind). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): route Review git ops through device RPC (remote-capable) Extend the device-RPC git pipeline to the 4 ops the Review panel needs (getGitWorkingTreePatches / getGitBranchDiff / listGitRemoteBranches / revertGitFile), mirroring the listGitBranches pattern end-to-end: desktop RPC dispatch → deviceGateway → device.* tRPC → gitService. Adds minimal DeviceGit* mirror types to @lobechat/types. Review (useReviewPatches / useGitRemoteBranches / FileItem) now goes through gitService with a deviceId, dropping the isDesktop gate so web/remote devices get the diff + revert too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent): resolve repoType from device store so remote Review tab shows useRepoType now reads the persisted workingDirs[].repoType from the device store (keyed by deviceId), so a remote device's git/github type — and thus the Review tab visibility — resolves without a local-only IPC probe. The IPC probe + localStorage fallback are kept only when the target is the local machine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(agent): optimistic branch switch in the branch switcher Flip the displayed branch the instant a checkout is clicked (or a new branch created) instead of waiting for the IPC/RPC round-trip + gitInfo refetch. The git-info SWR cache is optimistically updated and reconciled on completion — a failed checkout rolls the label back and toasts the error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat: support remote device files panel * 💄 style: restore desktop this-device option * 🐛 fix: keep files panel local for this device --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a75eba5a4f |
💄 style(chat-input): use compact stats footer for skill tools popover (#15552)
* 💄 style(chat-input): use compact stats footer for skill tools popover - Replace the two full-width footer rows (store / management) with a compact stats footer: pinned / auto counts on the left, an "Add Skills / Connector" store button (icon + label) and a settings icon button on the right. - Right-align each item's type tag (MCP / Skills / builtin) so badges sit flush next to the row action instead of trailing the name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ test(aiAgent): mock deviceGateway in connectorOverlap exec test execAgent reads `deviceGateway.isConfigured`, which under the happy-dom test environment hits real t3-env and throws "server-side env var on the client". Mock `@/server/services/deviceGateway` like the sibling device tests do so the connector/plugin overlap cases run in isolation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
9eff025787 |
💄 style(modal): use base-ui Button in custom modal footers (#15444)
💄 style(modal): use @lobehub/ui/base-ui Button in modal footers
Align custom-footer button padding/font with base-ui Modal's built-in
OkBtn/CancelBtn (32h / 14 / 13) for consistent visual rhythm. Affects
AuthRequiredModal footer and TaskTemplateDetailModal content button.
|
||
|
|
9b19ebb2c6 |
🐛 fix(desktop): unbreak dev cold-start + restore UI language across reloads (#15547)
* 🐛 fix(desktop): unbreak dev cold-start on non-default UI languages `ViteRendererFallback` now proxies via globalThis `fetch` (Node undici) instead of Electron `net.fetch`, and Vite dev server is pinned to IPv4 listen. The main-process Chromium `net` pool is small and surfaces `ERR_INSUFFICIENT_RESOURCES` under cold-start module bursts + ~50 i18n namespace fan-out under non-en-US locales. undici queues internally and avoids that pool entirely; v4 listen avoids happy-eyeballs dual-stack connect storms. A Semaphore(64) still caps in-flight fetches so the OS socket layer never gets buried. Fixes LOBE-10086 * 🐛 fix(desktop): restore persisted UI language across renderer reloads The renderer's `<html lang>` was being computed from `?lng=` (injected by the main process at `loadURL` time) with `navigator.language` as fallback. On `Cmd+R` the webContents reload reuses the prior URL without rebuilding it against `storeManager.locale`, so users who changed their language after launch got dropped back to the OS locale on every reload (white screen, then English). Read the i18next localStorage cache first — that's the actual persisted user setting written by the language switcher — and fall back to the URL param + navigator as before. * ✅ test: mock device gateway in connector overlap spec |
||
|
|
a2fd98a2d1 | 🐛 fix: restore file URLs in context prompts (#15549) v0.0.0-nightly.pr15547.15472 v0.0.0-nightly.pr15547.15511 | ||
|
|
235a16fc11 |
✨ chore(agent): agencyConfig contract + git-over-RPC backend (#15542)
* ✨ feat(agent): agencyConfig contract — workingDirByDevice + executionTarget Type-only contract for the unified per-device working-directory work. Adds `workingDirByDevice` (per-device cwd) and `executionTarget` to agencyConfig. No runtime logic consumes them yet — the server/client wiring lands in the UI PR so it can be validated as one unit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): device gitInfo over RPC + shared local-file-shell git impl Backend/RPC capability for "git branch / changes / PR for remote devices". Dormant — no client caller yet; merging changes no existing behavior. - `@lobechat/local-file-shell/git`: repoType + branch / linked-PR / working-tree / ahead-behind + `gitInfo` aggregate + `DeviceGitInfo` type (desktop + CLI). - desktop `GitCtr.gitInfo()` (@IpcMethod) delegates to it; registered in GatewayConnectionCtr's RPC dispatch. `utils/git` re-exports the helpers. - server: `deviceGateway.gitInfo()` wrapper + `device.gitInfo` TRPC query. - `@lobechat/types`: `DeviceGitInfo` shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ test(desktop): fix stale mocks after git impl moved to local-file-shell Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(server): extract DeviceGateway into its own service dir deviceGateway is a device-scoped gateway client (status/list/tool-call/git/ workspace RPC), not tool-execution-specific. Move it out of toolExecution/ into its own services/deviceGateway/ and update all import sites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ee65cf2a0f |
✨ feat(connector): custom OAuth MCP connectors — onboarding, runtime execution & connector-first (LOBE-9983) (#15546)
* ✨ feat(connector): wire custom MCP OAuth — Pre-registration & DCR (LOBE-9983) Connect the two OIDC schemes designed in LOBE-9736 (oidcConfig) end-to-end so users can add a custom OAuth MCP server from /settings/skill. Until now the DB schema, models, and tool-permission UI existed, but nothing ran the OAuth authorization flow — syncTools only worked when a token already existed. Flow (shared pipeline, branches only on where client_id comes from): - Add modal (client_id present → Pre-registration; absent → DCR/RFC 7591) - startOAuth: probe MCP URL → RFC 9728 protected-resource metadata → RFC 8414 AS metadata; DCR-register the client when no client_id; persist resolved oidcConfig; build PKCE authorize URL, stash verifier in Redis keyed by state - /oauth/connector/callback: consume state → exchange code → store encrypted tokens (KeyVaultsGateKeeper) + tokenExpiresAt + status=connected → postMessage - syncTools lazily refreshes the access token before connecting Built on @modelcontextprotocol/sdk OAuth helpers (discover/register/start/ exchange/refresh) — no hand-rolled protocol code. Security: - Wire KeyVaultsGateKeeper into ConnectorModel so OAuth tokens are encrypted at rest (previously the router passed no gatekeeper → plaintext) - Strip decrypted credentials and oidcConfig.clientSecret from the list response UI: - "+" button in /settings/skill Connectors tab opens the Add modal - SkillList surfaces custom connectors from the connector store - Modal wires the client secret field, infers the scheme, and shows the redirect URI to register Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(connector): request server-advertised scopes in OAuth flow The authorize request sent an empty scope list, so providers that require a scope (e.g. Linear MCP advertises scopes_supported ["read","write"]) issued a useless token or rejected the flow. Default to the authorization server's advertised scopes_supported when the user did not specify any, and use them for both DCR registration and the authorize request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(connector): let OAuth callback bypass SPA rewrite and auth gate /oauth/connector/callback is a backend route handler reached via a cross-site redirect from the OAuth provider, so the proxy middleware broke it two ways: 1. It was not in the backend passthrough list, so it got rewritten to the SPA / locale shell instead of running the route handler (307 → blank). 2. It was not in isPublicRoute, so BetterAuth treated it as protected; the cross-site top-level navigation doesn't reliably carry the SameSite session cookie, so it redirected to sign-in (307). Add /oauth/connector to backendApiEndpoints and /oauth/connector/callback to isPublicRoute (the handler validates its own single-use state, so it must not be session-gated). Scoped so /oauth/callback/success|error SPA pages are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(connector): execute connector tools server-side + agent-runtime wiring Make custom OAuth MCP connectors actually callable, and sync their tools as soon as authorization completes. - callback: after token exchange, sync the tool list server-side via a shared syncConnectorToolsById — the connector is usable without a client round-trip - sync.ts: extract buildConnectorMcpParams (http+auth / stdio), shared by syncTools and the new callTool - connector router: add `callTool` (resolve connector, hard-block disabled tools, refresh token, call the remote MCP with decrypted credentials) - aiAgent runtime: pass a KeyVaultsGateKeeper when resolving connectors so OAuth tokens decrypt (otherwise tool calls 401); surface connectors in the agent-management availablePlugins as a new 'connector' type - AgentManagementContextInjector: render a <connector_plugins> section Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(connector): wire connectors into the classic client chat path The front-end chat orchestrates tools client-side (via /webapi/chat proxy), separate from the server agent runtime. Connectors were invisible and unexecutable there. Wire them in, connector-first. - toolEngineering: build connector manifests from the store and inject them into createToolsEngine; drop plugins sharing a connector identifier (connector wins) - buildClientConnectorManifests: store rows → type 'mcp' manifests (no token; the client has none) with permission → humanIntervention mapping - mcpService.invokeMcpToolCall: route connector tool calls to connector.callTool before the plugin path (only connectors with a real MCP endpoint, so Lobehub/Klavis skills keep their executor) - DeferredStoreInitialization: fetch connectors post-login so chat sees them - AddConnectorModal: refresh after OAuth regardless of popup outcome - chat-input skills picker: surface custom connectors in the auto group Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(connector): open OAuth popup synchronously + escape callback HTML (codex P1) - AddConnectorModal: open the OAuth popup synchronously inside the click handler (before any await), then navigate it to the authorize URL. Browsers block window.open once an async boundary is crossed, which left popup=null and the poll loop never resolving — the Add modal hung. Null popup now fails fast with a "allow popups" message. - callback route: escape the postMessage payload for `<script>` context (`<`, `>`, `&`, U+2028/U+2029 → \uXXXX). A malicious OAuth server could put `</script>...` in the error param and execute script on the app origin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(connector): tighten execution boundary + surface OAuth failures + tests Address review: enforce the same constraints at the call site that the manifest layer enforces, and stop swallowing OAuth failures. - isEnabled on BOTH sides: invokeMcpToolCall only routes enabled connectors (a disabled connector no longer steals a same-name plugin's call), and the server rejects calls to a disabled connector. Matches buildClientConnectorManifests which only exposes enabled connectors. - callTool requires the toolName to exist in the synced user_connector_tools list — unsynced / hand-crafted tool names are rejected instead of being forwarded blindly to the remote MCP. - extract callConnectorToolById (typed ConnectorToolCallError → tRPC codes) so the gates are unit-testable. - AddConnectorModal: distinguish success / provider-error (show the reason) / user-dismissed instead of collapsing every failure into a silent close. - tests: exec gates (not-found / disabled connector / unknown tool / disabled tool / success / token-refresh) + buildClientConnectorManifests mapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(connector): align redirect URI, connector-override & partial-failure UX Second review round. - redirect URI: the modal showed a client-origin URI while the server sent an APP_URL one — register-vs-use mismatch broke the callback. Add a `connector.getRedirectUri` query (server source of truth) and show exactly that in the modal. - execAgent: derive the plugin-override set from the connectors that ACTUALLY produce a manifest (enabled + with tools), not the raw endpoint-having set — a disabled / not-yet-synced same-named connector no longer evicts the plugin and leaves the runtime with no tools. Matches the client-chat behaviour. - partial failure: when code exchange succeeds but the tool sync fails, the callback now reports `synced: false`; the modal shows "authorized but tools could not be synced" instead of a false "connected". Tests: execAgent overlap regression (disabled / 0-tool keeps the plugin; real tools replace it) + callback partial-failure (synced:false on sync error). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ♻️ refactor(connector): name the availablePlugins source 'custom' not 'connector' The agent-management availablePlugins types describe a tool's SOURCE (builtin / klavis / lobehub-skill); 'connector' named the storage system instead. Once plugins migrate to the connector table everything is a connector, so the source-based label is what matters. Rename to 'custom' to align with ConnectorSourceType.custom (single source of truth); section is <custom_plugins>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(connector): enforce connector permissions for community MCP plugins Community MCPs execute via the plugin path (not connector.callTool), so the per-tool permissions a user sets in the new Connectors UI weren't surfaced: needs_approval didn't trigger the approval prompt on either runtime. (disabled was already hard-blocked at execution by ToolExecutionService and the mcp router.) - extract patchManifestWithPermissions into a pure, client-safe module (patchManifestPermissions.ts); connectorPermissionCheck.ts re-exports it. - execAgent: also patch community-plugin manifests (pluginsWithoutConnectors) with their connector permissions, alongside lobehub/klavis. - client createToolsEngine: patch community-plugin manifests with connector permissions from the store so needs_approval surfaces as humanIntervention in the classic chat path too. - unit tests for the shared patch function. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✅ fix(connector): tolerate uninitialized connectors slice in selectors createToolsEngine now reads connectorSelectors.{customConnectors,connectorList}; toolEngineering/index.test.ts mocks getToolStoreState without `connectors`, so the selectors hit `undefined.filter`. Guard with `?? []` (the real store always seeds connectors:[] via initialState) and add connectors:[] to the test mock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✅ fix(connector): guard every connector selector against an uninitialized slice mcp.test.ts mocks the tool store without `connectors`, and invokeMcpToolCall calls connectorByIdentifier → `s.connectors.find` threw. The previous fix only guarded connectorList/customConnectors; harden all of them (find/filter) so any partial-store mock is safe. The real store always seeds connectors:[]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0ac53b4e80 |
🐛 fix(agent-runtime): capture Gemini multimodal content_part/reasoning_part output (#15535)
Gemini 2.5+/3 thinking streams deliver assistant text and reasoning as content_part/reasoning_part events instead of plain text/reasoning. The runtime registered no onContentPart/onReasoningPart handlers, so the text was silently dropped: onCompletion still reported usage tokens, the empty-completion guard saw outputTokens > 0, and the turn finalized to a blank `done` (lost in DB, client stream and trace alike). Add the two handlers, mirroring onText/onThinking for text parts so streaming, persistence and tracing all capture the content. Image parts are uploaded to object storage and serialized as multimodal content (text + image URLs, in order) — never persisting raw base64. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
91588bfdf8 |
📝 docs: add June 8 weekly changelog (#15537)
* 📝 docs: add June 8 weekly changelog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 📝 docs: add June 8 changelog cover and register index entry Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
927a79c3fb |
✨ feat(auth): preserve utm_source through the OIDC sign-in/sign-up flow (#15544)
When Market kicks off OIDC against LobeHub, unauthenticated users are redirected by the auth middleware to /signin (and onward to /signup). The utm_source param sent on the original /oidc/auth request was only buried inside callbackUrl and never surfaced on the sign-up page. Carry utm_source as a first-class query param through the auth detour, mirroring how the `hl` locale param is already preserved: - middleware lifts utm_source from the request onto the /signin URL - sign-in forwards utm_source to /signup in both navigation paths Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>v0.0.0-nightly.pr15541.15409 |
||
|
|
c5c047e4b5 |
🐛 fix(desktop): misc independent fixes (vite fetch cap, gateway loading, token animation) (#15541)
* 🐛 fix(desktop): bound concurrent Vite dev-server fetches Since #15304 unified dev under app://, every renderer asset round-trips through the main-process net stack. A cold start (thousands of module requests) or a non-default UI language (~50 i18n namespaces over HTTP at once) could exhaust the net request pool and surface as ERR_INSUFFICIENT_RESOURCES. Gate Vite dev-server fetches behind a FIFO semaphore (cap 64), holding each slot until the response body is fully drained so streaming responses count for their whole lifetime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(desktop): add trailing inset to tab title Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix: eliminate blank loading state during Gateway/ServerRuntime execution When sending a message in Gateway (ServerRuntime) mode, the UI showed a blank state between 'Sending message' and 'Task is running in server' because the new execServerAgentRuntime operation was associated with the server-created message ID, while the UI was still rendering the temp message ID. The temp ID had no running operation, so ContentLoading returned null. Fix: pass temp message IDs to executeGatewayAgent and associate them with the gateway operation alongside the server message ID. This ensures ContentLoading finds a running operation regardless of which message ID the UI is currently rendering. * ✨ feat(agent): animate subagent token count with count-up effect Promote a shared AnimatedNumber into @lobechat/shared-tool-ui/components and use it for the subagent metrics token total so it rolls up smoothly while streaming instead of jumping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
419aca2c59 |
🐛 fix(skill): stop OAuth connectors duplicating into the Skills tab (#15510)
The unified /settings/skill manager renders the Connectors and Skills sub-tabs from one SkillList via viewMode. Lobehub/Klavis OAuth connectors (type 'lobehub' | 'klavis') belong only in the Connectors view, but the Skills view's "Community Skill" section still mapped them alongside the market agent skills — so Gmail, Notion, Google Drive, etc. showed up in both tabs. Render only market agent skills in the Skills view; OAuth connectors stay exclusively under the Connectors view's "OAuth Connectors" group. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f0f8ecd64d |
🧹 chore: clean LOBE marker comments from aiInfra schema (2026-06-08) (#15536)
🧹 chore: replace LOBE-10056 markers with inline context in aiInfra schema comments
|
||
|
|
b19008ed24 | 💄 style: bring various details for better experience (#15486) | ||
|
|
dbf743cc12 |
✨ feat(verify): Agent Run delivery checker system (#15489)
* 🗃️ feat(database): add verify system tables for agent run delivery checker Implement the database layer for the Agent Run delivery checker (Verify System). Reuse / definition layer: - verify_criteria: a single reusable pass/fail standard (atomic unit), carrying its verifier config + onFail default and bound to a document for judging guidance (iteration history reuses document_history; no version columns) - verify_rubrics: a named group that aggregates criteria — the reusable unit - verify_rubric_criteria: junction, which criteria a rubric aggregates (criteria are reusable across rubrics) Mounted onto an agent via the existing agency config jsonb: - agencyConfig.verifyRubricId: a reusable rubric (criteria template) - agencyConfig.verifyCriteriaIds: ad-hoc one-off criteria A run's plan instantiates the union of both. No dedicated bindings table. Snapshot + result layer: - agent_operations.verify_plan (jsonb) + verify_plan_confirmed_at: the per-run immutable check-item snapshot lives ON the operation (1:1 — auto-repair spawns a new operation), instead of a separate plans table - agent_operations.verify_status: denormalized rollup for list-page badges - verify_check_results: per-criterion result with the Toulmin model (verdict/confidence as columns, narrative in a typed toulmin jsonb), N:1 verifier_tracing_id for batch judging, FP/FN flags for the data flywheel; relates to the plan via operation_id + stable check_item_id Ref: LOBE-10019 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(verify): add Agent Run delivery checker backend + frontend module Implements the verify system on top of the schema (PR #15480): - models: verifyCriterion / verifyRubric (+junction) / verifyCheckResult; agentOperation verify plan/status methods - services/verify: AI plan generation (auto-create criteria), executor with LLM Toulmin judge (per-criterion + batch), program placeholder, agent & auto-repair spawner seams, rollup chokepoint, feedback fp/fn, completion lifecycle bridge - lambda verify router (criteria/rubric CRUD, plan, results, feedback) - frontend feature module: service, SWR hooks, CheckerDock state machine, RunArtifact, verify i18n namespace - tracing scenarios: VerifyPlanGen / VerifyJudge Live UI mount (dock/artifact into chat) pending server operationId source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(verify): persist delivery-checker verdicts via async tracing backfill The LLM judge produced valid verdicts but they were never persisted, leaving every run stuck at `verifying`. Two root causes: 1. FK ordering: `writeVerdict` stamped `verifier_tracing_id` synchronously, but the `llm_generation_tracing` row is written asynchronously (best-effort, after the response) — so the hard FK was violated every time and the verdict write was rolled back. Now the verdict is written with a null link, and the tracing id is backfilled by an `onPersisted` callback that fires only after the tracing row commits (still non-blocking). If tracing is disabled the link simply stays null. 2. Verdict parse: the judge JSON schema is non-strict, so the provider returns optional Toulmin fields as explicit `null`. The Zod validator used `.optional()` (accepts undefined, not null), so any null failed the whole `safeParse` and discarded the batch. Switched to `.nullish()`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(cli): add `verify` command for the delivery checker Adds `lh verify` covering the full delivery-checker chain — criteria & rubric CRUD, per-run plan (generate/state/confirm/skip), execute (LLM judge), results, and feedback — calling the `verify` lambda router. Enables end-to-end backend testing of the verify system. Also adds the missing `tool-runtime` / `prompts` / `const` workspace entries to the CLI's `pnpm-workspace.yaml` so the standalone package installs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(verify): add verify message role + delivery-checker card UI Make the delivery-checker renderable in chat: - Fix the `features/Verify` components so they compile: flatten the `verify` locale to the repo's flat-dotted-key convention (keySeparator: false), import `Flexbox`/`TextArea` from `@lobehub/ui` (react-layout-kit is no longer a dep), and the token cast. - Add a `verify` UI message role + a `VerifyMessage` card that renders the Run Artifact + checker dock from `metadata.verifyOperationId`, wired into the message renderer switch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): add lobe-agent `generateVerifyPlan` tool (server runtime) Lets an agent set up the delivery checker for its run: the agent calls `generateVerifyPlan` early (per the new `<delivery_checker>` system-role guidance), which instantiates the rubric / ad-hoc criteria into a frozen plan on the current `agent_operations` row. Executed server-side only — the executor is dispatched via `runtime[apiName]` with `operationId` threaded through the tool execution context; the client `BaseExecutor` gracefully no-ops it. Also registers the metadata fields (`verifyOperationId`/`verifyRound`) on the message metadata zod schema so the role='verify' card can carry its operation id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): surface role=verify card on run completion (LOBE-10051) Connect the delivery checker to the conversation: when an Agent Run with a verify plan completes, `CompletionLifecycle` inserts a persisted `role='verify'` message (parented to the assistant, carrying `metadata.verifyOperationId`) that renders the checker card. Self-guarded — no plan → no card, failures never affect the run. `role='verify'` behaves like a `user` leaf message everywhere it flows (persistence + conversation-flow pass it through unchanged); only the context-engine treats it specially: a new `VerifyMessageProcessor` drops it from the model context (UI-only card, not a valid model role). Adds `verify` to `CreateMessageRoleType`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(verify): merge run-artifact + checker into one card The role=verify message rendered two stacked cards (Run Artifact summary + Delivery Checker) that duplicated the check-item list. Merge into a single card: the `Run Artifact · Round N` header, then the checker results + actions, then the snapshot note. RunArtifact/CheckerDock gain an `embedded` prop (header-only / body-only, no card chrome) and VerifyMessage composes them under one border. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): derive generateVerifyPlan rubric from agencyConfig A real agent calls `generateVerifyPlan` with just a `goal` and doesn't know rubric ids. When `rubricId`/`criteriaIds` params are absent, derive the mounted rubric + ad-hoc criteria from the executing agent's `agencyConfig.verifyRubricId / verifyCriteriaIds`. Params still win when given. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(cli): surface agent gateway WebSocket close code + reason The `onclose` handler logged `String(event)` → the useless "[object CloseEvent]". Surface `event.code` (+ `event.reason` when present) so a gateway disconnect before completion is actually diagnosable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 fix(verify): rename "Run Artifact" → "Verification", drop failed red border - The kicker said "Run Artifact" — it's automated verification, not an artifact. Renamed to "Verification · Round N". - Removed the red error border on a failed check — a normal card reads better. - Fixes a render crash (`useVerifyState is not defined`): the border removal left a dangling reference after the import was dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(cli): poll run status when the agent stream drops When the live stream (gateway WebSocket / SSE) closes before the run finishes, the run is still executing server-side — so instead of hard-exiting, fall back to polling `aiAgent.getOperationStatus` every 10s until the run reaches a terminal state (or is no longer tracked). Pairs with surfacing the WS close code/reason. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(verify): add Render for generateVerifyPlan tool call The generateVerifyPlan tool call rendered as the default param/result dump. Add a Render that lists the generated delivery checks (title + gate/auto-fill tag), and surface the items on the tool state so the Render can read them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): auto-confirm generated plan so checks run on completion The agent generated a plan but it stayed `planned`/unconfirmed, so the completion hook (which gates on a confirmed plan) never ran the checks — the card was stuck at "awaiting confirmation" with no pass/fail. In the headless agent flow there's no one to click Confirm, so `generateVerifyPlan` now auto-confirms the plan it generates; the checks then run automatically on completion. (An interactive "review before run" gate is a future enhancement.) Also: the verify card header disappeared in the draft/planned phase (`phaseToArtifact.draft` was null). Give it a header so the card always shows its "Verification · Round N" heading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent-tracing): only count opaque/presentational attrs as structural noise The first structuralNoiseRatio charged ALL markup (every <...> tag) as noise, which over-penalized legitimately structured results 3x. Grounding against real web-search output (`<item title="…" url="…">snippet</item>`) showed the tags and the title=/url= attributes ARE the signal the model reads. Now only opaque/presentational attribute names (id, class, style, data-*, aria-*, role, on*) count as noise; semantic element tags and content-bearing attributes (title, url, href, name…) are kept. On a 57-op user-interrupted sample this drops web-search noise 42%→0% and overall estimated waste 16%→5%, leaving large-payload (readDocument) and high error-rate tools as the real signal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): model-authored criteria with name/description/instruction-in-document + agent verifier Restructure the generateVerifyPlan tool to a createDocument-style full-create flow and wire up the agent verifier path: - criteria now = title + description (required one-liner) + instruction (required detailed rubric); instruction lives in a linked document (verify_criteria.documentId), description is a new verify_criteria column (migration 0111). verifierConfig no longer holds description/instruction. - generateVerifyPlan creates verify_criteria + a rubric, snapshots the plan onto the operation and confirms it; judge resolves the instruction from the document. - agent-type checks run as verifier sub-agents (execAgent + isolated thread) whose onComplete hook parses a VERDICT and writes it back to verify_check_results (renamed AgentVerifierSpawner → VerifierAgentRunner). - UI: custom Inspector for the tool header; check list shows per-verifier-type icons (llm/agent/program) + description + required/optional tag; i18n en/zh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ⚡️ perf(verify): run program/llm/agent checks concurrently on completion The three verifier kinds are independent; previously the agent spawn waited for the batched LLM judge to finish. Run them via Promise.all so agent sub-agents start immediately alongside the LLM batch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): dedicated builtin verify-agent + writeback tool, role=verify message, portal check editor - Add `@lobechat/builtin-tool-verify` (submitVerifyResult) + builtin `verify-agent`; agent-type checks now run as the dedicated verify agent (not the user's agent), which investigates and writes its verdict back via the tool during its run. - Verifier inherits the parent run's model/provider (builtin default may be unconfigured locally). - role=verify completion message no longer requires an assistantMessageId, so the delivery-checker card always surfaces when a plan exists. - Portal editor for verify checks (title/description/instruction/verifier/onFail). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(verify): restrict verify-agent to its writeback tool; fix running loader icon Root cause of stuck `running` agent checks: the verify-agent ran in agent mode and inherited all default tools (web-browsing, cloud-sandbox, skills, activator), so it went off web-searching/crawling to "investigate" and never called submitVerifyResult. - Run the verify-agent in chat mode (enableAgentMode: false, searchMode: off) — the strict whitelist — and whitelist `lobe-verify` for chat mode so the verifier gets ONLY its writeback tool. - Sharpen the verify systemRole: judge from the provided deliverable/instruction (no external tools), always reach a verdict, and always call submitVerifyResult. - CheckerDock: running check now uses the standard RingLoadingIcon (warning ring), matching the app's loader instead of a blue spinner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): auto-repair loop — re-run the agent with failure feedback on failed checks When required checks fail with onFail=auto_repair, automatically run a second iteration instead of ending at `failed`: - createRepairRunner: re-runs the SAME agent in the same topic with the failure feedback as the prompt, re-snapshots the plan onto the repair operation and confirms it so it re-verifies on completion (the next round). Capped at MAX_REPAIR_ROUNDS via parent-chain depth to prevent runaway loops. - maybeAutoRepair: fires only once every required check has a terminal result, so it works for inline LLM checks (triggered from lifecycle) and async agent checks (triggered from the verify tool's writeback path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): open check result detail in portal & rename artifact→result - add a VerifyResult portal view: clicking any check row opens that result's detail (verdict, confidence, Toulmin sections, suggestion) on the right; agent checks expose their execution trace from inside the panel - CheckerDock rows are all clickable now (chevron affordance), status shown by icon only; verify card uses colorBgElevated - rename the run-result surface from "artifact" to "result" everywhere: RunArtifact → RunResult, phaseToArtifact → phaseToResult, and all `artifact.*` i18n keys → `result.*` - ship verify namespace zh-CN / en-US locales Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): enrich check result portal — criterion stepper, richer detail view Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): rubric run-policy config + repair feedback on the verify card Auto-repair feedback now lives on the failed round's role=verify message (content), and the VerifyMessageProcessor surfaces it into the repair run's context as a tagged user turn — so the repair op runs off history via a new execAgent `suppressUserMessage` path instead of injecting a synthetic user message. createVerifyMessage is awaited before verification to avoid a race. maxRepairRounds becomes a rubric-level config: new `verify_rubrics.config` jsonb column, read live at repair time via the plan's sourceRubricId. Adds a RubricConfig portal panel (reachable from the plan card's settings affordance) to view/edit it, wired through the verify store + TRPC. Verify domain types/vocab/config are extracted from the DB schema into @lobechat/types as the single source of truth; schema and consumers import from there. Tests: VerifyMessageProcessor dual behavior; VerifyRubricModel config round-trip; MessageModel.findVerifyMessageByOperationId. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🗃️ refactor(verify): squash the 3 verify migrations into one Collapse 0110 (tables) + 0111 (criteria.description) + 0112 (rubrics.config) into a single regenerated 0110_add_verify_tables so the PR ships one clean, idempotent migration. No schema change vs the three combined. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(cli): verify rubric run-policy config commands + shrink judging-rule editor font CLI: `verify rubric create --max-repair-rounds`, `verify rubric view`, and `verify rubric update` exercise the rubric config endpoints end-to-end; adds a mocked command test. UI: judging-rule editor font 16px → 14px. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): editable rubric name in the config panel + default 3 repair rounds Add a name (title) field to the RubricConfig portal, persisted via a new updateRubricTitle store action + service (optimistic + debounced, alongside the config write-back). Bump DEFAULT_MAX_REPAIR_ROUNDS 2 → 3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(verify): extract generateVerifyPlan into installable lobe-delivery-checker tool Move the delivery-checker plan-creation flow out of the always-on lobe-agent tool into a new standalone, installable builtin tool `lobe-delivery-checker` (Skill Store, opt-in per agent — not loaded by default). lobe-agent no longer ships generateVerifyPlan. - new packages/builtin-tool-lobe-delivery-checker (manifest/types/systemRole + client Render/Inspector/Portal moved wholesale from lobe-agent) - new serverRuntimes/lobeDeliveryChecker.ts (generateVerifyPlan moved out of lobeAgent.ts), registered alongside verifyResult - registered installable in builtin-tools (no hidden/discoverable:false, not in defaultToolIds/alwaysOnToolIds/runtimeManagedToolIds); renders/inspectors/ portals/identifiers wired; lobe-agent portal entries removed - i18n keys moved builtins.lobe-agent.verifyPlan.* → builtins.lobe-delivery-checker.* Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): add `custom` tool mode; verify agent uses it instead of chat-mode Chat mode's contract is to strip ALL user/agent plugins (strict KB/memory/web allow-list) — so the verify sub-agent couldn't get its writeback tool without a leaky blanket rule. Introduce a third tool mode `custom` where the toolset is EXACTLY the agent's declared plugins (no always-on, no defaults, no activator), for focused builtin sub-agents. - chatConfig.toolMode: 'agent' | 'chat' | 'custom' (overrides enableAgentMode) - AgentToolsEngine: custom branch (defaultToolIds = plugins, rules = plugins-on, allowExplicitActivation only in agent mode); chatModeRules restored to strict - verify agent → toolMode: 'custom'; lobe-verify dropped from chatModeAllowedToolIds - test: custom mode enables exactly the declared plugin, no always-on / defaults Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fc0daa7604 |
💄 style(conversation): show running indicator after a settled inline tool while generating (#15528)
✨ feat(conversation): show running indicator after a settled inline tool while generating Heterogeneous agent turns render a single tool call inline (no WorkflowCollapse chrome). Once that tool settles but the run is still generating the next step, the inline path showed nothing below it — a blank gap that reads as "stuck". Render the same turn-start "running" indicator at the segment tail for this case. Multi-tool segments keep WorkflowCollapse's own streaming header; a tool still executing is already covered by its loading placeholder. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
df72bc335e |
🎨 refactor(local-system): preserve ANSI escape codes in command output (#15529)
* 🎨 refactor(local-system): preserve ANSI escape codes in command output The client now renders ANSI sequences, so stripping color codes from shell command output is no longer needed. Drop the stripAnsi helper and let truncateOutput keep the raw colored output intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(local-system): drop dangling ANSI escape and reset open SGR state before truncation notice Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e855fcc0b8 |
♻️ refactor(desktop): move backend URL rewrite into main process (#15304)
* ♻️ refactor(desktop): move backend URL rewrite into main process Renderer code no longer needs `withElectronProtocolIfElectron` to rewrite backend URLs to `lobe-backend://`. The Electron main process now diverts backend-prefixed paths (`/trpc`, `/webapi`, `/api/auth`, `/market`) to the remote LobeHub server in two places: - prod: `RendererProtocolManager` (`app://` handler) delegates to `BackendProxyProtocolManager.proxy(request, session)` after the existing hostname guard. - dev: `Browser.setupRemoteServerRequestHook` registers a `webRequest.onBeforeRequest` listener that redirects `http://localhost(:*)/<backend-prefix>...` to `lobe-backend://lobe<path>`. `BackendProxyProtocolManager` keeps a per-session `WeakMap<Session, Context>` and exposes `proxy(request, session)` so the same OIDC token / Vercel cookie / 401 debounce / `X-Auth-Required` pipeline serves both entry points. The helper and ~35 call sites in `src/services/_url.ts` and the three tRPC clients are removed. `ELECTRON_BE_PROTOCOL_SCHEME` stays for the main process; new `BACKEND_PATH_PREFIXES` + `isBackendPath` predicate live in `apps/desktop/src/main/const/protocol.ts`. * ♻️ refactor(desktop): decouple renderer protocol from backend proxy via interceptor pipeline `RendererProtocolManager` no longer imports `BackendProxyProtocolManager` or `isBackendPath`. It exposes a generic `addRequestInterceptor(fn)` hook and runs interceptors in order inside the `app://` handler — first non-null Response short-circuits the file pipeline. `BackendProxyProtocolManager.createAppRequestInterceptor()` owns the "what counts as a backend path" knowledge and returns a 502 for backend prefixes when no proxy context is wired up (must not fall through to SPA HTML). Wiring happens in `App.ts` after `RendererUrlManager` construction — composition root knows both modules so neither has to know the other. * ♻️ refactor(desktop): unify dev/prod renderer under app:// and drop lobe-backend:// Dev mode no longer uses `http://localhost:<port>` as the renderer origin; the BrowserWindow now loads `app://renderer/` in both dev and prod. Non-backend requests fall through to a strategy: - prod: `StaticRendererFallback` serves the static export from `rendererDir` (Range support, SPA HTML fallback, 404 handling) - dev: `ViteRendererFallback` proxies to the electron-vite dev server via `net.fetch('http://localhost:5173/<path>')`; HMR WebSocket connects directly (configured via `server.hmr.{host,clientPort}` + `strictPort`) `lobe-backend://` is gone — the scheme, its privileged registration, the `session.protocol.handle('lobe-backend', ...)` call, and the dev `webRequest.onBeforeRequest` trampoline are all removed. `BackendProxyProtocolManager` now only stores per-session context and exposes `createAppRequestInterceptor()` for the `app://` pipeline. Dev userData is pinned to `<appData>/lobehub-desktop-dev` via a new `pre-app-init.ts` that runs before `@/const/dir` captures `app.getPath('userData')` — necessary because dev and prod now share the `app://renderer` origin and would otherwise collide on localStorage / cookies / IndexedDB. Also adds `stream: true` to the `app` scheme privilege so dev media Range requests survive forwarding. |
||
|
|
ee6a74ba06 |
🗃️ feat(db): verify delivery-checker schema + ai_providers/ai_models _id column (#15526)
🗃️ feat(db): delivery-checker schema + ai_providers/ai_models surrogate `_id` The DB layer, split out so it merges ahead of its callers (services / TRPC / store / UI ship in a follow-up stacked PR). One consolidated, idempotent migration (0110_add_verify_tables_and_ai_infra_id): - verify delivery-checker: verify_criteria / verify_rubrics (+ config) / verify_rubric_criteria / verify_check_results tables + verify_status / verify_plan / verify_plan_confirmed_at columns on agent_operations; plus the verify domain types/vocab/config in @lobechat/types the schema imports. All four verify tables carry a workspace_id FK + index (cascade on workspace delete), matching documents / agent_operations. verify_check_results has a UNIQUE (operation_id, check_item_id) index — one lifecycle row per plan item per run, so a retry / concurrent worker can't create conflicting duplicates. - ai-infra (LOBE-10072): nullable `_id uuid DEFAULT gen_random_uuid()` on ai_providers / ai_models, written as the safe two-step form (ADD nullable, then SET DEFAULT) to avoid a full-table rewrite + ACCESS EXCLUSIVE lock; backfill + NOT NULL are later manual steps (LOBE-10073 / LOBE-10074) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
20cea3a6bf |
✨ feat(page-agent): execute tools server-side via HeadlessEditor (#15023)
* ✨ feat(page-agent): execute tools server-side via HeadlessEditor Page-agent tools (initPage / editTitle / getPageContent / modifyNodes / replaceText) now run on the server against a `@lobehub/editor/headless` instance and persist through `DocumentService.updateDocument`, instead of executing inside the renderer's Lexical instance. The renderer applies the resulting snapshot via the builtin-tool `onAfterCall` hook, so the document store stays in sync without an extra fetch. This makes page-agent execution independent of the client lifecycle (editor unmount, tab switch, network blip), gives us full server-side tracing for free (OTel gen-ai + agent-signal + documentHistories), and exposes a `silent-no-op` / `unexpected-mutation` invariant when the exported editorData hash diverges from what the handler reported. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * 🐛 fix(page-agent): decouple EditorRuntime from @lobehub/editor side-effecting bundle EditorRuntime statically imported LITEXML_*_COMMAND from @lobehub/editor, which pulls ReactSlashPlugin and crashes Node (`document is not defined`) in any server-side test that transitively touched the runtime. The same import also dispatched the wrong command identity on HeadlessEditor's kernel — pnpm resolves @lobehub/editor to a different module copy than the headless bundle, so dispatchCommand would silently no-op server-side. Introduce a LiteXMLAdapter strategy: renderer wires command dispatch against the live editor; server wires HeadlessEditor.applyLiteXMLBatch / applyLiteXML so the correct headless-bundle symbols are used. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * 🐛 fix(page-agent): restore client-side mutate handlers on PageEditor mount The main commit dropped `setBeforeMutateHandler`/`setAfterMutateHandler` under the assumption that page-agent tools always execute server-side. But the chat-store path (`invokeBuiltinTool` → `PageAgentExecutor.modifyNodes` → `EditorRuntime.modifyNodes`) still routes through the client-bound runtime whenever the LLM dispatcher is the chat slice — it does not consult `manifest.executors`. Without the handlers, that path mutates the live editor but skips both `documentHistoryQueueService.enqueueEditorSnapshot` (loses undo baseline) and `commitEditorMutation(saveSource: 'llm_call')` (row never persists). Re-wire both handlers. Server-runtime path is unaffected: it instantiates its own `EditorRuntime` against `HeadlessEditor` and never sees the client's StoreUpdater wiring, so the two paths can coexist without double-writing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ♻️ refactor(editor-runtime): split client / server entries so renderer gets adapter for free Renderer call sites shouldn't have to opt in to the obvious default (dispatch LITEXML_*_COMMAND on the live editor). Split the package into two entries: - `@lobechat/editor-runtime` — renderer entry; constructor auto-wires the LiteXML adapter from `@lobehub/editor`. Static-importing this from Node still crashes (ReactSlashPlugin), so it's the right shape for the browser only. - `@lobechat/editor-runtime/server` — server-safe entry; exports the bare class without touching `@lobehub/editor`. Callers (currently only the page-agent server runtime) supply their own HeadlessEditor- backed adapter. Drops the renderer-side setLiteXMLAdapter patch and a stale comment block in StoreUpdater. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ♻️ refactor(page-agent): drop LiteXMLAdapter, dispatch commands directly `@lobehub/editor` 4.16.1 ships the LiteXML command identities through the side-effect-free `@lobehub/editor/litexml-commands` subpath, so a single command object is shared across the browser and node bundles and can be imported in Node without pulling the DOM-dependent editor bundle. `EditorRuntime` now imports `LITEXML_MODIFY_COMMAND` / `LITEXML_APPLY_COMMAND` from that subpath and dispatches them straight onto the editor kernel. This removes the `LiteXMLAdapter` strategy object (`setLiteXMLAdapter` / `getLiteXMLAdapter`) — a leaky abstraction whose only purpose was to keep the crash-on-Node command import out of the shared base. - editor-runtime: dispatch `LITEXML_*_COMMAND` directly; delete the adapter interface, field, setter and runtime-throw guard. - Collapse the client/server entry split (its sole reason — isolating the DOM-crashing import — is gone); both entries now re-export the isomorphic base. - pageAgent server runtime: drop the HeadlessEditor-backed adapter wiring. - Bump `@lobehub/editor` to ^4.16.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(editor-runtime): drop redundant /server entry Now that `EditorRuntime` is isomorphic (LiteXML commands come from the DOM-free `@lobehub/editor/litexml-commands` subpath), the `./server` entry is byte-for-byte identical to the root `.` entry. Remove it and point the only consumer (pageAgent server runtime) at the root entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
78657d496e |
🐛 fix(desktop): pin electron-builder to 26.14.0 to fix broken macOS update signing (#15527)
electron-builder was floating on `^26.8.1` and the repo commits no lockfile, so each CI build resolved a fresh version. The canary.12 build (2026-06-07) picked up 26.15.0, which regressed macOS .app bundle signing: codesign reports "bundle format is ambiguous (could be app or framework)" and Squirrel.Mac rejects the update during code-signature validation, so the app never quits to install — surfacing as "auto-update does nothing". 26.15.0 introduced the two suspect changes (mac signing rework #9822 and the full app-builder-bin Go→TS replacement #9829). 26.14.0 predates both and does not touch macOS app-bundle signing/layout. Pinning the exact version cascades to app-builder-lib / dmg-builder / builder-util (electron-builder pins those exactly), stopping the toolchain from floating across CI installs. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>v0.0.0-nightly.pr15527.15320 |
||
|
|
2453fc3515 |
🐛 fix(desktop): skip browser beforeunload guard so auto-update can quit (#15525)
On desktop the chat-loading beforeunload guard (preventLeavingFn) blocks window.close() during quitAndInstall, so the app fails to quit & install the update. The main process already manages close/quit via keepAlive + isQuiting, so short-circuit the guard on desktop. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>v0.0.0-nightly.pr15525.15309 |
||
|
|
a28fd30719 |
✨ feat: suppport sandbox provider (#15184)
* ✨ feat(cloud-sandbox): add Onlyboxes provider support for self-hosted sandbox (#15136) - Add `SANDBOX_PROVIDER` env var (market | onlyboxes) to select sandbox backend - Add Onlyboxes-specific env vars: `ONLYBOXES_BASE_URL`, `ONLYBOXES_API_TOKEN`, `ONLYBOXES_LEASE_TTL_SEC` - Create `SandboxService` abstraction layer with `MarketSandboxService` and `OnlyboxesSandboxService` implementations - Add `createSandboxService` factory that routes to configured provider - Migrate `execInSandbox` and `exportFile` t * ✨ feat(sandbox): improve Onlyboxes export flow * 🐛 fix(sandbox): pass presigned upload headers to Onlyboxes * ✅ test(sandbox): import tool runtime package * 🐛 fix(sandbox): preserve Market export errors * 🐛 fix(sandbox): allow empty docker env defaults * 🔒 fix: redact sandbox auth params in logs * 🐛 fix: address sandbox provider review comments * 🔐 feat: use onlyboxes jit tokens * 📝 docs: clarify cloud sandbox provider config * 🐛 fix: align cloud sandbox timeout defaults * 🐛 fix(sandbox): lower default Onlyboxes lease TTL to 15 minutes * 🐛 fix(sandbox): cap Onlyboxes task wait time * ♻️ refactor: split sandbox env config |
||
|
|
c711279edf |
✨ feat(tools): show app-fixed tools in the chat-input Pinned section (#15509)
* ✨ feat(tools): show app-fixed tools in the chat-input Pinned section Surface always-on, runtime-owned tools (lobe-agent + always-on infra) read-only at the top of the Tools popover "Pinned" group, so users can see what the app keeps active for every conversation. These have no toggle — a Pin indicator with a hint replaces the per-tool policy menu. - builtin-tools: add `fixedDisplayToolIds` ([lobe-agent, ...alwaysOnToolIds]) - builtin selectors: add `fixedDisplayMetaList` (reads hidden tools by id) - useControls: render read-only fixed items, prepend to Pinned, fold into counts - i18n: add `tools.activation.fixed.hint` + `tools.builtins.lobe-agent.*` Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(tools): make lobe-agent actually always-on; gate fixed display to runtime The Pinned section was rendering tools that aren't enabled every turn: - lobe-agent was only enabled when injected into plugins/runtime ids (it has no rule in the engine, so it defaulted to disabled) — showing it as "always on" was a UI lie. - manual skill-activate mode strips manualModeExcludeToolIds (activator, skill-store) from the defaults, so they're off — but they still showed as fixed. Fixes: - Add lobe-agent to alwaysOnToolIds so its core capabilities (plan/todo, sub-agent dispatch, visual-media fallback) are genuinely on every agent-mode turn. Chat mode still drops alwaysOn entirely. - Derive fixedDisplayToolIds from alwaysOnToolIds (single source of truth, no drift). - Make fixedDisplayMetaList mode-aware: drop manualModeExcludeToolIds in manual mode so the Pinned list matches what the engine actually enables. - Update engine tests that asserted the old "lobe-agent off by default" behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ♻️ refactor(tools): drop fixedDisplayToolIds alias, use alwaysOnToolIds directly fixedDisplayToolIds was just `= alwaysOnToolIds`; collapse it. The selector now reads alwaysOnToolIds directly and still applies the manual-mode exclusion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e7c73bd4ce |
💄 style: support show CC subagent metrics chip (#15217)
* ✨ feat(cc): show tool count + token + model metrics on Agent inspector chip Surface per-subagent progress on the inline Agent inspector row so users can see how much work has happened without expanding the thread: - Inspector chip renders `[count] tools · [tokens]` after the description chip, with the model name in a Tooltip. Tool count = count of `role==='tool'` child messages; tokens = LAST subagent assistant's `metadata.usage.totalTokens` (CC's per-turn `message.usage` already includes the full prior context, so summing would double-count the shared history — the final turn's value matches the main-agent message-footer convention). - New `threadSelectors.getThreadDbMessages` reads the raw DB-shape child messages from `dbMessagesMap[thread_*]` (the display-bound `messagesMap` bucket only holds the parent + a virtual `assistantGroup`). - `BuiltinInspectorProps` carries `toolCallId` so the chip can join to its subagent Thread via `metadata.sourceToolCallId`; propagated from both the chat Inspector caller and the DevPanel `ToolInspectorSlot`. Adapter / executor changes so subagent token usage actually flows in: - `claudeCode.ts` `handleSubagentAssistant` emits a `step_complete{phase:turn_metadata, subagent}` event when `raw.message.usage` is present. Subagent assistant events are not partial-streamed (unlike main-agent), so `message.usage` is authoritative — no de-stale logic needed. The subagent ctx tag lets the executor route the usage write onto the in-thread assistant instead of the main agent's, so CC's `result_usage` grand-total semantics aren't double-counted. - Renderer + server `step_complete{turn_metadata}` branches check for `event.data.subagent` and route to the run's `currentAssistantMsgId`. Renderer mirrors the write into `dbMessagesMap` via `run.stream.update` so the chip's selector picks up usage as it lands. Server-side finalize rolls totals onto `thread.metadata` for the historical-view cold-load path: tool count from `lifetimeToolCallIds.size`, tokens from the last in-thread assistant's `metadata.usage.totalTokens`, plus `completedAt` / `duration`. Done via the existing `threadModel.update` with an inline metadata read-merge — no new `ThreadModel.updateMetadata` method or `threadRouter.updateThreadMetadata` endpoint introduced. i18n: 5 keys under `chat.thread.subagentMetrics.*` in `chat.ts` + zh-CN + en-US. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(cc): persist subagent metrics so the inspector chip survives cold-load The metrics chip (tool count · tokens, model in tooltip) only rendered while the run streamed — after a reload it vanished on desktop. Two gaps: - The renderer `heterogeneousAgentExecutor.finalizeSubagentRun` never rolled totals onto `thread.metadata` (only the server `HeterogeneousPersistenceHandler` did). On cold-load the child messages aren't hydrated, so the live selector had nothing to read and the chip's `hasAny` went false. Added the symmetric rollup (`totalToolCalls` / `totalTokens` / `completedAt` / `duration`), re-sending the create-time `sourceToolCallId` / `subagentType` / `startedAt` since `updateThread` replaces the whole metadata column. - Subagent assistant messages carried no `model`, so the tooltip's model line never showed. The subagent `turn_metadata` branch now writes `model` / `provider` onto the in-thread assistant (live tooltip) and persists `model` onto `thread.metadata.model` (cold-load tooltip); the chip selector falls back to `thread.metadata.model`. Also fixes a latent bug both paths shared: finalize read `totalTokens` off `currentAssistantMsgId`, which by then points at the freshly-created terminal assistant (no usage), so it always resolved `undefined`. Now tracks the last non-zero per-turn `totalTokens` on the run — matching the live selector's "last turn, not a sum" convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(cc): derive subagent chip metrics on read, drop run-state tracking The chip's tool-count / token / model metrics were captured incrementally on the subagent run (`lastTurnTokens` / `subagentModel`) and denormalized onto `thread.metadata` at finalize — in BOTH the renderer executor and the server handler, so the rule lived in three places and the two finalize paths had to be kept in sync by hand. Derive them on read instead, from the child messages (the single source of truth): - `aggregateSubagentMetrics(messages)` (new, `src/utils`) is the one rule: COUNT `role='tool'`, SUM every assistant turn's `usage.totalTokens`, pin the model. SUM (not last-turn) matches the project's token-usage heatmap convention — "total tokens processed". - The chip selector aggregates the in-memory child messages live, falling back to `thread.metadata.*` on cold-load. - `threadModel.queryByTopicId` computes the SAME projection in SQL (LEFT JOIN + GROUP BY, reusing the `usage->totalTokens` index, with a legacy `metadata.usage` fallback) and folds it onto `metadata`, so cold-load reads a server-derived value without hydrating the child messages. Both finalize paths drop the metadata rollup and now only flip thread status Active; `lastTurnTokens` / `subagentModel` run-state fields are gone. Each subagent turn still writes its `usage` + `model` onto the in-thread assistant — those rows are what the read-time aggregation sums over. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
28f0117932 |
💄 style(tool-ui): render ANSI escape codes in RunCommand output (#15516)
✨ feat(tool-ui): render ANSI escape codes in RunCommand output Parse ANSI SGR sequences in shell stdout/stderr with anser and emit styled spans for fg/bg colors, dim, bold, italic, underline, strikethrough. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
573cc5b798 |
💄 style(desktop): move panel toggle into titlebar top-left (#15515)
* ✨ feat(desktop): move panel toggle into titlebar top-left Place a persistent collapse/expand toggle at the titlebar's top-left corner on desktop, to the right of the macOS traffic lights. The NavigationBar now splits into a left group (toggle) and a right group (back / forward / clock) with space-between: expanded, the right group hugs the sidebar's right edge; collapsed, the controls cluster at the left edge like codex. ToggleLeftPanelButton gains an optional `id` prop so the titlebar instance can opt out of the shared TOGGLE_BUTTON_ID, avoiding a duplicate DOM id and NavPanelDraggable's hover-reveal CSS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(desktop): expand untracked directories in git status `git status --porcelain` defaults to `--untracked-files=normal`, which collapses whole untracked directories into a single `?? path/` entry. That trailing-slash path then flowed into `readUntrackedAsPatch` as if it were a file — `stat()` reported `isFile()=false`, an empty patch was returned, and the Review panel rendered "无法加载该文件的 diff" against a directory row. Pass `-u` so git expands those directories into their individual files; each file then produces a real synthetic patch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * 💄 style(desktop): scope titlebar toggle to macOS, hide in-page toggles there The persistent titlebar toggle now renders only on macOS; Windows/Linux keep the original right-aligned navigation controls and their in-page toggles. On macOS desktop, ToggleLeftPanelButton instances hide themselves (the titlebar owns the control) unless `forceVisible` is set, removing the now-redundant sidebar-header and content-header toggles. NavHeader also skips rendering its empty toggle-only bar in this case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>v0.0.0-nightly.pr15515.15272 |
||
|
|
7b54edc665 |
🐛 fix(database): scope ai-infra upsert conflict targets to workspace (precursor for 0110) (#15507)
🐛 fix(database): scope ai-infra upsert conflict targets to personal partial index
The 0110 migration replaces the (id, user_id) / (id, provider_id, user_id)
primary keys with partial unique indexes (WHERE workspace_id IS NULL). A bare
ON CONFLICT target can no longer infer a partial index, so add
`targetWhere: isNull(workspaceId)` (and `where` for onConflictDoNothing) to
every personal-scope upsert. Keeps existing provider/model toggling, ordering
and batch upserts working after the migration.
|
||
|
|
b6ae130c97 |
✨ feat(agent): auto-scan project workspace (skills + AGENTS.md) for server agents (#15512)
* ✨ feat(agent): auto-scan project workspace (skills + AGENTS.md) for server agents When a server agent runs against a bound project directory, scan it server-side at run start for project skills (.agents/skills + .claude/skills) and root AGENTS.md/CLAUDE.md, cache the result on devices.workingDirs[].workspace (1h TTL), surface skills in <available_skills>, and inject instructions into the system role. Replaces the desktop-only client pre-scan so it works for any run initiator. - Generic device RPC channel (invokeRpc / rpc_request) for server-internal device methods, separate from the LLM-facing tool-call path - New desktop WorkspaceCtr owns project-skill / workspace scanning Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent): preserve workspace-init cache on device cwd save device.updateDevice validates workingDirs as { path, repoType } only, so zod strips the server-written workspace / workspaceScannedAt cache — an ordinary cwd pick wiped the 1h workspace-init cache (and web reuse), forcing every later run to rescan. The cache is server-owned, so re-attach it by path from the stored row instead of trusting the client to round-trip it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5b5794baa4 |
♻️ refactor(server): rename deviceProxy → deviceGateway (#15513)
Pure mechanical rename of the server device-relay module/class/singleton (deviceProxy → deviceGateway, file included) to match the underlying GatewayHttpClient naming. No behavior change. Split out of the workspace-init feature PR (lobehub/lobehub#15512) to keep that diff reviewable. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
04700bed52 |
✨ feat(agent-runtime): server callSubAgent async suspend/resume (#15481)
* ✨ feat(agent-runtime): add waiting_for_async_tool parked state for deferred tools
Add a dedicated `waiting_for_async_tool` operation status that mirrors
`waiting_for_human` as a non-terminal, resumable pause, and migrate the
client-tool execution pause off `interrupted` onto it — so `interrupted`
once again means only user-initiated cancellation.
Also add the AgentOperationModel primitives the upcoming server sub-agent
bridge needs: queryByParentOperationId (reconcile child ops) and
tryResumeFromAsyncTool (atomic single-fire CAS).
Foundation for the server sub-agent suspend/resume mechanism (LOBE-9763).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ♻️ refactor(agent-runtime): extract isParkedStatus / isBlockedStatus predicates
Replace the repeated `status === 'waiting_for_human' || ... === 'waiting_for_async_tool' || ... === 'interrupted'`
chains with named predicates so the parked/blocked semantics live in one place
(runtime step-loop break, completion lifecycle completedAt, executeSync pause,
operation isActive).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ♻️ refactor(aiAgent): rename execSubAgentTask -> execSubAgent
Full rename of the service method, its `ExecSubAgentTaskParams`/`ExecSubAgentTaskResult`
types, the tRPC endpoint, the injected `RuntimeExecutorContext`/`AgentRuntimeServiceOptions`
callback, and tests. Group-mode `execGroupSubAgent*` identifiers are intentionally left
untouched. Prep for the server sub-agent suspend/resume work (LOBE-9763).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "♻️ refactor(aiAgent): rename execSubAgentTask -> execSubAgent"
This reverts commit
|
||
|
|
ad87e43b2e |
✨ feat(agent-tracing): tool-result feedback quality analysis (tq command) (#15508)
* ✨ feat(agent-tracing): add tool-result feedback quality analysis (tq command) Adds a shared, no-LLM analyzer that scores how "clean / LLM-friendly" the environment feedback (tool return content) is, plus an `agent-tracing tq` CLI command to preview it over a snapshot corpus. - src/analysis/toolFeedback.ts: pure analysis lib (reusable core) — per tool-result metrics (tokens, self-redundancy, structural-noise ratio, error flag/size, format) + op-level and corpus-level rollups. - src/cli/tool-quality.ts: `tq` (alias `tool-quality`) — token-size histogram, dirty leaderboard ranked by token-weighted waste, single-op drill-down, and --json. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent-tracing): guard against undefined histogram bucket in buildCorpusReport Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
32c293f8c0 |
✨ feat(claude-code): add per-question custom input to askUserQuestion (#15506)
* ✨ feat(claude-code): add per-question custom input to askUserQuestion Let users write their own answer as the trailing item in each question's option list, beside picking a numbered choice. Single-select treats the two as mutually exclusive; multi-select appends the custom text as an extra entry. Merged into the question's answer at submit, so the bridge formatter and completed Render need no changes. Draft round-trips via a __custom__: prefix on the existing askUserDraft map. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(claude-code): split askUserQuestion form & drop draft key prefix Break the single ~530-line AskUserQuestion.tsx into a folder: - draft.ts pure helpers (read/buildSubmitPayload/isQuestionAnswered) - useAskUserForm.ts all state + handlers + draft persistence - OptionCard.tsx / QuestionPanel.tsx presentational pieces - index.tsx thin view Also drop the `__custom__:<question>` draft-key prefix: persist the draft as a typed object { picks, custom, escapeText, escapeActive } instead of a flat string-keyed map. The picks/custom split now lives in named fields, so the only sentinel left is `__freeform__` — and only in the submit payload, which is the actual bridge contract. No behaviour change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(claude-code): make AskUserDraft assignable to setInterventionDraft `setInterventionDraft` takes `Record<string, unknown>`; an `interface` isn't assignable to it (open to declaration merging, so no implicit index signature). Switch `AskUserDraft` to a `type` alias, which is closed and satisfies the index signature. Fixes the tsgo TS2345 in CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
6f5a633c9f |
✨ feat(connector): Connectors system — API-level tool permissions with plugin fallback (#15463)
* ✨ feat(connector): add ConnectorModel, ConnectorToolModel, tRPC router, and inferCrudType util (LOBE-9984, LOBE-9985) - packages/database/src/models/connector.ts: ConnectorModel with create/delete/query/queryByIdentifiers/findById/update/updateStatus - packages/database/src/models/connectorTool.ts: ConnectorToolModel with upsertMany (preserves user permission on sync), updatePermission, queryByConnector, queryByConnectorIds - src/libs/mcp/utils.ts: inferCrudType() — name-based CRUD type inference (delete > update > read > write) - src/server/routers/lambda/connector.ts: tRPC router with list/create/update/delete/syncTools/updateToolPermission - src/server/routers/lambda/index.ts: register connectorRouter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): runtime integration — connector-first tool resolution with plugin fallback (LOBE-9986) - src/libs/mcp/buildConnectorManifests.ts: converts user_connector_tools rows into LobeToolManifest entries; maps permission → humanIntervention ('needs_approval' → 'required', 'disabled' → excluded) - src/server/services/aiAgent/index.ts: - queryByIdentifiers(agentPlugins) to find matching connectors first - filter installedPlugins to exclude connector-covered identifiers - inject connectorManifests as additionalManifests into createServerAgentToolsEngine - add connector stdio tools to client executor map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): add connector Zustand store slice (LOBE-9987) - src/store/tool/slices/connector/: new slice with ConnectorState, ConnectorAction, connectorSelectors - fetchConnectors, createConnector, deleteConnector, syncConnectorTools, disconnectConnector - updateToolPermission with optimistic update + rollback - connectorToolsGrouped selector splits tools into read / write groups - Wired into ToolStore (initialState + store.ts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): add Connectors UI feature — list, detail, tool permission editor (LOBE-9988) - src/features/Connectors/: new feature with two-panel layout (list + detail) - ConnectorList: groups connectors by Connected / Not connected, Add button - ConnectorDetail: sync button, disconnect, tool permission groups (read/write) - ToolPermissionGroup: collapsible with batch set (auto/approval/disable all) - ToolPermissionRow: three-state toggle auto(✓) / needs_approval(✋) / disabled(🚫) - AddConnectorModal: name + MCP URL input via @lobehub/ui/base-ui Modal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): add Connectors tab to Agent customization panel (LOBE-9989) - src/store/global/initialState.ts: add ChatSettingsTabs.Connector = 'connector' - src/features/AgentSetting/AgentCategory/useCategory.tsx: add Connectors tab with LinkIcon - src/features/AgentSetting/AgentConnectors/: new component listing user connectors with toggle - toggle calls toggleAgentPlugin(connector.identifier) — reuses agents.plugins[] field - shows per-connector tool count - src/features/AgentSetting/AgentSettingsContent.tsx: render AgentConnectors for Connector tab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): wire Connectors feature to /settings/connector route - src/store/global/initialState.ts: add SettingsTabs.Connector = 'connector' - src/routes/(main)/settings/hooks/useCategory.tsx: add Connectors item (LinkIcon) after Skills in AI config group - src/routes/(main)/settings/features/componentMap.ts: map SettingsTabs.Connector → '../connector' - src/routes/(main)/settings/features/SettingsContent.tsx: render Connector tab full-width (no SettingContainer), same as Provider - src/routes/(main)/settings/connector/index.tsx: route page rendering the Connectors feature Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): use cssVar.property syntax in createStaticStyles (not function call) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): refactor /settings/skill to unified master-detail tool manager ## Backend - connector.ts: add syncBuiltinTool — bootstraps user_connectors from builtin manifest api[] - connector.ts: add syncPluginTools — bootstraps user_connectors from user_installed_plugins manifest - connector.ts: upsertConnectorEntry helper + resolveDefaultPermission (maps humanIntervention → permission) - connectorTool.ts: SyncToolInput.defaultPermission — per-tool default for new rows, existing rows preserved ## Store - connector/selectors.ts: add connectorByIdentifier, connectorToolsGroupedByIdentifier, isSyncingByIdentifier - connector/action.ts: add syncBuiltinTool, syncPluginTools (idempotent — safe to call on panel open) ## /settings/skill refactor - index.tsx: two-panel master-detail layout (left: 300px skill list, right: detail + permissions) - SkillList: add onSelect + selectedIdentifier props, pass through to builtin/mcp items - BuiltinSkillItem: add onSelect + isSelected (selection highlight, click triggers right panel) - McpSkillItem: add onSelect + isSelected - SkillDetail (new): auto-syncs connector entry on mount, then renders ConnectorDetail permission editor - SettingsContent: Skill tab now renders full-width (same as Provider/Connector) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(skill): createStaticStyles returns static object, not a hook Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(skill): wire onSelect to all skill item types — LobehubSkillItem, KlavisSkillItem + error handling in SkillDetail Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): use createStaticStyles correctly — static object, not hook; use string concat instead of cx() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): whole row clickable in list mode, hide action buttons when onSelect provided All 5 item types (Builtin/Mcp/Lobehub/Klavis/AgentSkill): - When onSelect is provided (list mode): entire row is clickable, action buttons hidden - When onSelect is not provided (other usages): original behavior preserved - Added onSelect/isSelected to AgentSkillItem + wired in SkillList for all agent skill types - SkillDetail: show friendly message instead of error when skill has no tool permissions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): route sync action by sourceType; improve no-tools skill UI ConnectorDetail: - builtin → Reset (syncBuiltinTool from local manifest, resets permissions to defaults) - marketplace → Refresh (syncPluginTools from installed plugin manifest) - custom MCP → Sync (syncTools via remote MCP server, existing behavior) - Hide Disconnect button for builtin/marketplace (only MCP connectors can disconnect) - Show 'No tool permissions' message when connector has 0 tools - Fix hooks-rules violation: move useCallback before early return SkillDetail: - Catch sync failure cleanly — shows graceful 'no tool permissions' panel - Show skill identifier as title even when no tools available Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): inline AgentSkillDetail for agent skills; clean ConnectorDetail layout SkillDetail: - Add 'agent-skill' ToolDetailType — renders AgentSkillDetail inline (no modal, no connector sync) - All hooks called before conditional returns (fixes rules-of-hooks) SkillList: - Pass type='agent-skill' for market/user agent skills (UUID identifiers, not plugin identifiers) ConnectorDetail: - Remove 'Tool permissions / Choose when AI...' subheader — tool groups render directly - Cleaner layout: name → sync/disconnect buttons → tool groups Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): description in ConnectorDetail header + builtin-skill detail panel Backend (connector.ts): - syncBuiltinTool: store manifest meta.description + meta.avatar in connector.metadata - syncPluginTools: same for plugin manifest meta - upsertConnectorEntry: always update metadata on re-sync (keeps description fresh) ConnectorDetail: - Show connector.metadata.description below name in header SkillDetail: - Add 'builtin-skill' ToolDetailType for builtinSkills (Artifacts, Task, AgentBrowser) → Shows avatar + name + description panel; no connector sync needed (prompt-based) - Add 'builtin-skill' type: reads from store builtinSkills array by identifier SkillList: - builtinAgent items → pass type='builtin-skill' (not 'builtin') to SkillDetail Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): fix crudType for camelCase, show skill content, compact items + categorized groups inferCrudType (utils.ts): - Fix: use prefix ^ anchoring instead of \b word boundary - getReactions/listPins/searchMessages now correctly → 'read' (not 'write') - \b fails on camelCase: 'getreactions' has no boundary after 'get' (both \w chars) SkillDetail: - builtin-skill type: render builtinSkill.content via <Markdown variant='chat'> - Artifacts/Task/LobeHub skills now show their full markdown content in right panel style.ts: - Compact skill items: icon 48→36px, padding-block 12→6px SkillList: - Remove old flat renderIntegrations() + Divider - Add categorized sections with headers: LobeHub 内置 Tools | 内置 Skill | 社区 Skill | 社区 Tools | 自定义 - Add sectionHeader style Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): collapsible sections, compact items matching reference design style.ts: - icon: 28→24px, no background (reference style: plain icon, no container bg) - padding-block: 4→3px, font-size: 13px - sectionHeader: collapsible with hover state SkillList: - Sections are collapsible — click header to toggle - ChevronDown/ChevronRight icons on section headers - All renderSection calls now pass a unique key All item components (Builtin/Mcp/Lobehub/Klavis/AgentSkill): - gap: 16→8px (tighter horizontal spacing) - avatar/icon: 32→22px (matches reference ~24px icon) - In list mode (onSelect): tag moves to RIGHT side of row - In list mode: remove tag from title area, status text below title Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): default select first item; + button opens Add custom connector modal index.tsx: - Auto-select first installed builtin tool (or first builtin skill) on page load - + button → opens AddConnectorModal (add custom MCP connector) - 技能商店 button → still opens skill store (unchanged) AddConnectorModal: - Add Advanced settings section (collapsible chevron) - OAuth Client ID field → stored in oidcConfig.clientId - OAuth Client Secret field (UI only, encryption path TBD) - Clear all fields on cancel/submit Connectors/index.ts: export AddConnectorModal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): reference-quality UI polish + Connectors/Skills tab switcher Style polish (matching linear-tool-permissions demo): - style.ts: icon 20px, padding-block 6px, font-size 14px (no bold) - All item avatars: 16px - ToolPermissionRow: py-10px px-12px, font-mono tool names, 15px icons, hover bg - ToolPermissionGroup: rounded badge for count, outline 'Custom ▾' batch button - ConnectorDetail: restore 'Tool permissions' h3 + subtitle Connectors/Skills tab switcher: - Top of left panel: Connectors tab | Skills tab - Connectors: builtin tools + OAuth connectors + community/custom MCPs - Skills: builtin agent skills + community/user agent skills - Switching tabs resets selection and auto-selects first item in new view - + button only shown in Connectors view SkillList: add viewMode='connector'|'skill' prop with filtered section display Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(skill): active permission state + Lobehub OAuth skill tools sync ToolPermissionRow: - btnActive: use primary color + primaryBg background (clearly visible selected state) connector router: - Add syncToolsFromClient: accepts client-provided tool list for skills that already have their tool list fetched (Lobehub OAuth skills, etc.) Store action: - Add syncToolsFromClient action SkillDetail: - Add 'lobehub-connector' ToolDetailType - For lobehub-connector: reads server.tools from lobehubSkillStore (already populated after OAuth connect) and syncs via syncToolsFromClient — no remote MCP call needed SkillList: - Pass type='lobehub-connector' for Lobehub OAuth items (was 'plugin', wrong path) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ♻️ refactor(connector): replace 'Tool permissions' header with connector description Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): show disabled tools in settings UI (only filter at runtime) connectorToolsGrouped: remove permission !== disabled filter — all tools should be visible in ConnectorDetail so users can re-enable them. Disabled filtering already happens at runtime in buildConnectorManifests and queryByConnectorIds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(skill): section lowercase, 4-group tools, remove tags in list mode SkillList: remove text-transform: uppercase from sectionHeader ConnectorDetail: split tools into 4 groups — Read / Create / Update / Delete (maps to crudType: read / write / update / delete) connectorToolsGrouped selector: return { readTools, createTools, updateTools, deleteTools } All item components: remove SkillSourceTag in list mode (onSelect provided) — tags are redundant when section headers already provide categorization Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): add Reset permissions button — restore all tools to auto connector router: resetPermissions endpoint — sets all connector's tools to 'auto' store: resetConnectorPermissions action ConnectorDetail: - Add 'Reset permissions' button — resets ALL tools back to auto (fully open) - Rename 'Reset'/'Refresh' button to 'Refresh' — clarifies it syncs tool list only - Two separate concerns: Refresh (tool list) vs Reset permissions (all → auto) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): use excluded.* in onConflictDoUpdate to ensure crudType updates + add description to tool rows connectorTool.ts: - Use sql`excluded.crud_type` etc. instead of table.column refs in onConflictDoUpdate - table.column in set generates self-reference (no-op) in some Drizzle versions - Now correctly updates crudType when Refresh is clicked (read/update/delete groups will show correctly) ToolPermissionRow: - Add description below tool name: 11px, tertiary color, single-line truncate with ellipsis - Tooltip shows full description on hover (mouseEnterDelay: 0.5s) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): createStaticStyles returns static object not hook in ConnectorItem Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🗑️ chore(settings): remove /settings/connector route — Connectors are in /settings/skill - Remove src/routes/(main)/settings/connector/index.tsx - Remove SettingsTabs.Connector from enum and componentMap - Remove Connectors item from settings sidebar useCategory - Remove Connector from full-width list in SettingsContent - Remove unused LinkIcon import from useCategory ChatSettingsTabs.Connector (agent panel) is separate and unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): disabled tools stay in manifest with blocking description + hard-block at callTool buildConnectorManifests: - Disabled tools are now INCLUDED in the manifest (not excluded) - Description replaced with: '[TOOL DISABLED] The user has disabled this tool and it cannot be executed...' - humanIntervention: 'required' set for disabled tools so AI is explicitly warned - AI can inform user the tool is disabled instead of silently not knowing it exists mcp.callTool: - Pre-call permission gate: query ConnectorModel + ConnectorToolModel by connector identifier - If tool.permission === 'disabled': return immediately with "disabled by user" message - MCP server is never called — the block is enforced server-side regardless of what AI attempts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): add permission gate to klavis.callTool for disabled tools Gmail (and other Klavis-sourced connectors) use tools.klavis.callTool, not tools.mcp.callTool, so the previous MCP permission gate didn't apply. Fix: Add serverDatabase to klavisProcedure, extract connector identifier from toolName prefix, query user_connector_tools, hard-block if permission=disabled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🗑️ chore(skill): hide + button (custom MCP connector creation — OAuth flow TBD) Remove AddConnectorModal entry point from /settings/skill header. Custom HTTP MCP connectors require OAuth (Pre-registration / DCR) which is not yet fully implemented. Will be re-added in a future PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): only replace plugins with connectors that have a real MCP endpoint Root cause: Lobehub/Klavis OAuth skills are synced into user_connectors via syncToolsFromClient with mcpServerUrl=null. buildConnectorManifests generates mcpParams={url:''} for them. After humanIntervention approval, the runtime calls tools.mcp.callTool({url:''}) → fails silently → empty result. Fix: only use connectorsMcp (connectors with mcpServerUrl or stdio config) to replace installedPlugins and build connector manifests. Connectors without a real MCP endpoint (Lobehub/Klavis) fall back to their original plugin executor path, preserving the Klavis callTool execution chain and fixing needs_approval flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(connector): centralized tool permission enforcement across all execution paths connectorPermissionCheck.ts (new shared utility): - getConnectorToolPermission(): look up permission by identifier + toolName - buildBlockedToolResponse(): standardized "disabled by user" response - patchManifestWithPermissions(): patch manifest api[] with DB permissions ToolExecutionService.executeTool() — centralized disabled gate: - Queries DB at execution entry for ALL tool types (Lobehub skills, Klavis, MCP connectors, builtin plugins, and qstash/execAgent async path) - Hard-blocks 'disabled' tools before any executor runs - needs_approval handled by manifest humanIntervention (not blocked here) aiAgent/index.ts — manifest patching for Lobehub/Klavis: - After fetching lobehubSkillManifests + klavisManifests, query connector tools - Patch manifests: needs_approval → humanIntervention:'required' (pauses for approval) - Patch manifests: disabled → blocking description (AI informed, executor blocks) - humanIntervention system already handles headless auto-reject for qstash Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): invokeBuiltinTool falls back to store lookup when payload.source is undefined Root cause: when a tool call is re-invoked after humanIntervention approval, the payload comes from the DB-stored message which does NOT persist the `source` field. `internal_transformToolCalls` sets source correctly but it only runs for LLM-generated tool calls, not for the approval re-invocation path. Fix: in `invokeBuiltinTool`, if `payload.source` is undefined, do a live lookup from the tool store (klavisAsLobeTools / lobehubSkillAsLobeTools) to determine the correct executor. Applies to Klavis (Gmail, etc) and LobeHub Skills alike. Also: remove all temporary [DEBUG] console.log statements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🔨 chore: fix TypeScript errors and test failures after canary rebase - buildConnectorManifests: LobeToolManifest → ToolManifest (correct export name) - connectorPermissionCheck: cast permission string to ConnectorToolPermission - connector.ts model: guard encryptCredentials against null credentials - ConnectorDetail: String() cast for unknown metadata.description - AddConnectorModal: move loading to Modal.confirmLoading (correct prop) - connector/action.ts: break circular ToolStore type reference with Pick<Impl> - execAgent.disableTools.test.ts: mock ConnectorModel/ConnectorToolModel DB deps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(connector): P1/P3 fixes + test mock coverage after code review P1 — real MCP disabled tools now appear in manifest: - ConnectorToolModel.queryAllByConnectorIds: new method without disabled filter - aiAgent.ts: uses queryAllByConnectorIds for manifest building so buildConnectorManifests receives ALL tools (including disabled) and can emit blocking descriptions - queryByConnectorIds (non-disabled filter) retained for runtime hot-path P1 — Klavis gate works for hyphenated identifiers (google-calendar, etc): - klavis.ts: replace split('_')[0] prefix hack with direct findByToolName DB lookup - ConnectorToolModel.findByToolName: query user_connector_tools by userId + toolName P3 — queryByConnector adds userId filter: - Prevents leaking tool metadata to wrong user if connector UUID is known Tests — mock ConnectorModel/ConnectorToolModel in all execAgent test files: - execAgent.builtinRuntime.test.ts - execAgent.deviceToolPipeline.test.ts - execAgent.disableTools.test.ts (queryAllByConnectorIds added to mock) TypeScript — ConnectorDetail metadata.description: - Use typeof === 'string' type guard to narrow unknown → string for JSX render Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🔨 fix(connector): precise Klavis permission gate + update stale disabled comments Klavis gate — identifier + toolName (precise, no same-name collision risk): - CallKlavisToolParams: add identifier? field - klavisExecutor: pass identifier to callKlavisTool - callKlavisTool store action: thread identifier through to tRPC mutate - klavis.callTool router: accept optional identifier in input schema - Permission gate: when identifier present, do queryByIdentifiers + queryByConnector + find by toolName for a precise 2-field lookup; fall back to findByToolName for legacy callers without identifier Comments updated to reflect current disabled behavior: - buildConnectorManifests.ts: disabled → injected with blocking description - connector.ts schema: same correction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
485d664589 | 💬 style: rebrand platform agent copy to Connect Agent (#15498) | ||
|
|
b1ada9e5fc |
🐛 fix(conversation): hide Usage extra for local hetero agents until model arrives (#15501)
Local CLI hetero agents (claude-code, codex) only report `model` after turn_metadata lands mid-stream. The previous `showUsage` check used the broad `HETEROGENEOUS_TYPE_LABELS` lookup which matches both local and remote types, so it returned true with an empty model. Usage then fell through to the `ModelIcon` path (Usage uses the narrower `isRemoteHeterogeneousType` for the brand-label branch) and rendered a lone empty-model placeholder icon under the message. Align the gate with Usage's internal branching: only bypass `!!model` for remote hetero (openclaw, hermes) which never expose a real model id. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |