[pull] canary from lobehub:canary#363
Merged
Merged
Conversation
…s registry (#15850) ♻️ refactor(swr): converge remaining store-layer keys into swrKeys registry Migrate all ad-hoc SWR keys still living in the store/service layer onto the central registry (src/libs/swr/keys.ts), under the uniform `domain:resource` naming. New domains: discover, eval (agent eval), ragEval, knowledgeBase, device (incl. git), userMemory, agentKnowledge, agentBot, file, chatTool. - Pure key convergence: no tiering/caching change. The new prefixes are kept deliberately OUT of CACHE_TIERS, so every migrated key stays memory-only exactly as before (agentKnowledge:/agentBot: avoid the cached `agent:` tier). - Behavior preserved: key array shapes, mutate matchers (key[0] === *.root), and personal-vs-workspace match semantics are unchanged; string-join keys (discover assistant/social) become arrays with equivalent identity. - UI-embedded SWR keys (features/routes/components/packages) intentionally left for a later pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nalize (#15849) The subagent run coordinator keys thread creation purely on the in-memory `runs` map. On a cold serverless replica / BatchIngester retry the map is empty, and `refreshSubagentRunsFromDb` only rehydrates `Processing` isolation threads — a spawn that already finalized (thread flipped `Active`) is excluded. So a replayed first-event for a finished subagent hits the `!existing` branch of `ensureRun` and forks a SECOND thread with the identical title ("一模一样的两个 thread"). Sibling of #15838 (main-turn) / #15808 (subagent-turn), but for the thread-create step. Fix: give thread creation a DB-homed, status-independent idempotency guard keyed by `sourceToolCallId`. - `SubagentRunsState` gains `finalizedParents: Set<string>`; `finalizeRun` records the parent there (instead of just deleting the run), so `ensureRun` returns a no-op for a replayed finished spawn — no duplicate thread or message. - `refreshSubagentRunsFromDb` seeds `finalizedParents` from this operation's `Active` isolation threads (without resurrecting them as live runs, which would mint empty assistants / re-finalize churn). Regression: subagent reducer unit test (finalize → replay first event → 0 intents) + handler cold-replica test (finished subagent replay → still 1 thread). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ool/global/userMemory) (#15853) ♻️ refactor(swr): converge remaining discover/tool/global/userMemory store keys Completes the store-layer SWR key convergence into the central registry (batch3 only partially covered discover). Migrates the remaining ~39 ad-hoc keys: - discover: model/plugin/provider/skill/mcp/groupAgent list+detail+categories and user profile (the `.join('-')` string keys → registry array factories). - tool: agentSkills, installedPlugins, builtin uninstalled-tools, lobehubSkill store, mcpPluginList, klavis store. (The dynamic `plugins`-array key is left as-is — it's data-derived, not a named key.) - global: latest/server version, system status. - userMemory: retrieve / memoryDetail / activities / contexts / experiences / identityList / preferences. The `purgeAllMemories` invalidation was rewritten from `startsWith('useFetch…')` string matchers to array `key[0] === *.root` matchers, in lockstep with the fetch keys. No tiering/caching change: all new prefixes (discover/tool/global/userMemory) are kept out of CACHE_TIERS, so everything stays memory-only as before. Behavior preserved (key identity, mutate match sets, personal-vs-workspace). UI-layer keys + the cross-layer `cronTopicsWithJobInfo` remain for the next PR. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ng back to unknown (#15831) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…issed wakeups (#15855) * 🐛 fix(agent-runtime): harden async sub-agent suspend/resume against missed wakeups The server callSubAgent async park/resume chain (#15481) had a one-shot, no-retry recovery: a single transient miss left the parent stuck in waiting_for_async_tool forever. Harden the resume barrier and watchdog (LOBE-10385 parts 1-3, 5; the park-side deadline fallback follows separately): - Read-your-writes barrier: completeSubAgentBridge passes the just-backfilled toolMessageId to the barrier, which trusts that local write instead of re-reading message_plugins from a possibly-stale read replica. - Bounded backoff watchdog: verifyAsyncToolBarrier now re-arms with exponential backoff (15s→30s→60s→120s→240s, 5 attempts) until the barrier passes or the op is terminal, replacing the single 15s shot that never re-armed. - Plug silent bails: !state and pending.length===0 now warn + emit a metric; the empty-pending case also arms a fallback verify for snapshot-persist lag. - Observability: new agent_runtime_async_tool_resume_total counter keyed by outcome (resumed/barrier_held/no_pending/no_state/lost_cas/verify_exhausted) so missed wakeups surface instead of accumulating silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(hetero): reconstruct queued upload files from filesPreview on run continuation When continuing a heterogeneous agent run with remaining queued messages, rebuild the upload file items from filesPreview metadata instead of passing bare { id } stubs, so file context (name/type/preview) survives the continuation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#15461) * ✨ feat(composio): add Composio integration layer as Klavis replacement - Add @composio/core SDK client factory (src/libs/composio) - Add COMPOSIO_API_KEY server config + enableComposio flag - Add COMPOSIO_APP_TYPES const with 21 curated apps (appSlug-based) - Add lambda/composio tRPC router (createConnection, deleteConnection, getConnection, updateComposioPlugin) - Add tools/composio tRPC router (executeAction, listActions, getActions) - Add ComposioService with executeComposioTool + getComposioManifests - Add composioStore Zustand slice (7 files: types, initialState, action, selectors, index, test) - Wire composioStore into ToolStore state and action tree - Add composioStoreSelectors to tool selectors index - Add handleComposioInstall to AgentManagerRuntime - Extend CustomPluginParams with composio field - Add enableComposio to GlobalServerConfig types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🔥 refactor(klavis): remove Klavis integration and migrate all references to Composio - Delete all Klavis source files (libs, config, const, routers, services, store, UI components) - Rename KlavisX components to ComposioX equivalents - Replace all Klavis store selectors, types, and action names with Composio counterparts - Fix authConfigId to be server-side managed (auto-fetch/create from Composio API) - Update DB customParams.klavis → customParams.composio throughout - Fix ToolSource type: 'klavis' → 'composio' - Fix TaskTemplateSkillSource: 'klavis' → 'composio' - Fix RecommendedSkillType.Klavis → RecommendedSkillType.Composio - Remove klavis npm package dependency - Update builtin-tool-creds: connectKlavisService → connectComposioService - Update RuntimeExecutors: KLAVIS_SERVICES_LIST → COMPOSIO_SERVICES_LIST - All Composio-related type errors: 0 remaining Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * 🐛 fix(composio): complete the klavis→composio migration and wire the OAuth callback The composio branch had renamed the klavis modules but left consumers half-migrated, so the OAuth connect link did not work end-to-end. Finish it: - Add the missing OAuth callback route `/api/composio/oauth/callback` (Composio uses managed auth, so it only lands the user back and closes the popup; the opener then polls getConnection and syncs tools). Allowlist it as a public cross-site redirect landing in the proxy define-config. - Remove leftover `import { type Klavis } from 'composio'` (non-existent package) and type the prop as `string`. - Fix undefined `oauthUrl` → `redirectUrl` in every OAuth popup opener. - Map `serverName` to `appSlug` (API) / `label` (display); unify every createComposioConnection call to `{ appSlug, identifier, label }`. - Compare against the `ComposioServerStatus` enum instead of the `'ACTIVE'` string literal. - Use the renamed store fields `composioServers` / `isComposioServersInit`. - executeComposioTool: `toolName` → `toolSlug`. - Rename onboarding `KlavisServerItem.tsx` → `ComposioServerItem.tsx` to match its import. * 🐛 fix(composio): use connectedAccounts.link for Composio-managed OAuth `connectedAccounts.initiate` is no longer supported for Composio-managed OAuth auth configs (HTTP 400), which broke connecting apps like Gmail. Switch to `connectedAccounts.link` (POST /api/v3/connected_accounts/link) — same `{ callbackUrl }` options and `{ id, redirectUrl }` result, so it is a drop-in. Also treat Composio's `status=failed` callback query param as a failed authorization in the OAuth callback page. * 🐛 fix(composio): correct tool sync, execution, callback build, and list dedup Four fixes found while testing the Composio integration end-to-end: - listActions: use `getRawComposioTools` (raw defs with slug/inputParameters) instead of `tools.get()` (provider-wrapped, name/params under `.function`). The wrapped shape left every synced tool with an empty name, so they all collapsed to `${identifier}____` and the LLM rejected the request with "Tool names must be unique." - tools.execute: pass `dangerouslySkipVersionCheck: true` (manual execution otherwise throws ComposioToolVersionRequiredError when the toolkit version resolves to "latest"). Applied to both the executeAction router and the ComposioService used by the agent runtime. - OAuth callback route: escape only `<`/`>`/`&` for the inline-script payload; the previous regex embedded literal U+2028/U+2029 line separators which broke the regex literal at build time ("Unterminated regular expression"). - installed-plugin selectors: filter out `customParams.composio` (was still checking the old `customParams.klavis`), so a connected Composio app no longer shows up twice in the skill picker / tool discovery list. * ✨ feat(composio): pin auth config id per toolkit via env Add `COMPOSIO_AUTH_CONFIG_IDS` (JSON map of `identifier -> authConfigId`) so a pre-created Composio auth config (e.g. a custom/white-label OAuth app set up in the dashboard) can be used directly per toolkit. `createConnection` now resolves the pinned auth config first, then falls back to discovering an existing one for the toolkit (matched case-insensitively), and only auto-creates a Composio-managed config when nothing is configured. * 🐛 fix(composio): update plugin invoke test to composio + sort tool initialState imports - action.test.ts: the action was renamed invokeKlavisTypePlugin → invokeComposioTypePlugin (Klavis is being removed); update the test to call the composio action and drop the klavis-era naming/mock field. - store/tool/initialState.ts: order the composioStore import before connector to satisfy simple-import-sort/imports. * 🐛 fix(composio): stop client deleting remote connections by static allowlist useFetchUserComposioConnections no longer deletes remote connections/plugins for identifiers outside the compile-time COMPOSIO_APP_TYPES list — an outdated client bundle would silently destroy a legitimate connection. Unknown identifiers are now only hidden locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(composio): resolve connectedAccountId server-side in executeAction executeAction now takes `identifier` and looks up the connectedAccountId from the caller's own user-scoped plugin record (PluginModel), instead of trusting a connectedAccountId supplied by the client — which would let a user drive another user's connection. Callers (callComposioTool, composioExecutor) pass identifier accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(composio): enable plugin only after OAuth succeeds Move enablePluginForAgent into the ACTIVE and post-auth-success branches so a cancelled/timed-out authorization no longer leaves an enabled-but-unauthorized Composio tool on the agent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🔥 fix(composio): drop dead OAuth callback postMessage The lobe-composio-oauth postMessage had no consumer — the OAuth wait uses polling + window.closed detection. Remove it and its escaping helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(composio): resolve type-check errors after canary merge - Guard authConfigId to a definite string before persisting/returning it (createConnection), fixing the string|undefined assignment in both the server router and the composio store server object. - Replace leftover KLAVIS_SERVER_TYPES with COMPOSIO_APP_TYPES in AgentTool. - Update SkillAuthRow test to a composio source/provider (klavis is removed from TaskTemplateSkillSource). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ♻️ refactor(composio): remove leftover klavis naming after migration Klavis is deprecated and fully replaced by Composio. The migration kept the underlying composio wiring but left klavis-named identifiers, comments, prompt tags, i18n keys, and files throughout. Sweep them to composio: - Code identifiers/comments across ~70 files (isKlavisEnabled→isComposioEnabled, allKlavisServers→allComposioServers, klavisManifests→composioManifests, etc.) - LLM prompt tags (<klavis_tools>→<composio_tools>, KLAVIS_SERVICES_LIST→ COMPOSIO_SERVICES_LIST) — kept consistent across definition and substitution - i18n keys tools.klavis.*→tools.composio.* + user-facing "Klavis"→"Composio" brand strings, in default setting.ts and all locale setting.json files - Rename useKlavisOAuth→useComposioOAuth, useKlavisServerActions→ useComposioServerActions (+ imports) - klavis.ai homepage URLs → composio.dev - Remove the dead `klavis` npm peerDependency; swap .env.example Klavis section for Composio; update product docs Changelog history left untouched. Pure rename — no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(composio): remove duplicate composio key in CustomPluginParams The klavis→composio rename collapsed the deprecated klavis param block onto the live composio one, producing a duplicate `composio` property. The klavis shape (instanceId/serverName/serverUrl/isAuthenticated) is dead — no code reads it — so drop it and keep the live composio shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(composio): let pending/errored connections re-authorize or be deleted A Composio connection link (lk_...) expires after a while. Previously a pending/errored row only offered to reopen the stored — now expired — redirectUrl, and the delete action existed only for ACTIVE connections, so an expired link left the tool permanently stuck: unauthenticatable and unremovable. - Add reauthorizeComposioConnection store action: best-effort delete the stale connection, then mint a fresh link (replaces the record in place) - Settings skill item + chat toolbar item: PENDING/ERROR now render a ··· menu with Re-authorize (fresh link) and Delete - Onboarding: pending/errored row click re-mints a fresh link instead of reopening the stale one - i18n: add tools.composio.reauthorize (en-US + zh-CN) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(composio): return auth link instead of opening popup from agent connectComposioService runs from the agent's response, which carries no user gesture, so window.open was blocked by the browser and the flow always failed with "Authorization was cancelled or timed out". Instead of opening the popup ourselves, return the authorization redirectUrl in the tool result so the agent can surface a clickable link — the user's click is a real gesture and completes the OAuth normally. Drops the now-unused popup/poll helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 💄 fix(composio): match pending toolbar item to sibling authorize affordance The ··· dropdown I added to the chat-toolbar Composio item was a bare icon (inconsistent color/size with the app's standard menus), its popup was mis-anchored/offset, and replacing the visible "authorize" cue with a ··· made an un-authorized (pending) row look connected. Match the sibling LobehubSkillServerItem instead: render a clickable "Re-authorize" text + external-link icon for PENDING/ERROR. Clicking re-mints a fresh link (the prior one may have expired) and opens it. No dropdown, so no offset; the explicit affordance makes it clear the row still needs auth. Delete stays on the settings page (siblings have no inline delete here either). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…15858) Completes the SWR key convergence by migrating the remaining UI-layer ad-hoc keys (features / routes / components) into the central registry. New domains: stats, messenger, verify, inbox, share, fork, portal, favorite, changelog, onboarding, agentHome, agentProfile, agentSignal, ollama, auth, cron, topicAction — plus extensions to discover (mcpAgents/skillAgents/market), device (gitBranches/repoType), session (createSession), group (queryAgents*). - Shared keys (availablePlatforms, agentsForBinding, bindingScopes, shared-topic, favorite-status, openNewTopicOrSaveTopic, portal-document-header, inbox notifications/unread) are routed through one factory at every call site so they still dedupe to a single cache entry. - The notifications useSWRInfinite getKey and the userMemory-style matcher invalidations were migrated in lockstep with their fetch keys. - No tiering/caching change: every new prefix is kept out of CACHE_TIERS, and names avoid the cached prefixes (share:/portal:/agentHome:/agentProfile: etc. instead of topic:/document:/agent:). Behavior preserved. - Folds in the lone cross-layer `cronTopicsWithJobInfo` store mutate. Packages (builtin-tool *) keep their local keys — they can't import from `@/libs/swr/keys`; left as-is intentionally. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…efetch key (#15863) * ♻️ refactor(swr): converge the last straggler SWR keys + fix stale prefetch key Final cleanup of the SWR key convergence. Migrates the remaining ad-hoc keys that earlier grep-based sweeps missed (they hid behind non-obvious const names like SWR_KEY / FETCH_*_KEY / SWR_RESOURCES, template-literal keys, the electron store, and assorted one-off hooks): - hooks: usePrefetchAgent, useHomeDailyBrief, useGatewayReconnect - features: OpenInAppButton, Recommendations/useHeteroDetections, RecommendTaskTemplates, ResourceManager search - routes: provider ClientMode + DisabledModels (useSWRInfinite), memory analysis task, sidebar task groups, imessage bridge status, Review git patches - store: user initState + checkTrace, builtin agent init, file resources, electron settings/gateway/sync New registry domains: home, taskTemplate, resource, provider, recommendations, openInApp, gateway, user, builtinAgent, imessage, sidebar, electron — plus extensions to aiModel (disabledModelsPage), device (gitReviewPatches / gitRemoteBranches), userMemory (analysisTask). 🐛 Fix: usePrefetchAgent warmed `['FETCH_AGENT_CONFIG', agentId]`, which never matched what `useFetchAgentConfig` reads. It now warms `augmentKey(agentConfigKeys.config(agentId), getActiveWorkspaceId())` — the exact workspace-scoped key the consumer subscribes to, so hover-prefetch actually populates the cache. No tiering/caching change: every new prefix is kept out of CACHE_TIERS (names avoid the cached agent:/task:/brief: tiers). The electron factory roots retain their original `electron:getXxx` strings, so those cache identities are unchanged. After this, the only ad-hoc SWR keys left are in `packages/*` (can't import `@/libs/swr/keys`); every `src/` SWR call site now routes through the registry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(swr): drop suspense: true from data-fetching hooks Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ test(swr): update refreshUserState assertion to registry key Follow-up to the prior commit: the auth-slice test still expected mutate('initUserState'); refreshUserState now passes userKeys.initState() (['user:initState']). Assert against the factory so it tracks the registry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…agent (#15862) Switching to or opening an agent tab could flash the conversation header/welcome back to the inbox "Lobe AI" identity. Two causes: - `useFetchAgentConfig.onData` set the global `activeAgentId` to whatever config resolved, so a background/secondary fetch (the inbox config from the home input, a side-panel copilot, or another open tab) hijacked the routed agent. It now only adopts the fetched agent when none is active; route-level sync (AgentIdSync on desktop/mobile, the popup pages' own setState) owns `activeAgentId`. - `AgentInfo` (the agent conversation welcome) read the global `currentAgentMeta` / `isInboxAgent`. Scope it to the conversation's agent via `useConversationStore(contextSelectors.agentId)` + `*ById` selectors, so it renders the routed agent even if the global races. Also remove the dead `Conversation/AgentWelcome/{index,OpeningQuestions}` (the conversation welcome is `AgentHome`/`AgentInfo`; this variant was unreferenced). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…w source (#15867) 🐛 fix(updater): render release notes as Markdown instead of raw source The update modal injected release notes via dangerouslySetInnerHTML, but the content is a Markdown source string (e.g. `## Canary Build`, GFM tables), so headings/tables/bold were shown literally as raw text. Render it with @lobehub/ui's <Markdown> component instead. Also handle the `ReleaseNoteInfo[]` shape of `releaseNotes` by rendering each note. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…text (#15865) * 🐛 fix(chat): preserve subAgentId/documentId in message bucket key context `replaceMessages` and `internal_getConversationContext` rebuilt the conversation context with a hand-picked field whitelist, silently dropping `subAgentId` (and others). Since `messageMapKey` uses `subAgentId` as the group_agent scope subTopicId, group-agent writes collapsed into the wrong bucket. Spread the whole context instead and only special-case the fields that need a fallback/assertion (agentId, topicId), so every bucket-key field carries through. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ test(database): deterministic ordering in topic.duplicate test Both seed messages were inserted in one transaction with no explicit createdAt, so they shared the same `now()` default. `duplicate`'s `orderBy(createdAt)` then returned the tied rows in arbitrary order, making the positional assertions flaky. Give them distinct createdAt (user before assistant) so the order is well-defined. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )