ๆ–‡ไปถๆœ€ๅŽๆไบค่ฎฐๅฝ•ๆœ€ๅŽๆ›ดๆ–ฐๆ—ถ้—ด
โœจ feat(platform-agent): openclaw/hermes agent creation UI, device guard, and remote dispatch backend (#15065) * โ™ป๏ธ refactor(agent-invocation): add AgentInvocationIntent + unified non-hetero dispatcher (LOBE-8927/8928) Introduce a shared invocation contract and unified dispatcher for the non-hetero, non-group agent call paths (callAgent speak mode and @agent direct mentions). Removes the implicit client-only fallback that existed in both entry points. Changes: - agentDispatcher.ts: add AgentInvocationIntent interface as the unified intent type for callSubAgent / callAgent / @agent invocations - nonHeteroSubAgentDispatcher.ts (new): dispatchNonHeteroSubAgent() resolves child runtime via selectRuntimeType and routes to executeClientAgent (client) or executeGatewayAgent (gateway); throws for hetero (out of scope per LOBE-8926) - conversationLifecycle.ts #executeDirectMentionRoute: replace hardcoded executeClientAgent + TODO fallback with dispatchNonHeteroSubAgent call - builtin-tool-agent-management executor.ts callAgent speak mode: replace hardcoded executeClientAgent + TODO fallback with dispatchNonHeteroSubAgent call Fixes LOBE-8927 Fixes LOBE-8928 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœจ feat(platform-agent): openclaw/hermes agent creation UI, device guard, and remote dispatch backend - Add CreatePlatformAgent 3-step creation modal (type select โ†’ config โ†’ bind device) - Add RemoteAgentConfigCard to agent profile editor for openclaw/hermes config - Add device guard banner in HeterogeneousChatInput for offline/unavailable devices - Add useRemoteAgentDeviceGuard hook for real-time device status polling - Fix backend dispatch: openclaw/hermes now use executeToolCall(runHeteroTask) instead of dispatchAgentRun (lh connect only handles tool_call_request) - Add agentNotify router for lh notify โ†’ DB write + gateway stream event - Add device.checkCapability endpoint for platform availability probe - Add notify_update event type to gateway stream and event handler - Add sendDoneSignal in heteroTask.ts for clean openclaw exit signaling - Unify non-hetero sub-agent dispatch via dispatchNonHeteroSubAgent (LOBE-8927) - Route openclaw/hermes to gateway runtime; keep claude-code/codex on hetero/client paths - Add i18n keys for platform agent UI and device guard banners Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ› fix(agentNotify): reuse execAgent placeholder message on first lh notify call Instead of creating a second empty bubble, the first assistant notify without a messageId now updates the placeholder assistantMessageId that execAgent already seeded in runningOperation.assistantMessageId. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœจ feat(agentNotify): cancel openclaw/hermes process on interruptTask - Store deviceId + heteroType in topic.metadata.runningOperation at dispatch time - interruptTask now dispatches cancelHeteroTask tool call to the bound device when topicId reveals a remote hetero operation, sending SIGINT to the process - Pass topicId from gateway cancel callback to interruptTask - Add topicId to InterruptTaskSchema and InterruptTaskParams Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โ™ป๏ธ refactor(hetero-agent): consolidate remote/local type classification into heterogeneous-agents package - Add RemoteHeterogeneousAgentConfig, REMOTE_HETEROGENEOUS_AGENT_CONFIGS, isRemoteHeterogeneousType, and derived type aliases (HeterogeneousAgentType, LocalHeterogeneousAgentType, RemoteHeterogeneousAgentType) to packages/heterogeneous-agents/src/config.ts - Extend HETEROGENEOUS_TYPE_LABELS to cover remote platform types (openclaw, hermes) via REMOTE_HETEROGENEOUS_AGENT_CONFIGS - Replace all inline `=== 'openclaw' || === 'hermes'` checks and local Sets/type aliases across aiAgent service, ProfileEditor, HeterogeneousChatInput, useRemoteAgentDeviceGuard, CreatePlatformAgent, RemoteAgentConfigCard, and deviceProxy with the shared utility - Show OpenClaw/Hermes display name in assistant message model tag (Usage component) by setting provider=heteroType on placeholder message and using HETEROGENEOUS_TYPE_LABELS for rendering - Fix ReferenceError: move remoteDeviceId declaration before updateMetadata call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add the platform agents get profiles * ๐Ÿ› fix(platform-agent): routing, security, and i18n issues from review - Route openclaw/hermes to gateway on desktop (P1): add isRemoteHeterogeneousType check in selectRuntimeType before desktop hetero branch โ€” remote agents never use local desktop IPC, no special-casing needed - Fix race in heteroTask: sendAutoNotify โ†’ sendDoneSignal now sequential via .finally() so error message is written before agent_runtime_end is published - Security: validate messageId belongs to topicId in agentNotify before MessageModel.update to prevent cross-conversation data corruption - Clear capability/device/profile state on platform change in creation modal (P2) - Derive PLATFORM_DEFS from REMOTE_HETEROGENEOUS_AGENT_CONFIGS โ€” new platforms automatically appear in the modal without code changes - Use HETEROGENEOUS_TYPE_LABELS for platform names in HeterogeneousChatInput and RemoteAgentConfigCard (remove hardcoded PLATFORM_NAMES map) - i18n: platform card descs, 'online'/'offline' tags, 'Select a device' placeholder, checkFailed error โ€” all now use i18n keys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โ™ป๏ธ refactor(platform-agent): derive remote platform enum from config + fix test - device.ts: replace hardcoded z.enum(['hermes','openclaw']) with a zod enum derived from REMOTE_HETEROGENEOUS_AGENT_CONFIGS so new platforms are automatically covered without touching this file - heteroTask.ts / getAgentProfile.ts: use RemoteHeterogeneousAgentType instead of literal 'hermes' | 'openclaw' union for the same reason - gateway.test.ts: update cancel-handler assertion to include topicId which was added to the interruptTask call in the previous commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœจ feat(platform-agent): gate creation entry behind labs flag + expand dispatcher tests - Add enablePlatformAgent lab preference (default false) โ€” the "Add Platform Agent" menu item is hidden until the user opts in via Settings โ†’ Advanced โ†’ Labs - Wire toggle in settings/advanced with labs i18n key (en/zh) - createPlatformAgentMenuItem returns null when flag is off - agentDispatcher.test: add remote hetero cases (openclaw/hermes โ†’ gateway on both web and desktop) to cover the routing fix added earlier Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ› fix(lint): merge duplicate import + sort interface props in nonHeteroSubAgentDispatcher Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ’„ feat(platform-agent): disable Hermes option in creation modal (coming soon) Hermes is not yet ready for production. Mark it as coming-soon in the platform selection step: grayed-out card, not clickable, "Coming Soon" tag next to the name. To enable Hermes when ready: remove 'hermes' from COMING_SOON_PLATFORMS in CreatePlatformAgent/index.tsx. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœ… fix(test): mock CreatePlatformAgentModal in ModalProvider.test The modal always mounts (open=false) and calls lambdaQuery.useQuery which requires a tRPC context not present in the test environment. Mock it out the same way as ChatGroupWizard and EditingPopover. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœ… fix(test): mock useUserStore + labPreferSelectors in useCreateMenuItems.test Adding useUserStore to useCreateMenuItems triggered user store initialization in tests, which pulled in @lobechat/const and failed because the existing mock only exports isDesktop. Mock the store and selectors directly instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ› fix(platform-agent): hide divider when platform agent entry is disabled The divider before 'Add Platform Agent' was unconditional โ€” it showed even when the labs flag was off. Conditionally include both the divider and the menu item together so no orphaned separator appears. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 ๅคฉๅ‰
๐Ÿ› fix(skill): skip OAuth redirectUri on desktop to prevent broken app (#14345) ๐Ÿ› fix(skill): skip OAuth redirectUri on desktop to prevent broken app:// navigation On desktop (Electron), window.location.origin is app://renderer which the system browser cannot navigate to. Skip passing redirectUri so market shows a default success page instead, relying on existing window-close monitoring and fallback polling to detect OAuth completion. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>22 ๅคฉๅ‰
โ™ป๏ธ refactor(builtin-tool): move sub-agent dispatch from `lobe-gtd` to `lobe-agent` (#14715) * โ™ป๏ธ refactor(builtin-tool): move sub-agent dispatch from lobe-gtd to lobe-agent Move the `execTask` / `execTasks` capability out of `packages/builtin-tool-gtd/` and into `packages/builtin-tool-lobe-agent/`, renaming the public APIs to `callSubAgent` / `callSubAgents`. The "subtask" naming inside GTD overlapped with the new lobe-task tool's task model and conflated planning with sub-agent dispatch. - API names: `execTask` โ†’ `callSubAgent`, `execTasks` โ†’ `callSubAgents` - TS types: `ExecTaskParams` โ†’ `CallSubAgentParams`, etc.; introduce `SubAgentTask` to replace `ExecTaskItem` - Client UI (Inspector / Render / Streaming) ported under `packages/builtin-tool-lobe-agent/src/client/` - Central registries (`packages/builtin-tools/src/{inspectors,renders,streamings}.ts`) updated to register lobe-agent - GTD `meta.description` and system role no longer mention async tasks; they point to lobe-agent for sub-agent dispatch - `isSubTask` filtering in `agentConfigResolver` now excludes `lobe-agent` (new owner of sub-agent dispatch) instead of `lobe-gtd` - i18n: new `builtins.lobe-agent.apiName.callSubAgent*` and `workflow.toolDisplayName.callSubAgent*` keys in default/zh-CN/en-US Kept the executor's emitted `state.type` values (`execTask` / `execTasks` / `execClientTask` / `execClientTasks`) unchanged so the agent-runtime instruction layer (`exec_task` / `exec_tasks` / `exec_client_task*`) and all downstream tests / heterogeneous executors (`builtin-tool-agent-management`, server `agentManagement` runtime) continue to work without modification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(chat): rename isSubTask flag to isSubAgent After moving sub-agent dispatch from lobe-gtd to lobe-agent, the flag name no longer matches what it controls. Rename `isSubTask` โ†’ `isSubAgent` across the chat / agent runtime layer and update related comments and test labels. - `agentConfigResolver` context field + filter helper - `streamingExecutor.internal_createAgentState` + `executeClientAgent` signatures and call sites - `createAgentExecutors` (exec_task / exec_client_task handlers) and `GroupOrchestrationExecutors` (batch_exec_async_tasks) - `chatService.createAssistantMessageStream` `resolvedAgentConfig` docs - Test descriptions and assertions in `agentConfigResolver.test.ts` and `streamingExecutor.test.ts` No behavior change โ€” the flag's filter target (`lobe-agent` identifier) is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(agent-runtime): rename exec_task wire identifiers to exec_sub_agent Bring the agent-runtime "wire" naming in line with the lobe-agent callSubAgent / callSubAgents API rename. Three layers are renamed in lockstep to keep the bridge between tool executors and the runtime consistent: 1. Tool-emitted state.type discriminators - 'execTask' โ†’ 'execSubAgent' - 'execTasks' โ†’ 'execSubAgents' - 'execClientTask' โ†’ 'execClientSubAgent' - 'execClientTasks' โ†’ 'execClientSubAgents' 2. AgentInstruction.type and matching TS interfaces - 'exec_task' / 'exec_tasks' / 'exec_client_task' / 'exec_client_tasks' โ†’ 'exec_sub_agent' / 'exec_sub_agents' / 'exec_client_sub_agent' / 'exec_client_sub_agents' - AgentInstructionExecTask โ†’ AgentInstructionExecSubAgent (and the three siblings) - ExecTaskItem โ†’ SubAgentTask 3. AgentRuntimeContext.phase + matching payload types - 'task_result' โ†’ 'sub_agent_result' - 'tasks_batch_result' โ†’ 'sub_agents_batch_result' - TaskResultPayload โ†’ SubAgentResultPayload - TasksBatchResultPayload โ†’ SubAgentsBatchResultPayload Also renames the operation-type discriminator 'execClientTask' / 'execClientTasks' to 'execClientSubAgent' / 'execClientSubAgents' and updates its locale string in default / zh-CN / en-US. Tests / fixtures / mocks updated in lockstep: - packages/agent-runtime/src/agents/{GeneralChatAgent.ts,__tests__/...} - packages/builtin-tool-{lobe-agent,agent-management}/src/... - src/server/services/toolExecution/serverRuntimes/agentManagement.ts - packages/agent-mock/src/cases/builtins/todo-write-stress.ts (helper renamed to callSubAgent) - src/store/chat/agents/createAgentExecutors.ts + exec-task / exec-tasks tests + fixtures/mockInstructions.ts (createExecSubAgent[s]Instruction) - src/store/chat/slices/aiChat/actions/streamingExecutor.ts (phase check) - packages/conversation-flow/src/__tests__/fixtures/**/*.json (8 fixtures retargeted from lobe-gtd/execTask[s] to lobe-agent/callSubAgent[s] with the new state.type wire values) No behavior change โ€” the agent runtime, executors and tests all go through the same code paths; only the strings on the wire change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(builtin-tool): absorb GTD tool (plan + todo) into lobe-agent Delete `packages/builtin-tool-gtd/` and fold its full surface โ€” plan, todo, ExecutionRuntime, all client UI (Inspector / Render / Streaming / Intervention / SortableTodoList) and the system role โ€” into `packages/builtin-tool-lobe-agent/`. Single `lobe-agent` identifier now owns: plan + todo management, sub-agent dispatch, and visual media analysis. Also restructures the lobe-agent package so the executor lives under `./client/` alongside the UI it ships with, and drops the dedicated `./executor` export โ€” consumers go through `./client` for everything client-side. Package-level changes: - DELETE `packages/builtin-tool-gtd/` entirely. - `packages/builtin-tool-lobe-agent/` - Move `src/executor/` โ†’ `src/client/executor/`. Drop `./executor` from `package.json` exports; expose `lobeAgentExecutor` via `./client` only. - Rename `GTDExecutionRuntime` โ†’ `PlanExecutionRuntime` and place under `src/client/executor/PlanRuntime/`. Re-export from package root so the server runtime can consume it without pulling in client UI deps. - Extend `LobeAgentExecutor` with `createPlan` / `updatePlan` / `createTodos` / `updateTodos` / `clearTodos`, all delegated to the shared runtime. - Add Plan + Todo API entries to the manifest (with their original descriptions, humanIntervention, renderDisplayControl). - Move all GTD client UI verbatim: `Inspector/{ClearTodos,CreatePlan,CreateTodos,UpdatePlan,UpdateTodos}`, `Render/{CreatePlan,TodoList}`, `Streaming/CreatePlan`, `Intervention/{AddTodo,ClearTodos,CreatePlan}`, `components/SortableTodoList`. Register them in `LobeAgentInspectors / Renders / Streamings`, add new `LobeAgentInterventions`. - Merge GTD system role into lobe-agent's (`<plan_and_todos>` plus the existing `<sub_agents>` and `<run_in_client>` sections). - `package.json`: pick up `@lobechat/prompts` dep and `@lobehub/editor` + `antd` + `lucide-react` peer-deps inherited from GTD. Central registries (`packages/builtin-tools/src/*`) and consumers: - Remove every `GTDManifest / Inspectors / Renders / Streamings / Interventions` import + registration; existing `LobeAgent*` registrations now cover them. - Replace `[GTDManifest.identifier]: GTDInterventions` with `[LobeAgentManifest.identifier]: LobeAgentInterventions`. - Drop `@lobechat/builtin-tool-gtd` workspace dep from `packages/builtin-tools/package.json`, `packages/builtin-agents/package.json` and root `package.json`. - Remove `gtdExecutor` from `src/store/tool/slices/builtin/executors/index.ts`; switch `lobeAgentExecutor` import to `/client`. - Replace `serverRuntimes/gtd.ts` with a service factory `serverRuntimes/lobeAgentPlan.ts` (`createServerPlanRuntimeService`). `serverRuntimes/lobeAgent.ts` instantiates `PlanExecutionRuntime` with that service so the registry exposes one runtime per `lobe-agent` identifier covering both visual analysis and plan/todo. - `services/chat/mecha/contextEngineering.ts`: gate plan/todo injection on `LobeAgentIdentifier` instead of `GTDIdentifier`. - `agentConfigResolver.test.ts`: switch fixture plugin IDs to `LobeAgentIdentifier`. - `packages/const/src/recommendedSkill.ts`: drop the standalone `lobe-gtd` recommendation โ€” `lobe-agent` already covers it via `defaultToolIds`. i18n migration (default + zh-CN + en-US; other locales regenerate on `pnpm i18n`): - `builtins.lobe-gtd.*` โ†’ `builtins.lobe-agent.*` in `plugin.ts/json`. - `lobe-gtd.*` (tool namespace) โ†’ `lobe-agent.*` in `tool.ts/json`. - Remove `tools.builtins.lobe-gtd.{description,readme,title}` from `setting.ts/json` (lobe-agent has its own meta now). - Update all client component `t(...)` keys to the new namespace. Mocks / fixtures / tests: - `packages/agent-mock/src/cases/builtins/todo-write-stress.ts`: all `identifier: 'lobe-gtd'` โ†’ `'lobe-agent'`; helper comments updated. - `packages/types/src/stepContext.ts`: comment refers to `builtin-tool-lobe-agent` (the only consumer of `StepContextTodoItem`). - `packages/model-runtime/src/core/streams/google/google-ai.test.ts`: function-call names from `lobe-gtd____createPlan` etc. โ†’ `lobe-agent____*`. - `src/store/chat/slices/message/selectors/dbMessage.test.ts`: same. - `src/features/DevPanel/RenderGallery/fixtures/lobe-gtd.ts` deleted; its plan/todo fixtures are folded into `fixtures/lobe-agent.ts` alongside the existing `callSubAgent[s]` ones. - Replace `console.log` โ†’ `console.info` in moved client components to satisfy lobe-agent's stricter ESLint rules (GTD package allowed `console.log`; lobe-agent inherits the repo-wide `no-console` rule). No behavior change for end users: `lobe-agent` now owns all the APIs, identifiers, and UI that previously lived in `lobe-gtd`, but as a single consolidated package under a single tool identifier. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(context-engine): drop residual GTD naming, rename to PlanInjector / TodoInjector Follow-up to 9ca5c9d (which absorbed the GTD tool package into lobe-agent). That commit moved the package surface but left the GTD vocabulary embedded in context-engine providers, types, metadata fields, XML tags, and a pile of comments. This change finishes the sweep so the only remaining GTD references are user-facing docs and the legitimate Productivity & GTD Coach methodology suggestion. context-engine - `GTDPlanInjector` โ†’ `PlanInjector`; types `GTDPlan`/`GTDPlanInjectorConfig` โ†’ `Plan`/`PlanInjectorConfig`; metadata `gtdPlanId`/`gtdPlanInjected` โ†’ `planId`/`planInjected`; XML tag `<gtd_plan>` โ†’ `<plan>`; debug channel `provider:GTDPlanInjector` โ†’ `provider:PlanInjector`. - `GTDTodoInjector` โ†’ `TodoInjector`; types `GTDTodoItem`/`GTDTodoList`/ `GTDTodoStatus`/`GTDTodoInjectorConfig` โ†’ `TodoItem`/`TodoList`/ `TodoStatus`/`TodoInjectorConfig`; metadata `gtdTodo*` โ†’ `todo*`; XML tag `<gtd_todos>` โ†’ `<todos>`, wrapper `gtd_todo_context` โ†’ `todo_context`; debug channel renamed similarly. - `MessagesEngineParams.gtd?: GTDConfig` โ†’ `planTodo?: PlanTodoConfig`; internal vars `isGTDPlanEnabled`/`isGTDTodoEnabled` โ†’ `isPlanEnabled`/`isTodoEnabled`. Re-exports updated in `providers/index.ts` and `engine/messages/{index,types}.ts`. prompts - `packages/prompts/src/prompts/gtd/` โ†’ `planTodo/` (only export was `formatTodoStateSummary`, which kept its name). Updated `prompts/index.ts` re-export. src/services - `contextEngineering.ts`: `GTDConfig` import โ†’ `PlanTodoConfig`; `isGTDEnabled`/`gtdConfig` โ†’ `isPlanTodoEnabled`/`planTodoConfig`; payload field `gtd` โ†’ `planTodo`; log message wording. Tests - `dbMessage.test.ts`: helper `createGTDToolMessage` โ†’ `createLobeAgentToolMessage`; `gtdMessage` โ†’ `lobeAgentMessage`; all `it` descriptions reworded to "lobe-agent" instead of "GTD". - `agentConfigResolver.test.ts`: test descriptions reworded. Comments / docs (no behavior change) - agent-runtime (`instruction.ts`, `runtime.ts`, `generalAgent.ts`, `messageSelectors.ts`), `types/{stepContext,tool/builtin}.ts`, `builtin-agents/group-supervisor`, `builtin-tool-claude-code/types.ts`, `builtin-tool-lobe-agent/Render/TodoList`, `createAgentExecutors.ts:1426`, `AssistantGroup/{constants,Fallback.test}`, `agent-mock/todo-write-stress`, `.agents/skills/builtin-tool/references/architecture.md`. Intentionally left alone - `docs/usage/agent/gtd.{mdx,zh-CN.mdx}` and other docs โ€” user-facing product brand "GTD Tools". - `src/locales/default/suggestQuestions.ts` "Productivity & GTD Coach" โ€” references the methodology, not the tool. - `ToolSystemRoleProvider.test.ts` `'gtd-tool'` fixture โ€” generic test identifier, unrelated. - Translated locale files still carrying `lobe-gtd.*` keys โ€” regenerated by `pnpm i18n` from the updated default namespace. Verified: `bun run type-check` passes; touched test files (dbMessage, agentConfigResolver) and full context-engine + prompts test suites pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(builtin-tool-lobe-agent): reset TodoList auto-save status to idle `performSave` (the debounced auto-save path) was leaving `saveStatus` stuck on 'saved' forever โ€” `saveNow` had the 1.5s setTimeout-to-idle but the auto-save twin didn't, so the inline indicator never eased back to idle after a settle. Add the same idle-reset to performSave so both paths behave the same. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>11 ๅคฉๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
๐Ÿ› fix(agent-signal,app): anchor agent signal receipts to messages (#14969)4 ๅคฉๅ‰
๐Ÿ› fix: allow templates to specify policyLoad so default docs are fully injected (#13672) * ๐Ÿ› fix: allow templates to specify policyLoad so default docs are fully injected All documents were hardcoded to PolicyLoad.PROGRESSIVE on creation, causing CLAW template docs (IDENTITY, SOUL, BOOTSTRAP, AGENTS) to be progressively disclosed instead of fully injected into context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix: forward policyLoad through upsertDocument and persist on update - Add policyLoad to UpsertDocumentParams and pass it through to model - Add policyLoad param to update() so upsert's existing-document path writes the value instead of silently discarding it - Ensures re-running template init migrates pre-existing docs to ALWAYS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: change update() to use named params object instead of positional args Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: change create() and upsert() to use named params object Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โœ… test: improve agentDocuments test coverage to 99% Add tests for uncovered branches: - normalizeLoadRule default branch (unknown rule) - explicit 'always' rule match - by-time-range with NaN dates - resolveDocumentLoadPosition fallback paths - composeToolPolicyUpdate with existing context values - upsert create path for new filenames - getAgentContext empty docs path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix: preserve policyLoad when copying documents Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โœ… fix: align test assertion with refactored create() params object signature Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 ไธชๆœˆๅ‰
โœจ chore(agent-tracing): resolve partial op id by _remote/ cache prefix (#15015) * โœจ feat(agent-tracing): resolve partial op id by _remote/ cache prefix `agent-tracing inspect op_<timestamp>` used to fail with "Snapshot not found" because the CLI only accepted the full `op_<ts>_agt_..._tpc_..._<suffix>` id. Now when the input starts with `op_` but isn't a full id, scan the local `_remote/` cache and resolve a unique prefix match automatically; on multiple matches, list them and exit so the user can pick the full id. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(agent-tracing): preserve FileSnapshotStore fallback for op_ prefixes The previous commit routed partial `op_<timestamp>` ids straight at the `_remote/` cache, bypassing `FileSnapshotStore.get(...)`. That meant in-progress local `_partial/` snapshots (which `FileSnapshotStore.get` finds via substring match through `getPartial`) were no longer reachable by prefix; users hit `Snapshot not found` even when the partial existed on disk. Try the file store first, then fall back to the remote cache prefix scan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>2 ๅคฉๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
๐Ÿ› fix: sanitize sensitive comments and examples from production JS bundle (#14557) * ๐Ÿ› fix: sanitize sensitive comments and examples from production JS bundle - Replace app.example.com with RFC 2606 example.com in agent-browser skill content - Replace password-stdin examples with interactive auth prompts - Remove hardcoded password-like strings from code examples - Reword flagged code comments in page-agent system role Addresses TAC Security CASA Tier 2 DAST Info findings: Information Disclosure - Suspicious Comments (CWE-615) The flagged strings appeared in SPA production bundles: - /_spa/assets/chat-*.js - /_spa/assets/index-*.js * ๐Ÿ› fix: revert --interactive to --password-stdin in auth vault examples The --interactive flag does not exist in agent-browser CLI (only --password and --password-stdin are supported). Using --interactive would cause auth save to fail and block login workflows. Reverted both auth vault examples to use echo | --password-stdin pattern, which pipes the password via stdin โ€” the recommended secure approach.14 ๅคฉๅ‰
โœจ feat(skills): recognize project-level skills in the homogeneous agent runtime (#15110)1 ๅคฉๅ‰
โ™ป๏ธ refactor(agent-settings): consolidate Chat tab into Params popover, drop dead auto-topic feature (#14885) * ๐Ÿ”ฅ chore(agent-config): drop dead enableAutoCreateTopic feature Drop enableAutoCreateTopic + autoCreateTopicThreshold end-to-end. No business code consumed these fields anymore โ€” only types, defaults, locale copy, UI form items, agent-builder LLM prompts, and test fixtures kept the dead config alive. Sweep: - types & zod schema (LobeAgentChatConfig, AgentChatConfigSchema, openapi) - DEFAULT_AGENT_CHAT_CONFIG constant - locale keys in default + 18 translations - agent-builder system prompts & tool manifests - AgentChat form items (auto-topic switch + threshold slider) - test fixtures & integration tests (replaced sample boolean key in parser tests with enableHistoryCount) - docs/self-hosting env-var examples - settings.test snapshot dataImporter JSON fixtures keep the legacy keys on purpose โ€” they simulate historical user exports and the zod schema strips unknowns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœจ feat(chat-input): move inputTemplate + autoScroll into Params popover Surface the User Input Preprocessing template (inputTemplate) and Auto-scroll During AI Response toggle (enableAutoScrollOnStreaming) in the chat-input Params popover, alongside compression / history / max_tokens. Drop the matching form items from AgentChat โ€” the popover is now the single entry point for these two agent-level preferences. ControlRow's action prop becomes optional so inputTemplate can render as a label + TextArea without a Switch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ”ฅ refactor(agent-settings): drop AgentChat tab in favor of Params popover Remove the now-redundant Chat Preferences tab from agent settings: - delete src/features/AgentSetting/AgentChat/ - drop ChatSettingsTabs.Chat enum and its three registrations (useCategory, AgentSettingsContent, profile Content) - drop agentTab.chat locale key in default + 18 translations - drop MessagesSquare / MessagesSquareIcon imports that became unused History/compression/auto-scroll/inputTemplate already live in the chat-input Params popover, so this tab carried no unique functionality. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœจ feat(chat-input): surface enableStreaming + reasoning_effort + disabledParams in Params popover Bring the Model tab's controls into the chat-input Params popover so the popover can become the single entry point for agent-level params. - enableStreaming Switch at the top of Advanced (treats undefined as on, matching `chatConfig.enableStreaming !== false` in chat service) - reasoning_effort row after max_tokens (Select tied to chatConfig.enableReasoningEffort / params.reasoning_effort, matching the agentConfigResolver gating) - per-model disabledParams filter on the 4 sampling sliders (e.g. Claude Opus 4.7 hides temperature/top_p), via aiModelSelectors.modelDisabledParams - max_tokens defaults to 4096 on toggle-on (parity with AgentModal), matching the AgentModal UX - drop the !enableAgentMode gate on Advanced so agent-mode users still reach the model params once the Model tab is gone Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ”ฅ refactor(agent-settings): drop AgentModal tab in favor of Params popover Now that the chat-input Params popover surfaces enableStreaming, reasoning_effort, the 4 sampling params (model-aware via disabledParams), and max_tokens, the Model Settings tab carries no unique behavior. Remove it: - delete src/features/AgentSetting/AgentModal/ (index + ModelSelect) - drop ChatSettingsTabs.Modal enum and its three registrations (useCategory, AgentSettingsContent, profile Content) - drop agentTab.modal locale key in default + 18 translations - drop BrainCog / BrainIcon imports that became unused - simplify the profile Content inbox-default fallback to Opening (Content menu no longer carried Modal at all) settingModel.* locale keys are kept โ€” Controls still reads them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(chat-input): keep !enableAgentMode gate on Advanced sampling params Walk back the gate removal from the prior commit. Agent mode is meant to manage temperature / top_p / penalties / reasoning_effort itself; exposing user overrides there contradicts the design. - Move enableStreaming out of Advanced into the common section so it stays visible in both modes (streaming is a UI behavior, not a sampling param). - Re-wrap the SectionHeader + sampling sliders + max_tokens + reasoning_effort with `{!enableAgentMode && (...)}`, restoring the prior visibility rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>4 ๅคฉๅ‰
๐Ÿ› fix(agent-signal): route hinted skill documents (#14895)6 ๅคฉๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
๐ŸŒ chore: translate non-English comments to English in brief-types and conversation (#14410)18 ๅคฉๅ‰
โฌ†๏ธ chore(deps): migrate @lobehub/ui to base-ui exports (#12587) * โฌ†๏ธ chore(deps): migrate @lobehub/ui to base-ui exports - Migrate LobeSelect โ†’ Select from @lobehub/ui/base-ui - Migrate LobeSwitch โ†’ Switch from @lobehub/ui/base-ui - Fix DropdownItem import (use main package instead of internal path) - Add initialWidth, popupWidth support to ModelSelect Made-with: Cursor * โฌ†๏ธ chore(deps): update @lobehub packages to latest versions - Upgrade @lobehub/charts to ^5.0.0 - Upgrade @lobehub/editor to ^4.0.0 - Upgrade @lobehub/icons to ^5.0.0 - Upgrade @lobehub/market-sdk to ^0.31.1 - Upgrade @lobehub/tts to ^5.0.0 - Upgrade @lobehub/ui to ^5.0.0 across multiple packages - Update peer dependencies for various packages to align with new @lobehub/ui version Made-with: Cursor Signed-off-by: Innei <tukon479@gmail.com> * โฌ†๏ธ chore(deps): update @lobehub/tts to version 5.1.2 Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>2 ไธชๆœˆๅ‰
โœจ feat(review-panel): group review changes by submodule (#15148) * ๐Ÿ› fix(claude-code): show task subject in TaskUpdate inspector & header A TaskUpdate that only sets `subject` (no status flip) was falling through to the aggregate `Todos: x/y` chip and burying the per-call signal. Surface the new subject like the status branch already does: "Task updated: <subject>". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(review-panel): group changes by submodule with per-group collapse Surface dirty submodules as their own groups in the agent Review panel so users working in a parent repo with submodules see each repo's changes clustered together (mirrors WebStorm's per-repo commit grouping). Both Unstaged and Branch modes apply the same grouping โ€” submodules with internal working-tree changes (unstaged) or branch diffs against their own origin/HEAD (branch) surface as separate groups, each tagged with its own branch label and file/diff totals. Backend (`GitCtr`): - `getGitWorkingTreePatches` and `getGitBranchDiff` extracted into private recursive helpers that detect submodules via `git submodule status`, partition pointer-bump entries out of the parent's flat patches, and recurse one level for each dirty submodule's own patches + branch info. - Nested submodules are not traversed (phase 1); revert routes through each group's absolute path so submodule files revert inside the submodule. Renderer: - New `GroupHeader` and `FileRow` subcomponents split out of `Review`. `GroupHeader` is sticky with a chevron + name + file count + diff totals + branch; clicking collapses the group's rows. A hover-revealed `ActionIcon` on the right expands/collapses all file diffs in that group (`e.stopPropagation` keeps it from also collapsing the surrounding header). - Fixed `block-size: 32px` on the header so toggling the fold button on/off doesn't jitter the sticky height. - Single-repo working trees keep the previous flat layout when no submodule groups exist. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(review-panel): scan all submodules in branch mode Previously branch mode only surfaced a submodule group when the parent's diff against base ref contained a `Subproject commit` pointer bump for it. This missed the common case where the user has committed work in a submodule on a feature branch but the parent's pointer hasn't yet moved relative to its base โ€” the submodule's own branch differences stayed invisible in the Review panel. `collectBranchDiff` now recurses into every registered submodule (single level, in parallel) and keeps a group when EITHER its pointer differs in the parent OR its own branch diverges from its own origin/HEAD. Clean-on- both-axes submodules are dropped so the panel stays quiet for repos where the submodule isn't actively being worked on. Submodule count is small in practice (single digits), so the extra per-submodule fetch + diff in parallel is an acceptable cost. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(agent-documents): hide .tool-results archive from user-facing lists Auto-created tool-result archive folder and its children are now filtered out of getAgentDocuments. Agents still discover them via the tool-oriented listDocuments paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ’„ style(review-panel): drop "file not found in project index" toast Reveal-in-tree now silently no-ops when the path isn't indexed (e.g. submodule files) instead of nagging the user with a warning toast. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(review-panel): keep submodule groups visible on pointer-only bumps `isEmpty` was derived solely from `totalEntryCount`, which counts file patches across groups. A pointer-only submodule bump (parent patch filtered out, submodule group present but internally clean) produced `totalEntryCount === 0`, so the panel rendered the global empty state and silently skipped the submoduleClean group rendering โ€” even though git was dirty. Now `isEmpty` also requires zero submodule groups, so pointer-only bumps keep their GroupHeader + "submodule clean" line. The fold-all button visibility switches to `totalEntryCount > 0` so it stays hidden when there's nothing foldable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>11 ๅฐๆ—ถๅ‰
โ™ป๏ธ refactor(local-system,cloud-sandbox): drop "Local" prefix from tool names (#14364) * โ™ป๏ธ refactor(local-system,cloud-sandbox): drop "Local" prefix from tool names LLM-facing tool names dropped the redundant "Local" / "LocalFiles" prefix to shrink manifest/system-prompt token footprint: editLocalFileโ†’editFile, globLocalFilesโ†’globFiles, listLocalFilesโ†’listFiles, moveLocalFilesโ†’moveFiles, readLocalFileโ†’readFile, searchLocalFilesโ†’searchFiles, writeLocalFileโ†’writeFile. Also removed `renameLocalFile` entirely from the new surface โ€” `moveFiles` already covers in-place renames by changing only the filename in newPath. Old long names are still recognised in the rendering path (client Render/Inspector/Intervention/Streaming registries, placeholders, workflow display labels, i18n keys) and in Gateway/CLI routing, so historical messages and older Gateway versions keep working. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(local-system): reuse LocalSystemApiName / LocalSystemIdentifier exports Drop the inline LOCAL_SYSTEM_IDENTIFIER / READ_FILE / LIST_FILES consts in the snapshot materializer and import the canonical values from the package. Mark LocalSystemApiName `as const` (matching CloudSandboxApiName) so values narrow to literal types and satisfy LocalSystemToolSnapshot.apiName. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>19 ๅคฉๅ‰
โ™ป๏ธ refactor(creds): remove getPlaintextCred tool to prevent plaintext credential exposure (#14998) * refactor(creds): remove getPlaintextCred tool to prevent plaintext credential exposure * refactor(creds): remove getPlaintextCred tool to prevent plaintext credential exposure * refactor(creds): remove getPlaintextCred tool to prevent plaintext credential exposure * refactor(creds): remove getPlaintextCred tool to prevent plaintext credential exposure * refactor(builtin-tool-creds): remove getPlaintextCred from ExecutionRuntime and ICredsService * refactor(builtin-tool-creds): remove getPlaintextCred from systemRole prompt and local_integration section * fix(builtin-tool-creds): escape backticks in systemRole template literal3 ๅคฉๅ‰
โ™ป๏ธ refactor(agent-settings): consolidate Chat tab into Params popover, drop dead auto-topic feature (#14885) * ๐Ÿ”ฅ chore(agent-config): drop dead enableAutoCreateTopic feature Drop enableAutoCreateTopic + autoCreateTopicThreshold end-to-end. No business code consumed these fields anymore โ€” only types, defaults, locale copy, UI form items, agent-builder LLM prompts, and test fixtures kept the dead config alive. Sweep: - types & zod schema (LobeAgentChatConfig, AgentChatConfigSchema, openapi) - DEFAULT_AGENT_CHAT_CONFIG constant - locale keys in default + 18 translations - agent-builder system prompts & tool manifests - AgentChat form items (auto-topic switch + threshold slider) - test fixtures & integration tests (replaced sample boolean key in parser tests with enableHistoryCount) - docs/self-hosting env-var examples - settings.test snapshot dataImporter JSON fixtures keep the legacy keys on purpose โ€” they simulate historical user exports and the zod schema strips unknowns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœจ feat(chat-input): move inputTemplate + autoScroll into Params popover Surface the User Input Preprocessing template (inputTemplate) and Auto-scroll During AI Response toggle (enableAutoScrollOnStreaming) in the chat-input Params popover, alongside compression / history / max_tokens. Drop the matching form items from AgentChat โ€” the popover is now the single entry point for these two agent-level preferences. ControlRow's action prop becomes optional so inputTemplate can render as a label + TextArea without a Switch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ”ฅ refactor(agent-settings): drop AgentChat tab in favor of Params popover Remove the now-redundant Chat Preferences tab from agent settings: - delete src/features/AgentSetting/AgentChat/ - drop ChatSettingsTabs.Chat enum and its three registrations (useCategory, AgentSettingsContent, profile Content) - drop agentTab.chat locale key in default + 18 translations - drop MessagesSquare / MessagesSquareIcon imports that became unused History/compression/auto-scroll/inputTemplate already live in the chat-input Params popover, so this tab carried no unique functionality. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœจ feat(chat-input): surface enableStreaming + reasoning_effort + disabledParams in Params popover Bring the Model tab's controls into the chat-input Params popover so the popover can become the single entry point for agent-level params. - enableStreaming Switch at the top of Advanced (treats undefined as on, matching `chatConfig.enableStreaming !== false` in chat service) - reasoning_effort row after max_tokens (Select tied to chatConfig.enableReasoningEffort / params.reasoning_effort, matching the agentConfigResolver gating) - per-model disabledParams filter on the 4 sampling sliders (e.g. Claude Opus 4.7 hides temperature/top_p), via aiModelSelectors.modelDisabledParams - max_tokens defaults to 4096 on toggle-on (parity with AgentModal), matching the AgentModal UX - drop the !enableAgentMode gate on Advanced so agent-mode users still reach the model params once the Model tab is gone Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ”ฅ refactor(agent-settings): drop AgentModal tab in favor of Params popover Now that the chat-input Params popover surfaces enableStreaming, reasoning_effort, the 4 sampling params (model-aware via disabledParams), and max_tokens, the Model Settings tab carries no unique behavior. Remove it: - delete src/features/AgentSetting/AgentModal/ (index + ModelSelect) - drop ChatSettingsTabs.Modal enum and its three registrations (useCategory, AgentSettingsContent, profile Content) - drop agentTab.modal locale key in default + 18 translations - drop BrainCog / BrainIcon imports that became unused - simplify the profile Content inbox-default fallback to Opening (Content menu no longer carried Modal at all) settingModel.* locale keys are kept โ€” Controls still reads them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(chat-input): keep !enableAgentMode gate on Advanced sampling params Walk back the gate removal from the prior commit. Agent mode is meant to manage temperature / top_p / penalties / reasoning_effort itself; exposing user overrides there contradicts the design. - Move enableStreaming out of Advanced into the common section so it stays visible in both modes (streaming is a UI behavior, not a sampling param). - Re-wrap the SectionHeader + sampling sliders + max_tokens + reasoning_effort with `{!enableAgentMode && (...)}`, restoring the prior visibility rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>4 ๅคฉๅ‰
๐Ÿ’„ style: batch fix eslint violations across packages (#12601) 2 ไธชๆœˆๅ‰
โ™ป๏ธ refactor(knowledge-base): share RAG runtime across client/server via KnowledgeBaseSearchService (#14673) * โ™ป๏ธ refactor(knowledge-base): share runtime across client/server via KnowledgeBaseSearchService Extract a server-side `KnowledgeBaseSearchService` (semanticSearchForChat fan-out + getFileContents branching + groupAndRankFiles) so both the lambda chunk router and the builtin tool server runtime orchestrate RAG through one implementation. Wire the builtin knowledge-base tool to the shared ExecutionRuntime in the package by moving the client executor to `src/client/executor/` and registering a thin server runtime factory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(knowledge-base): move PG 23505 handling into adapters, restore executor path ExecutionRuntime is dual-end so it cannot detect PG error codes โ€” only the server adapter can. Move the unique-constraint check there and translate the lambda router's `FILE_ALREADY_IN_KNOWLEDGE_BASE` sentinel in the client adapter, so the runtime's generic catch surfaces the human-readable message on both code paths. Restore `src/executor/` as a top-level sibling of `src/client/` to match the convention of every other builtin tool. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(knowledge-base): collapse executor into /client, drop ./executor export The executor is just another client-only adapter (alongside Inspector and Render) โ€” no reason for it to sit at the package root with a dedicated subpath. Move it under `src/client/executor/`, re-export from `src/client/index.ts`, drop the `./executor` entry from package.json, and update the consumer to import from `@lobechat/builtin-tool-knowledge-base/client`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœ… test(knowledge-base): cover KnowledgeBaseSearchService 13 unit tests across both methods: - getFileContents: docs_* direct read, missing doc, file_* via findByFileId, parseFile fallback, parse failure surfaces as error entry, missing file, mixed batch. - semanticSearchForChat: chunk grouping + relevance ranking, BM25 skip when no knowledgeIds, knowledgeIds โ†’ fileIds expansion, vector/BM25 isolated failure capture (preserves the other path's results + structured rejections), full failure path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>12 ๅคฉๅ‰
๐Ÿ› fix: discourage redundant visual tool calls (#15025) ๐Ÿ› fix: discourage redundant visual analysis tool calls3 ๅคฉๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
โœจ feat(follow-up): extend follow-up chip suggestions to general chat (#15101) * โœจ feat(follow-up): add foundation types for chat follow-up chips - FollowUpExtractInput.threadId for portal thread isolation - UserSystemAgentConfig.followUpAction (global enable + model) - LobeAgentChatConfig.enableFollowUpChips (per-agent opt-in) - ConversationHooks.onAssistantTurnSettled first-class member - Remove dead onGenerationStart/Complete/Cancelled hooks - DEFAULT_SYSTEM_AGENT_CONFIG.followUpAction off by default - DEFAULT_AGENT_CHAT_CONFIG.enableFollowUpChips false default * โ™ป๏ธ refactor(follow-up): key follow-up store by conversation for concurrency - Convert useFollowUpActionStore from single-slot to slots map - conversationKey = messageMapKey(agentId, topicId, threadId?) for parity with chat store - contextSelectors.conversationKey exposes the key from ConversationProvider - FollowUpChips and ChatItem consume conversationKey - Onboarding hook adopts the new keyed API - Pass threadId through to extract (server filter lands in T3) * ๐Ÿ› fix(follow-up): address T2 code review feedback - Restore design-intent comments for 20s timeout and race guard - Remove dead pendingMessageId field from FollowUpActionSlot - Remove unused slotFor selector - Trim chipsFor / FollowUpActionSlot JSDoc to design intent only - Gate useOnboardingFollowUp against missing onboardingAgentId - removeSlot uses destructure; slotStatus uses ?? for falsy safety * โœจ feat(follow-up): filter extract by threadId for portal thread isolation - FollowUpActionService.extract honours optional threadId - threadId provided โ†’ eq(messages.threadId, threadId) - threadId absent โ†’ isNull(messages.threadId) so main topic never surfaces thread replies - Tests cover both branches * โœจ feat(conversation): emit onAssistantTurnSettled hook from provider - AssistantTurnSettledWatcher fires hooks.onAssistantTurnSettled(messageId, { reason }) once per turn - Reason derived from the most recent terminal Operation for the message id - Reason mapping: cancelled โ†’ stopped, type=regenerate โ†’ regenerated, type=continue โ†’ continued, else โ†’ completed - Settlement gated on idle + no pending tool intervention (mirrors Onboarding's logic) - Tests cover all four reason branches + intervention gating + no double-fire + fallback log - Onboarding bespoke prop untouched (migrates in T6) * ๐Ÿ› fix(conversation): scope settlement reason to turn-level operations - TURN_LEVEL_TYPES filter excludes child sub-ops (callLLM, executeToolCall, etc.) before sorting by endTime - Prevents successful regenerate/continue being misreported as 'completed' when a child finishes after the parent - Tests cover parent/child ordering for all reason branches * โœจ feat(follow-up): add useChatFollowUp hook and wire chat mount sites - New mergeConversationHooks composes multiple hooks with boolean short-circuit - useChatFollowUp computes effective enable (global ร— per-agent ร— valid model) - Registers onBeforeSendMessage/Continue/Regenerate to clear slot and onAssistantTurnSettled to extract - Mount sites: agent route ConversationArea, FloatingChatPanel, Portal Thread Chat (last in chain per ยง4.6) - Skips on reason='stopped'; skips when effective is false - Group chat intentionally not mounted * โ™ป๏ธ refactor(onboarding): migrate settlement to ConversationHooks first-class - Drop bespoke onAssistantTurnSettled prop and duplicate useEffect from AgentOnboardingConversation - useOnboardingFollowUp returns ConversationHooks { onBeforeSendMessage, onAssistantTurnSettled } - Split settlement work: context-sync + builtin refresh runs first, chip extract runs after - Phase snapshot captured at memoize time preserves original prevPhase semantics - Settlement detection now lives solely in AssistantTurnSettledWatcher * โœจ feat(settings): add Follow-up suggestions controls (global + per-agent) - Global System Agent page: new Follow-up Suggestions panel (model picker + enable toggle) - Per-agent chat controls: enableFollowUpChips toggle with hint when global not configured - i18n keys: setting.systemAgent.followUpAction.*, setting.settingChat.enableFollowUpChips.* - Hint surfaces when user toggles per-agent ON but global is disabled/unmodeled * ๐Ÿ”ง chore(follow-up): T8 โ€” scoped lint cleanup and comment discipline pass * ๐Ÿ› fix(follow-up): align conversationKey selector with callsite + wrap single hook - contextSelectors.conversationKey forwards full context (scope/isNew/groupId/subAgentId) so portal-thread NEW state matches callsite-computed keys - ConversationArea wraps chat-follow-up via mergeConversationHooks for spec ยง4.6 ordering robustness - Both per final-review Important concerns * โœ… test(settings): update follow-up defaults snapshots * โœจ feat(follow-up): surface model in service-model page + default to mini - Add followUpAction to /service-model OPTIONAL_FEATURE_ITEMS so model/provider and enable Switch render alongside inputCompletion and promptRewrite - Seed DEFAULT_FOLLOW_UP_ACTION_SYSTEM_AGENT_ITEM with DEFAULT_MINI model/provider so out-of-box config has a valid model; users only need to flip enabled - Sync settings selector snapshot1 ๅคฉๅ‰
โœจ feat: support bot attachments across all platforms (#15029) * feat: support bot attachments across all platforms Squashed from feat/support-bot-attachments (15 commits): - Wechat adapter attachment support (image/video/voice/file via iLink CDN) - All-platform attachments: Discord, Telegram, Slack, Feishu/Lark, LINE, QQ - Messager + CLI sendMessage/sendDirectMessage/replyToThread attachment params - System Bot messenger installs as outbound channels + listOutboundChannels - Onboarding messager integration + feedback commands - AI-side attachment ingestion across platforms - Updated builtin-tool-message systemRole / manifest / types * chore: unify client and runtime adapter * feat: support system bot messenger and cli * chore: remove unnecessary listOutboundChannels * chore: add test and prompts3 ๅคฉๅ‰
โ™ป๏ธ refactor(builtin-tools): retire lobe-tools alias and slim lobe-notebook to render-only (#14422) * โ™ป๏ธ refactor(builtin-tools): retire lobe-tools alias and slim lobe-notebook to render-only - Drop the deprecated `'lobe-tools'` identifier alias from the inspector / render registries plus its backward-compat checks in dbMessage selectors and the dev RenderGallery fixtures. - Hoist the only surviving notebook UI (the `createDocument` document card) into `packages/builtin-tools/src/notebook/`, mirroring the github tool layout. Marked the new module `@deprecated` with a ~3-month removal target. - Delete `packages/builtin-tool-notebook/src/client/` entirely and unregister notebook from the inspectors / interventions / placeholders / streamings registries (it can no longer be invoked by the LLM, so those surfaces are dead code). Manifest / executor / ExecutionRuntime stay so legacy tool calls keep resolving. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ”ง chore(builtin-tools): drop redundant antd peer dep antd is already provided by the workspace and peered through @lobehub/ui, so listing it explicitly on builtin-tools is noise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>19 ๅคฉๅ‰
๐Ÿ› fix: sanitize sensitive comments and examples from production JS bundle (#14557) * ๐Ÿ› fix: sanitize sensitive comments and examples from production JS bundle - Replace app.example.com with RFC 2606 example.com in agent-browser skill content - Replace password-stdin examples with interactive auth prompts - Remove hardcoded password-like strings from code examples - Reword flagged code comments in page-agent system role Addresses TAC Security CASA Tier 2 DAST Info findings: Information Disclosure - Suspicious Comments (CWE-615) The flagged strings appeared in SPA production bundles: - /_spa/assets/chat-*.js - /_spa/assets/index-*.js * ๐Ÿ› fix: revert --interactive to --password-stdin in auth vault examples The --interactive flag does not exist in agent-browser CLI (only --password and --password-stdin are supported). Using --interactive would cause auth save to fail and block login workflows. Reverted both auth vault examples to use echo | --password-stdin pattern, which pipes the password via stdin โ€” the recommended secure approach.14 ๅคฉๅ‰
โœจ feat: improve agent context injection (skills discovery, device optimization, prompt cleanup) (#13021) * โœจ feat: inject all installed skills into <available_skills> for AI discovery Previously, only skills explicitly added to the agent's plugins list appeared in <available_skills>. Now all installed skills are exposed so the AI can discover and activate them via activateSkill. Changes: - Frontend: use getAllSkills() instead of getEnabledSkills(plugins) - Backend: pass skillMetas through createOperation โ†’ RuntimeExecutors โ†’ serverMessagesEngine - Add skillsConfig support to serverMessagesEngine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix: use DB + builtin skills for available_skills instead of provider manifests lobehubSkillManifests are tool provider manifests (per-provider, containing tool APIs), not skill metadata. Using them for <available_skills> incorrectly showed provider names (e.g. "Arvin Xu") as skills. Now fetches actual skills from AgentSkillModel (DB) + builtinSkills for correct <available_skills> injection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ๐Ÿ’„ style: use XML structure for online-devices in system prompt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: extract online-devices prompt to @lobechat/prompts package Move device XML prompt generation from builtin-tool-remote-device into the shared prompts package for reusability and consistency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โœ… test: add failing tests for Remote Device suppression when auto-activated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โšก๏ธ perf: suppress Remote Device tool when device is auto-activated When a device is auto-activated (single device in IM/Bot or bound device), the Remote Device management tool (listOnlineDevices, activateDevice) is unnecessary โ€” saves ~500 tokens of system prompt + 2 tool functions. - Add autoActivated flag to deviceContext - Move activeDeviceId computation before tool engine creation - Disable Remote Device in enableChecker when autoActivated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update system role * update system role * โ™ป๏ธ refactor: use agentId instead of slug for OpenAPI responses model field Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix: use JSON round-trip instead of structuredClone in InMemoryAgentStateManager structuredClone fails with DataCloneError when state contains non-cloneable objects like DOM ErrorEvent (from Neon DB WebSocket errors). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix: only inject available_skills when tools are enabled Restore plugins guard to prevent skills injection when tool use is disabled (plugins is undefined), fixing 28 test failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * โœ… test: update system message assertions for skills injection Use stringContaining instead of exact match for system message content, since available_skills may now be appended after the date. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>2 ไธชๆœˆๅ‰
โœจ feat: align self-iteration builtin tool with shared runtime and inspector patterns (#14827)7 ๅคฉๅ‰
โ™ป๏ธ refactor(server,prompts,builtin-tool-skill-maintainer): correct context passing, skill format, chained (#14397)20 ๅคฉๅ‰
โ™ป๏ธ refactor: add the user creds modules & skill should auto inject the need creds (#13124) * feat: add the user creds modules & skill should auto inject the need creds * feat: add the builtin creds tools * fix: add some prompt in creds & codesandbox * fix: open this settings/creds in community plan * fix: refacoter the settings/creds the ui * feat: improve the tools inject system Role * feat: change the settings/creds mananger ui * fix: add the creds upload Files api * feat: should call back the files creds url1 ไธชๆœˆๅ‰
โœจ feat(skills): recognize project-level skills in the homogeneous agent runtime (#15110)1 ๅคฉๅ‰
๐Ÿ› fix(task-schedule): enforce maxExecutions cap and block sub-10min heartbeat (#14865) * ๐Ÿ› fix(task-schedule): enforce maxExecutions cap and block sub-10min heartbeat The "่ฟ่กŒๆฌกๆ•ฐ้™ๅˆถ" input on a scheduled task was accepted by the UI and persisted to `tasks.config.schedule.maxExecutions`, but no execution path ever read it โ€” scheduleDispatch/scheduleTick/runTask had no counter and no cap check, so a "stop after N runs" schedule would loop forever. Separately, the server-side `heartbeatInterval` zod schema was `min(0)`, and the `setTaskSchedule` tool manifest only said "recommend โ‰ฅ600s". An LLM could pass any positive number and trigger sub-minute heartbeats. Enforcement (no schema migration): - `TaskService.updateStatus` stamps `context.scheduler.scheduleStartedAt` (ISO) when a task transitions into `scheduled` from a non-`running` status. The cron loop's natural `running โ†’ scheduled` flips happen via `taskModel.updateStatus` (taskLifecycle), bypassing the service layer, so they don't reset the counter. User-initiated (re)starts do. - `TaskTopicModel.countByTaskSince(taskId, since)` counts task_topics rows created since a timestamp. - `runScheduleTick` reads `config.schedule.maxExecutions`; if the count since `scheduleStartedAt` has reached the cap, it marks the task `completed` (so the next dispatch sweep filters it out) and returns a new `max-executions-reached` skip reason. Heartbeat lower bound: - `updateSchema.heartbeatInterval` on the lambda router now refines to `v === 0 || v >= 600`, matching `MIN_MINUTES = 10` in the UI. - `setTaskSchedule` tool manifest description updated to "Minimum 600s โ€ฆ the server rejects positive values below 600" so the LLM sees the hard limit before the zod refine bounces the call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(task-topic-model): rename countByTaskSince โ†’ countByTask, use drizzle count() - Make `since` an optional `options` argument so the helper covers total counts too, not only the since-window the scheduler needed. - Swap `sql<number>\`count(*)::int\`` for drizzle's native `count()` aggregator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœ… test(task-schedule): cover countByTask, scheduleStartedAt stamping, and tick max-exec - `TaskTopicModel.countByTask`: total-mode, since-window mode, task scope, user scope (real DB). - `TaskService.updateStatus`: stamps `context.scheduler.scheduleStartedAt` on user-initiated starts/restarts of a schedule task; does NOT stamp on the cron loop's natural `running โ†’ scheduled` cycle, on heartbeat-mode tasks, or when the new status isn't `scheduled`. - `runScheduleTick`: cap not configured / under cap โ†’ runs; cap reached โ†’ marks `completed` and skips with `max-executions-reached`; missing `scheduleStartedAt` โ†’ falls through (backwards-compat for tasks created before this PR). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(task-schedule): complete capped schedules at the final allowed run The pre-tick cap check in `runScheduleTick` only sees `runCount` *before* starting the next tick. For low-frequency schedules (e.g. daily, `maxExecutions=1`), this meant the task would consume its final allowed run, get parked back at `scheduled` by `TaskLifecycleService.onTopicComplete`, and then sit in `scheduled` for a full cron period before the next pre-tick check noticed the cap was already consumed โ€” contradicting the "stop after N runs" promise. Move the canonical stop to post-completion: - New `TaskLifecycleService.scheduleCapReached(task)` helper counts `task_topics` rows since `context.scheduler.scheduleStartedAt` and compares against `config.schedule.maxExecutions`. Short-circuits when the task isn't in schedule mode, no cap is configured, or no `scheduleStartedAt` is stamped (pre-PR tasks). - The default post-tick transition in `onTopicComplete` now routes a cap-reached schedule task to `completed` instead of `scheduled`, so the UI/API reflect the cap immediately. The pre-tick check in `runScheduleTick` is kept as defense-in-depth: covers crashed ticks that never reached `onTopicComplete`, users editing `maxExecutions` downward past current count, and stale `scheduled` rows from older code paths. Comment updated to reflect that. Tests: - `onTopicComplete`: schedule task under cap โ†’ still `scheduled`; at cap โ†’ `completed`; with no `scheduleStartedAt` (pre-PR) โ†’ still `scheduled` (helper short-circuits before querying). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>7 ๅคฉๅ‰
โ™ป๏ธ refactor(tool): decouple topic-reference executor from app TRPC client (#13451) โ™ป๏ธ refactor(tool): inject topic reference runtime in app layer Move topic-reference executor to runtime injection so package code no longer imports app-level TRPC client aliases. Keep the TRPC call in store executor wiring for clear package/app boundaries. Made-with: Cursor1 ไธชๆœˆๅ‰
๐Ÿ’„ style(intervention): polish confirmation bar layout (#14587)14 ๅคฉๅ‰
โ™ป๏ธ refactor(chat): introduce agentDispatcher.selectRuntimeType (#14428) * ๐Ÿ”ฅ refactor: remove dead Search Summary chain Footer.tsx in web-browsing Search portal had near-zero usage. Removing it makes the entire chain dead: triggerAIMessage, summaryPluginContent, fillPluginMessageContent, saveSearchResult, plus the inSearchWorkflow param threaded through internal_execAgentRuntime. Part of LOBE-8519 โ€” clears the path before introducing agentDispatcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœจ feat: add agentDispatcher.selectRuntimeType Centralizes the client / gateway / hetero routing decision so every entry point shares one source of truth. parentRuntime override lets sub-agent dispatches inherit their parent operation's runtime. Part of LOBE-8519 โ€” call sites are migrated in following commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: route sendMessage through selectRuntimeType Compute runtimeType once per sendMessage call and dispatch off it instead of re-deriving the hetero/gateway/client decision inline. Behavior is identical; this just centralizes the routing rule (LOBE-8519, A1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: route regenerate / continue through selectRuntimeType regenerateUserMessage and continueGenerationMessage in the conversation store now consult selectRuntimeType for routing. Hetero variants of both are not yet implemented (they currently fall through to client mode with a TODO + warning). Also drops chatStore.continueGenerationMessage โ€” the conversation-store version is the only caller; the chat-store duplicate had zero production usage. Part of LOBE-8519 (A2, B4 deletion, B5). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: route resume helpers through selectRuntimeType approveToolCalling / rejectToolCalling / rejectAndContinueToolCalling now consult selectRuntimeType (via #shouldUseGatewayResume) using the operation's own ConversationContext, instead of the bare isGatewayModeEnabled() check. Behavior is preserved (gateway resume vs. local resume); hetero resume is not yet implemented and falls through to the client local path. Part of LOBE-8519 (A3, A4, A5). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: route sub-agent dispatch through selectRuntimeType directMentionRoute and callAgent now consult selectRuntimeType using the parent agent's config so sub-agent dispatches inherit the parent runtime. Only the client path is wired today; gateway / hetero variants warn + fall through with TODOs for follow-up. Part of LOBE-8519 (B3, B6). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor: rename internal_execAgentRuntime to executeClientAgent Aligns the client runner's name with executeGatewayAgent and executeHeterogeneousAgent so the three runtimes share a consistent verb-noun pattern. Pure rename โ€” no behavioral changes; log prefixes and test mock variables follow the new name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>19 ๅคฉๅ‰
๐Ÿ› fix(onboarding): prevent agent identity from using user name (#15112)1 ๅคฉๅ‰
Revert "fix(github): support both runCommand and run_command in render matching" This reverts commit 6770d8f321431be5ea425033a7a51bd145278b4d. 1 ๅคฉๅ‰
โ™ป๏ธ refactor: load models through model bank slot (#14877) * โ™ป๏ธ refactor: load models through model bank slot * โ™ป๏ธ refactor: remove static LobeHub model cards * โ™ป๏ธ refactor: share OpenAI image parameters * ๐Ÿ› fix: load async LobeHub model config in server paths * ๐Ÿ› fix: repair model bank CI follow-ups * ๐Ÿ› fix: avoid repeated model bank fallback loads * ๐Ÿ› fix: resolve business model config import in browser * ๐Ÿ› fix: align Nano Banana 2 resolution default * โ™ป๏ธ refactor: move model loader slot under client * โœ… test: move model bank aiModels spec out of build entries * ๐Ÿ› fix: use business model config for mixed provider parsing * โ™ป๏ธ refactor: consolidate model bank provider utilities * ๐Ÿ› fix: preserve Nano Banana 2 raw resolution * ๐Ÿ› fix: avoid generated locale sync for raw resolution * ๐ŸŒ style: add Nano Banana 2 resolution locales * ๐ŸŒ style: add online LobeHub model locales * ๐Ÿ› fix: guard optional model provider loaders * ๐Ÿ› fix: prevent sitemap build from hanging * ๐Ÿ› fix: clear sitemap timeout after model load3 ๅคฉๅ‰
โœจ feat: support bot attachments across all platforms (#15029) * feat: support bot attachments across all platforms Squashed from feat/support-bot-attachments (15 commits): - Wechat adapter attachment support (image/video/voice/file via iLink CDN) - All-platform attachments: Discord, Telegram, Slack, Feishu/Lark, LINE, QQ - Messager + CLI sendMessage/sendDirectMessage/replyToThread attachment params - System Bot messenger installs as outbound channels + listOutboundChannels - Onboarding messager integration + feedback commands - AI-side attachment ingestion across platforms - Updated builtin-tool-message systemRole / manifest / types * chore: unify client and runtime adapter * feat: support system bot messenger and cli * chore: remove unnecessary listOutboundChannels * chore: add test and prompts3 ๅคฉๅ‰
โœจ feat: support bot attachments across all platforms (#15029) * feat: support bot attachments across all platforms Squashed from feat/support-bot-attachments (15 commits): - Wechat adapter attachment support (image/video/voice/file via iLink CDN) - All-platform attachments: Discord, Telegram, Slack, Feishu/Lark, LINE, QQ - Messager + CLI sendMessage/sendDirectMessage/replyToThread attachment params - System Bot messenger installs as outbound channels + listOutboundChannels - Onboarding messager integration + feedback commands - AI-side attachment ingestion across platforms - Updated builtin-tool-message systemRole / manifest / types * chore: unify client and runtime adapter * feat: support system bot messenger and cli * chore: remove unnecessary listOutboundChannels * chore: add test and prompts3 ๅคฉๅ‰
โœจ feat: support bot attachments across all platforms (#15029) * feat: support bot attachments across all platforms Squashed from feat/support-bot-attachments (15 commits): - Wechat adapter attachment support (image/video/voice/file via iLink CDN) - All-platform attachments: Discord, Telegram, Slack, Feishu/Lark, LINE, QQ - Messager + CLI sendMessage/sendDirectMessage/replyToThread attachment params - System Bot messenger installs as outbound channels + listOutboundChannels - Onboarding messager integration + feedback commands - AI-side attachment ingestion across platforms - Updated builtin-tool-message systemRole / manifest / types * chore: unify client and runtime adapter * feat: support system bot messenger and cli * chore: remove unnecessary listOutboundChannels * chore: add test and prompts3 ๅคฉๅ‰
๐Ÿ› fix: onboarding im integration (#14988) * feat: support onboarding messager * chore: remove telegram CN screenshots * feat: add feedback commands * fix: bot feedback commands * chore: optimize messenger intergration * chore: update onboarding style * feat: support wechat adapter attachments * feat: support ai attachments * chore: update i18n files * fix: bot message image attachment4 ๅคฉๅ‰
โœจ feat: add notification system (temporarily disabled) (#13301)1 ไธชๆœˆๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
โœจ feat(trpc): add response metadata and auth header handling (#11816) * โœจ feat(trpc): add response metadata and auth header handling Add createResponseMeta utility to centralize tRPC response metadata handling. Set X-Auth-Required header for UNAUTHORIZED errors to distinguish real auth failures from other 401 errors. Update all tRPC routes to use the new utility. * โ™ป๏ธ refactor(desktop-bridge): extract auth constants to shared package Move AUTH_REQUIRED_HEADER and TRPC_ERROR_CODE_UNAUTHORIZED to @lobechat/desktop-bridge for consistent usage across server and desktop.3 ไธชๆœˆๅ‰
โœจ feat(platform-agent): openclaw/hermes agent creation UI, device guard, and remote dispatch backend (#15065) * โ™ป๏ธ refactor(agent-invocation): add AgentInvocationIntent + unified non-hetero dispatcher (LOBE-8927/8928) Introduce a shared invocation contract and unified dispatcher for the non-hetero, non-group agent call paths (callAgent speak mode and @agent direct mentions). Removes the implicit client-only fallback that existed in both entry points. Changes: - agentDispatcher.ts: add AgentInvocationIntent interface as the unified intent type for callSubAgent / callAgent / @agent invocations - nonHeteroSubAgentDispatcher.ts (new): dispatchNonHeteroSubAgent() resolves child runtime via selectRuntimeType and routes to executeClientAgent (client) or executeGatewayAgent (gateway); throws for hetero (out of scope per LOBE-8926) - conversationLifecycle.ts #executeDirectMentionRoute: replace hardcoded executeClientAgent + TODO fallback with dispatchNonHeteroSubAgent call - builtin-tool-agent-management executor.ts callAgent speak mode: replace hardcoded executeClientAgent + TODO fallback with dispatchNonHeteroSubAgent call Fixes LOBE-8927 Fixes LOBE-8928 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœจ feat(platform-agent): openclaw/hermes agent creation UI, device guard, and remote dispatch backend - Add CreatePlatformAgent 3-step creation modal (type select โ†’ config โ†’ bind device) - Add RemoteAgentConfigCard to agent profile editor for openclaw/hermes config - Add device guard banner in HeterogeneousChatInput for offline/unavailable devices - Add useRemoteAgentDeviceGuard hook for real-time device status polling - Fix backend dispatch: openclaw/hermes now use executeToolCall(runHeteroTask) instead of dispatchAgentRun (lh connect only handles tool_call_request) - Add agentNotify router for lh notify โ†’ DB write + gateway stream event - Add device.checkCapability endpoint for platform availability probe - Add notify_update event type to gateway stream and event handler - Add sendDoneSignal in heteroTask.ts for clean openclaw exit signaling - Unify non-hetero sub-agent dispatch via dispatchNonHeteroSubAgent (LOBE-8927) - Route openclaw/hermes to gateway runtime; keep claude-code/codex on hetero/client paths - Add i18n keys for platform agent UI and device guard banners Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ› fix(agentNotify): reuse execAgent placeholder message on first lh notify call Instead of creating a second empty bubble, the first assistant notify without a messageId now updates the placeholder assistantMessageId that execAgent already seeded in runningOperation.assistantMessageId. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœจ feat(agentNotify): cancel openclaw/hermes process on interruptTask - Store deviceId + heteroType in topic.metadata.runningOperation at dispatch time - interruptTask now dispatches cancelHeteroTask tool call to the bound device when topicId reveals a remote hetero operation, sending SIGINT to the process - Pass topicId from gateway cancel callback to interruptTask - Add topicId to InterruptTaskSchema and InterruptTaskParams Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โ™ป๏ธ refactor(hetero-agent): consolidate remote/local type classification into heterogeneous-agents package - Add RemoteHeterogeneousAgentConfig, REMOTE_HETEROGENEOUS_AGENT_CONFIGS, isRemoteHeterogeneousType, and derived type aliases (HeterogeneousAgentType, LocalHeterogeneousAgentType, RemoteHeterogeneousAgentType) to packages/heterogeneous-agents/src/config.ts - Extend HETEROGENEOUS_TYPE_LABELS to cover remote platform types (openclaw, hermes) via REMOTE_HETEROGENEOUS_AGENT_CONFIGS - Replace all inline `=== 'openclaw' || === 'hermes'` checks and local Sets/type aliases across aiAgent service, ProfileEditor, HeterogeneousChatInput, useRemoteAgentDeviceGuard, CreatePlatformAgent, RemoteAgentConfigCard, and deviceProxy with the shared utility - Show OpenClaw/Hermes display name in assistant message model tag (Usage component) by setting provider=heteroType on placeholder message and using HETEROGENEOUS_TYPE_LABELS for rendering - Fix ReferenceError: move remoteDeviceId declaration before updateMetadata call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add the platform agents get profiles * ๐Ÿ› fix(platform-agent): routing, security, and i18n issues from review - Route openclaw/hermes to gateway on desktop (P1): add isRemoteHeterogeneousType check in selectRuntimeType before desktop hetero branch โ€” remote agents never use local desktop IPC, no special-casing needed - Fix race in heteroTask: sendAutoNotify โ†’ sendDoneSignal now sequential via .finally() so error message is written before agent_runtime_end is published - Security: validate messageId belongs to topicId in agentNotify before MessageModel.update to prevent cross-conversation data corruption - Clear capability/device/profile state on platform change in creation modal (P2) - Derive PLATFORM_DEFS from REMOTE_HETEROGENEOUS_AGENT_CONFIGS โ€” new platforms automatically appear in the modal without code changes - Use HETEROGENEOUS_TYPE_LABELS for platform names in HeterogeneousChatInput and RemoteAgentConfigCard (remove hardcoded PLATFORM_NAMES map) - i18n: platform card descs, 'online'/'offline' tags, 'Select a device' placeholder, checkFailed error โ€” all now use i18n keys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โ™ป๏ธ refactor(platform-agent): derive remote platform enum from config + fix test - device.ts: replace hardcoded z.enum(['hermes','openclaw']) with a zod enum derived from REMOTE_HETEROGENEOUS_AGENT_CONFIGS so new platforms are automatically covered without touching this file - heteroTask.ts / getAgentProfile.ts: use RemoteHeterogeneousAgentType instead of literal 'hermes' | 'openclaw' union for the same reason - gateway.test.ts: update cancel-handler assertion to include topicId which was added to the interruptTask call in the previous commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœจ feat(platform-agent): gate creation entry behind labs flag + expand dispatcher tests - Add enablePlatformAgent lab preference (default false) โ€” the "Add Platform Agent" menu item is hidden until the user opts in via Settings โ†’ Advanced โ†’ Labs - Wire toggle in settings/advanced with labs i18n key (en/zh) - createPlatformAgentMenuItem returns null when flag is off - agentDispatcher.test: add remote hetero cases (openclaw/hermes โ†’ gateway on both web and desktop) to cover the routing fix added earlier Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ› fix(lint): merge duplicate import + sort interface props in nonHeteroSubAgentDispatcher Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ’„ feat(platform-agent): disable Hermes option in creation modal (coming soon) Hermes is not yet ready for production. Mark it as coming-soon in the platform selection step: grayed-out card, not clickable, "Coming Soon" tag next to the name. To enable Hermes when ready: remove 'hermes' from COMING_SOON_PLATFORMS in CreatePlatformAgent/index.tsx. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœ… fix(test): mock CreatePlatformAgentModal in ModalProvider.test The modal always mounts (open=false) and calls lambdaQuery.useQuery which requires a tRPC context not present in the test environment. Mock it out the same way as ChatGroupWizard and EditingPopover. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * โœ… fix(test): mock useUserStore + labPreferSelectors in useCreateMenuItems.test Adding useUserStore to useCreateMenuItems triggered user store initialization in tests, which pulled in @lobechat/const and failed because the existing mock only exports isDesktop. Mock the store and selectors directly instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ๐Ÿ› fix(platform-agent): hide divider when platform agent entry is disabled The divider before 'Add Platform Agent' was unconditional โ€” it showed even when the labs flag was off. Conditionally include both the divider and the menu item together so no orphaned separator appears. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 ๅคฉๅ‰
๐ŸŒ chore: translate non-English comments to English in edge-config (#14453)17 ๅคฉๅ‰
๐Ÿ› fix(editor-runtime): add mutation handlers for post-save synchronization (#14469) * ๐Ÿ› fix(editor-runtime): add afterMutateHandler for post-mutation synchronization Signed-off-by: Innei <tukon479@gmail.com> * ๐Ÿ› fix(editor-runtime): enhance beforeMutateHandler with context and add meaningful content check Signed-off-by: Innei <tukon479@gmail.com> * ๐Ÿ› fix(editor-runtime): improve data source validation and streamline command dispatch logic Signed-off-by: Innei <tukon479@gmail.com> * ๐Ÿ› fix(editor-runtime): add test for Page Agent editTitle behavior without sending content or editorData Signed-off-by: Innei <tukon479@gmail.com> * ๐Ÿ› fix(editor-runtime): update LiteXML node extraction to include attributes and improve error logging Signed-off-by: Innei <tukon479@gmail.com> * ๐Ÿ› fix: use namespace import for GeneralChatAgent to fix vi.spyOn in tests vi.spyOn on a module namespace object requires the production code to access the class through the same namespace. Destructured imports capture a direct binding that bypasses the spy, causing "Class constructor GeneralChatAgent cannot be invoked without 'new'" in tests. * ๐Ÿ› fix: replace vi.spyOn on class constructor with vi.mock for GeneralChatAgent vi.spyOn wraps a class with a plain function that loses [[Construct]] semantics in ESM, causing "Class constructor GeneralChatAgent cannot be invoked without 'new'". Replace with vi.mock + hoisted mock constructor that properly tracks calls while preserving new-ability. --------- Signed-off-by: Innei <tukon479@gmail.com>17 ๅคฉๅ‰
โœจ feat(review-panel): group review changes by submodule (#15148) * ๐Ÿ› fix(claude-code): show task subject in TaskUpdate inspector & header A TaskUpdate that only sets `subject` (no status flip) was falling through to the aggregate `Todos: x/y` chip and burying the per-call signal. Surface the new subject like the status branch already does: "Task updated: <subject>". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(review-panel): group changes by submodule with per-group collapse Surface dirty submodules as their own groups in the agent Review panel so users working in a parent repo with submodules see each repo's changes clustered together (mirrors WebStorm's per-repo commit grouping). Both Unstaged and Branch modes apply the same grouping โ€” submodules with internal working-tree changes (unstaged) or branch diffs against their own origin/HEAD (branch) surface as separate groups, each tagged with its own branch label and file/diff totals. Backend (`GitCtr`): - `getGitWorkingTreePatches` and `getGitBranchDiff` extracted into private recursive helpers that detect submodules via `git submodule status`, partition pointer-bump entries out of the parent's flat patches, and recurse one level for each dirty submodule's own patches + branch info. - Nested submodules are not traversed (phase 1); revert routes through each group's absolute path so submodule files revert inside the submodule. Renderer: - New `GroupHeader` and `FileRow` subcomponents split out of `Review`. `GroupHeader` is sticky with a chevron + name + file count + diff totals + branch; clicking collapses the group's rows. A hover-revealed `ActionIcon` on the right expands/collapses all file diffs in that group (`e.stopPropagation` keeps it from also collapsing the surrounding header). - Fixed `block-size: 32px` on the header so toggling the fold button on/off doesn't jitter the sticky height. - Single-repo working trees keep the previous flat layout when no submodule groups exist. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(review-panel): scan all submodules in branch mode Previously branch mode only surfaced a submodule group when the parent's diff against base ref contained a `Subproject commit` pointer bump for it. This missed the common case where the user has committed work in a submodule on a feature branch but the parent's pointer hasn't yet moved relative to its base โ€” the submodule's own branch differences stayed invisible in the Review panel. `collectBranchDiff` now recurses into every registered submodule (single level, in parallel) and keeps a group when EITHER its pointer differs in the parent OR its own branch diverges from its own origin/HEAD. Clean-on- both-axes submodules are dropped so the panel stays quiet for repos where the submodule isn't actively being worked on. Submodule count is small in practice (single digits), so the extra per-submodule fetch + diff in parallel is an acceptable cost. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(agent-documents): hide .tool-results archive from user-facing lists Auto-created tool-result archive folder and its children are now filtered out of getAgentDocuments. Agents still discover them via the tool-oriented listDocuments paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ’„ style(review-panel): drop "file not found in project index" toast Reveal-in-tree now silently no-ops when the path isn't indexed (e.g. submodule files) instead of nagging the user with a warning toast. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(review-panel): keep submodule groups visible on pointer-only bumps `isEmpty` was derived solely from `totalEntryCount`, which counts file patches across groups. A pointer-only submodule bump (parent patch filtered out, submodule group present but internally clean) produced `totalEntryCount === 0`, so the panel rendered the global empty state and silently skipped the submoduleClean group rendering โ€” even though git was dirty. Now `isEmpty` also requires zero submodule groups, so pointer-only bumps keep their GroupHeader + "submodule clean" line. The fold-all button visibility switches to `totalEntryCount > 0` so it stays hidden when there's nothing foldable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>11 ๅฐๆ—ถๅ‰
๐Ÿ“ docs: Polishing and improving product documentation (#12612) * ๐Ÿ”– chore(release): release version v2.1.34 [skip ci] * ๐Ÿ“ docs: Polish documents * ๐Ÿ“ docs: Fix typo * ๐Ÿ“ docs: Update start * ๐Ÿ“ docs: Fix style * ๐Ÿ“ docs: Update start * ๐Ÿ“ docs: Update layout * ๐Ÿ“ docs: Fix typo * ๐Ÿ“ docs: Fix typo --------- Co-authored-by: lobehubbot <i@lobehub.com>2 ไธชๆœˆๅ‰
โœ… test: add unit tests for eval-dataset-parser (#13197) Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 ไธชๆœˆๅ‰
โœจ feat(eval): add external scoring mode (#12729) * wip: add llm relevant & BrowseComp * wip: add widesearch desc * wip: dsqa, hle, widesearch * wip: add dsqa * wip: add awaiting eval status for runs * wip: add awaiting status for run * wip: adjust hle-verified * :bug: fix: browsecomp topics * :memo: docs: add annotations * wip: add awaiting status for pass@k * wip: add complete status * wip: update theard dots * wip: update run status page * wip: remove useless impl * wip: update prompt * :sparkles: feat: add external eval routes * wip: add eval cli * :bug: fix: support authoritize in no browser environment * wip: pass tests * :recycle: refactor: remove tests * :recycle: refactor: mo camel case2 ไธชๆœˆๅ‰
๐Ÿ› fix: onFinish never called when browser tab is backgrounded during SSE streaming (#14461) ๐Ÿ› fix: remove rAF animation blocking onFinish when tab is backgrounded Replace await-on-animation with synchronous flushQueue() to prevent background tab throttling from stalling chat completions, MCP tool calls, and agent loop continuation.16 ๅคฉๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
โœจ feat(onboarding): structured hunk ops for updateDocument (#13989) * โœจ feat(onboarding): structured hunk ops for updateDocument Extend `updateDocument` (and the underlying `@lobechat/markdown-patch`) with explicit hunk modes so agents can unambiguously express deletes and inserts instead of encoding them as clever search/replace pairs. Modes: `replace` (default, backward-compatible), `delete`, `deleteLines`, `insertAt`, `replaceLines`. Line-based modes use 1-based inclusive ranges and are applied after content-based hunks, sorted by anchor line descending so earlier lines stay stable. New error codes: `LINE_OUT_OF_RANGE`, `INVALID_LINE_RANGE`, `LINE_OVERLAP`. Onboarding document injection now prefixes each line with its 1-based number (cat -n style) so the agent can cite line numbers when issuing line-based hunks. Tool description, system role, and per-phase action hints updated to teach the new shape. * ๐Ÿ› fix(onboarding): align patchOnboardingDocument zod schema with structured hunks The tRPC input schema still accepted only the legacy `{search, replace}` shape, so agent calls using the new `insertAt`/`delete`/`deleteLines`/`replaceLines` hunk modes were rejected before reaching `applyMarkdownPatch`. Switch to a z.union matching MarkdownPatchHunk. * ๐Ÿ› fix(markdown-patch): validate line ranges before overlap detection Previously the overlap loop ran before per-hunk range validation, so an invalid range (e.g. startLine=0 or endLine<startLine) combined with another line hunk would be misreported as LINE_OVERLAP instead of the real LINE_OUT_OF_RANGE / INVALID_LINE_RANGE. Validate each line hunk against the baseline line count first, then run overlap detection on valid ranges only.1 ไธชๆœˆๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
๐Ÿ› fix: add pre-flight tool-limit check for GitHub Copilot (#14909) * fix: add pre-flight tool-limit check for GitHub Copilot (128 tools) - Add maxToolCount / maxToolPayloadBytes to AIChatModelCard - Set maxToolCount=128 on all githubCopilot models - Add ExceededToolLimit error type - Create validateToolLimits utility - Integrate pre-flight check into LobeGithubCopilotAI Closes LOBE-8660 Part of LOBE-8678 * refactor: lift Copilot tool limit to provider settings + map ExceededToolLimit to 400 - Move maxToolCount/maxToolPayloadBytes from AIChatModelCard to AiProviderSettings; the 128-tool cap applies to every GitHub Copilot model, so a single provider-level field replaces the per-model duplication. - Rewrite validateToolLimits to read limits from DEFAULT_MODEL_PROVIDER_LIST by providerId. - Add ExceededToolLimit to getStatus in errorResponse.ts (alongside ExceededContextWindow) so the pre-flight error returns HTTP 400 instead of throwing RangeError from new Response(..., { status: 'ExceededToolLimit' }). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: add coverage for validateToolLimits / assertToolLimits - ToolLimitExceededError: count overage message, payload-size message (KB rounding), combined overage, field assignment. - validateToolLimits: empty tools, provider without declared limits, unregistered provider, count under cap, count exceeding the real GitHub Copilot 128 limit, payload-size enforcement via a synthetic provider pushed into DEFAULT_MODEL_PROVIDER_LIST. - assertToolLimits: re-throws as a structured AgentRuntimeError chat payload with errorType ExceededToolLimit; no-op when limits are not exceeded. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 ๅคฉๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
๐Ÿ”จ chore(agent-signal,app): added tracing to agent signal, ensure traceparent propagate to handler (#14212)25 ๅคฉๅ‰
๐Ÿ› fix: guard system agent model config (#15058) * ๐Ÿ› fix: guard system agent model config * ๐Ÿ› fix: allow legacy system agent settings * โœ… test: fix disabled thinking payload type * ๐Ÿ› fix: allow thinking without budget tokens2 ๅคฉๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
โœจ feat(desktop): implement subscription pages embedding with webview (#12114) * โœจ feat(desktop): implement subscription pages embedding with webview - Add SubscriptionIframeWrapper component for embedding subscription pages in Electron webview - Replace compile-time ENABLE_BUSINESS_FEATURES with runtime serverConfigSelectors - Add useIsCloudActive hook to detect cloud sync status - Add setupSubscriptionWebviewSession service for webview session management Resolves LOBE-4589 * โœจ feat: enhance SubscriptionIframeWrapper for improved configuration - Updated iframe URL to use OFFICIAL_URL for better maintainability. - Integrated server configuration to conditionally enable business features. - Cleaned up code by removing commented-out lines and unnecessary eslint suppressions. This update improves the flexibility and readability of the SubscriptionIframeWrapper component. Signed-off-by: Innei <tukon479@gmail.com> * โœจ feat: enhance link interception in SubscriptionIframeWrapper - Updated the script to intercept both link clicks and window.open calls for better URL handling. - Added functionality to resolve relative URLs to absolute before logging. This improvement enhances the tracking of external link interactions within the iframe. Signed-off-by: Innei <tukon479@gmail.com> * ๐Ÿ”ง chore: clean up useIsCloudActive hook - Removed unnecessary eslint suppressions related to react-hooks rules. - Improved code readability by eliminating commented-out lines. This update enhances the clarity and maintainability of the useIsCloudActive hook. Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com> 3 ไธชๆœˆๅ‰
๐Ÿ’„ style: share branded inspector between CC MCP and built-in Linear skill (#14884) * โœจ feat(linear): share branded inspector between CC MCP and built-in Linear skill The Linear-branded inspector (logomark + action chip + parentId badge) was only registered against `mcp__claude_ai_Linear__*` tool names emitted by the CC adapter. LobeHub's own built-in Linear skill calls land with `identifier='linear'` and bare apiNames (`get_issue`, `save_issue`, โ€ฆ), so they fell through to the generic Title + JSON inspector despite being the exact same Linear surface. Moves the inspector + label utilities out of `builtin-tool-claude-code` into `packages/builtin-tools/src/linear/` (alongside `github/`) and registers them twice in the central inspector map: once under `LinearIdentifier = 'linear'` for the built-in skill path, once merged into the CC entry for the MCP-prefixed wire names. Same component, same look in both cases. `formatLinearShortLabel` now matches bare apiNames against the known tool list too, so the collapsed workflow summary reads `Linear ยท Get issue` for built-in calls as well โ€” previously only CC got the humanized label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(linear): leave CC's LinearMcp inspector inside CC, only ship the built-in skill side Walks back the cross-package edits from the previous commit. The CC adapter keeps its own `LinearMcp.tsx` + `linearMcpLabels.ts` exactly as #14864 left them โ€” `formatLinearMcpShortLabel` is still exported from `@lobechat/builtin-tool-claude-code/client/labels` and `toolDisplayNames.ts` still imports it from there. CC's inspector index continues to spread `LinearMcpInspectors` into its own map. The new shared module under `packages/builtin-tools/src/linear/` now only covers the built-in LobeHub Linear skill path: `LinearIdentifier='linear'` + bare apiNames (`get_issue`, `save_issue`, โ€ฆ). The inspector component is duplicated from CC on purpose โ€” `builtin-tools` already depends on `builtin-tool-claude-code`, so we can't import the other way without a circular dep, and the user wants the CC code to stay put. Drops the `LinearMcpInspectors` re-export and the CC-entry merge in `inspectors.ts` that the previous commit had introduced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โ™ป๏ธ refactor(linear): hoist shared LinearInspector + label utilities into shared-tool-ui The Linear-branded inspector and its tool-name parsing helpers were duplicated between `builtin-tool-claude-code/src/client/Inspector/LinearMcp` (MCP-prefixed wire names) and `builtin-tools/src/linear/` (built-in skill bare names). The dep graph (`builtin-tools` โ†’ `builtin-tool-claude-code` โ†’ `shared-tool-ui`) means CC can't import from `builtin-tools`, so the previous round kept two copies. Moves the component + labels into `packages/shared-tool-ui/src/Inspector/ Linear/` โ€” both CC and `builtin-tools` already depend on `shared-tool-ui`, so they can each pull the same `LinearInspector` and register it under whichever key shape their code path uses: - CC's `LinearMcp.tsx` is now a 10-line wrapper that maps the shared inspector across every MCP-prefixed name. - CC's `linearMcpLabels.ts` re-exports the parsing primitives + keeps the CC-only `formatLinearMcpShortLabel` (the prefix check stays here so the workflow-summary label only fires for MCP-prefixed wire names). - `builtin-tools/src/linear/` drops its own Inspector / labels files; the index just registers the shared component under bare apiNames. Exposes a labels-only subpath `@lobechat/shared-tool-ui/inspectors/ linear-labels` so the workflow-summary path can pull parsing helpers without dragging the React inspector (and its `keyframes`-using style modules) into `Group.test.tsx`'s mocked antd-style context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>6 ๅคฉๅ‰
๐Ÿ› fix(web-crawler): cap response body size to prevent serverless OOM (#14660) * ๐Ÿ› fix(web-crawler): cap response body size to prevent serverless OOM Production saw repeated SIGABRT crashes on `/trpc/tools/search.webSearch` where Node aborted with V8 "allocation failed" โ€” the naive crawler buffered entire response bodies into heap before the 1 MB downstream truncation could apply, so a single large page (or a batch of three under default concurrency=3) could push rss past the lambda memory ceiling. - ssrfSafeFetch: add opt-in `maxContentLength` that streams the response body via `for await` and stops at the cap (soft truncation โ€” still a successful response). Breaking the iterator destroys the underlying stream and releases the connection. Default behaviour (full `arrayBuffer()` read) unchanged when the option is absent. - naive crawler: pass `maxContentLength: MAX_HTML_SIZE` so any body beyond 1 MB is dropped at the network layer instead of being materialised in heap. - htmlToMarkdown: explicitly call `window.happyDOM.close()` in a finally block so the parsed DOM tree is released as soon as parsing finishes, rather than waiting for the function scope to drop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * โœ… test(ssrf-safe-fetch): add OOM regression tests for response body cap Verify that the maxContentLength cap actually prevents the production SIGABRT scenario, not just produces a truncated body. - Source-pull bound: a body source with 200 MB available, capped at 1 MB, must not be drained beyond ~1 MB. Asserts on bytes pulled from the generator, which is the property that prevents OOM. - Concurrency bound: matches production CRAWL_CONCURRENCY=3 โ€” three concurrent oversized fetches should pull at most ~3 MB total, not 300 MB. - Heap-delta bound (gated on --expose-gc): under real GC pressure, fetching a 50 MB body with a 1 MB cap should grow heapUsed by < 10 MB. Run with `NODE_OPTIONS=--expose-gc bunx vitest run` to exercise; skipped by default so CI doesn't false-fail on GC timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>11 ๅคฉๅ‰
๐Ÿ› fix: surface stderr in errorOutput fallback and add `UNKNOWN_EXEC_ERROR` prefix (#14964) * fix: surface stderr in errorOutput fallback and add UNKNOWN_EXEC_ERROR prefix When a shell command fails with a non-zero exit code (e.g. git commit with nothing to commit), the runner puts the error message in stderr but does not set the error field. This caused errorOutput() to fall through to the hardcoded 'Tool execution failed' string, losing the actual error. Changes: - errorOutput() now checks state.stderr and state.error before the final fallback, so real error messages from stderr are surfaced - Final fallback changed from 'Tool execution failed' to '[UNKNOWN_EXEC_ERROR] Tool execution failed' for easier grepping - Same prefix applied to toResult() in the executor for consistency * fix: pass stderr/stdout into errorOutput state for runCommand failures runCommand() called errorOutput() with a state that only contained { error, isBackground, success }, missing result.result.stderr. Since normalizeResult() stores the shell stderr under result.result.stderr (not result.error), the state.stderr fallback in errorOutput() was never reached for non-zero exit commands like 'git commit' with nothing to commit.4 ๅคฉๅ‰
โœจ feat: per-call llm_generation_tracing observability (#15124) * โœจ feat(database): add llm_generation_tracing schema + tracing package (LOBE-9462) Foundation layer for per-call observability of `generateObject` calls. - New Drizzle table `llm_generation_tracing` with identity / context / model / result / usage / storage / feedback / audit columns and full single-column index coverage (Postgres bitmap-scan friendly). Migration 0103 is idempotent (CREATE TABLE/INDEX IF NOT EXISTS) for safe re-runs. - `LlmGenerationTracingModel` with `record` / `updateFeedback` / `findById` / `listRecent`, all userId-scoped to prevent cross-user leaks. - New package `@lobechat/llm-generation-tracing` mirroring agent-tracing's shape: `ITracingStore` interface, `FileTracingStore` (local/dev, scenario subfolders + latest.json symlink), `computePromptHash` (6-char sha256 of systemPrompt + schema), and `TRACING_SCENARIO_REGISTRY` + `resolveScenario` with explicit scenario override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(model-runtime): wire llm_generation_tracing into ModelRuntime.generateObject (LOBE-9462) Per-call interception layer โ€” one hook covers all generateObject callers. - New `onGenerateObjectComplete` hook on `ModelRuntimeHooks`: always fires (success or failure) with latency, usage, output/error. Fixes the gap where `onGenerateObjectFinal` only fires when the runtime invokes `onUsage`. - `S3TracingStore` (zstd level 3, key `llm-generation-tracing/{scenario}/{v}-{hash}/{date}/{id}.json.zst`) and `LLMGenerationTracingService` that does DB insert โ†’ store.save โ†’ patch storage_key. Store failures preserve the row with `metadata.store_error`. - `createLLMGenerationTracingHook` + `mergeModelRuntimeHooks` wired into `initModelRuntimeFromDB`; tracing runs alongside business (billing) hooks via `next/server.after()` when available, microtask fallback otherwise. Unknown metadata keys (e.g. `parent_memory_trace_key`) pass through. - Memory extractor accepts `parentMemoryTraceKey` option for the job-level backlink. Follow-up-action caller given an explicit `scenario: 'follow_up'` metadata override โ€” it was the only OSS caller missing trigger metadata. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœ… test(llm-generation-tracing): type vi.fn mocks so tsgo accepts mock.calls indexing The hook + service tests destructured `mock.calls[0][0]` and accessed nested fields, which tsgo flagged as TS2493 / TS18046 because `vi.fn()` defaults to a zero-arg signature. Add explicit type parameters to the mocks so tsgo can infer the call tuple, and cast `call.payload` at the access point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(model-runtime): move mergeModelRuntimeHooks into the package It's a generic utility for composing `ModelRuntimeHooks` instances โ€” same import surface as `ModelRuntime` and the hooks interface โ€” so it belongs alongside them rather than tucked under a server-side consumer. - New `packages/model-runtime/src/core/mergeHooks.ts` exports `mergeModelRuntimeHooks` and is re-exported from the package index. - Move the unit tests to `packages/model-runtime/src/core/mergeHooks.test.ts`, including a new case covering the "a throws โ†’ b is skipped" load-bearing semantics. - `src/server/services/llmGenerationTracing/hook.ts` drops the local copy and the consumer (`src/server/modules/ModelRuntime/index.ts`) imports from `@lobechat/model-runtime`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): version lives with the prompt, not in a central table `promptVersion` was baked into `TRACING_SCENARIO_REGISTRY`, far from any prompt definition โ€” editing a prompt + forgetting to bump the entry in a completely different file was an obvious foot-gun. - Registry is now `Record<string, string>` mapping trigger โ†’ scenario only; it's the stable concern that rarely changes. - `resolveScenario` always passes `promptVersion` through from the caller, defaulting to `UNKNOWN_PROMPT_VERSION` ('v0') when absent. - Each call site declares its own `*_PROMPT_VERSION` constant next to the prompt it describes. `followUpAction` ships the first one: `FOLLOW_UP_PROMPT_VERSION` in `prompts/index.ts`, threaded through `metadata.promptVersion` at the `generateObject` call. Other callers can add the same constant when they next touch their prompts. The 6-char prompt hash on the row still catches forgotten bumps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): wire prompt-version metadata at the auto-complete call site Aligns input auto-complete with the FOLLOW_UP_PROMPT_VERSION convention so each prompt iteration is recordable as the chat-side tracing lands. - `INPUT_COMPLETION_PROMPT_VERSION = 'v1.0'` declared next to `chainInputCompletion` โ€” bump together with the prompt body. - `fetchPresetTaskResult` accepts optional `metadata` and forwards it to `getChatCompletion`; the existing chat path already plumbs metadata to `ModelRuntime.chat` options. - `InputEditor` call site passes `{ scenario: 'input_completion', promptVersion }`. Note: `llm_generation_tracing` currently only fires from `onGenerateObjectComplete`. Input completion is a `chat` call, so this metadata is forward-looking until a chat-side tracing hook lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ› fix(llm-generation-tracing): collapse bucketDir path.join args to silence turbopack glob warning Turbopack's static analyzer treats `path.join(root, dyn1, dyn2)` as a multi-segment glob pattern and warned that it could match ~12k files in the project. Compose the relative subdir as a single string first, so `path.join` only sees one dynamic segment. Behavior unchanged โ€” the resulting path is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(input-completion): route auto-complete through generateObject for tracing Auto-complete is the first preset-task caller migrated to the structured- output path so it lands in `llm_generation_tracing` via the existing `onGenerateObjectComplete` hook. No new server hook, no global chat-side tracing. - `chainInputCompletion` now returns `{ messages, schema }` with a minimal `{ completion: string }` schema and a stable `INPUT_COMPLETION_SCHEMA_NAME` constant. JSON wrapping costs ~15-30 tokens against a 100-token completion budget โ€” negligible for the observability win. - `StructureOutputSchema` / `StructureOutputParams` accept optional `metadata`; `aiChatRouter.outputJSON` merges caller metadata over the default trigger so `{ scenario, promptVersion, schemaName }` reach `ModelRuntime.generateObject` options unchanged. - `IStructureSchema.description` is now optional to match the zod schema โ€” previously the TS type was stricter than runtime validation accepted. - `InputEditor` switches from `chatService.fetchPresetTaskResult` to `aiChatService.generateJSON`, reading `response.completion`. Streaming is dropped because auto-complete already buffers the full result before inserting; no UX change. - Reverts the unused `metadata` field that was added to `fetchPresetTaskResult` in the previous commit โ€” no current caller needs it now that input completion uses the generateObject path. Bumps `INPUT_COMPLETION_PROMPT_VERSION` to v2.0 because the system prompt gained an "output the completion field" instruction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(aiGeneration): extract the runtime-init + generateObject dance into a service Every server-side caller that produces structured output was repeating the same two-step ritual: `initModelRuntimeFromDB(...)` โ†’ `runtime.generateObject(payload, { metadata })`. `AiGenerationService` collapses it into one call so future cross-cutting concerns (default metadata, retry, observability hooks) have one place to land. - New `src/server/services/aiGeneration/index.ts` exposes `generateObject<T>(input, options)` and is unit-tested for provider resolution + payload/metadata pass-through. - `aiChatRouter.outputJSON` and `FollowUpActionService.extract` migrated to the service (other callers move organically when next touched). - Drops the unused `keyVaultsPayload` field from `StructureOutputParams` and the placeholder at the InputEditor call site โ€” key vaults are server-resolved from DB, the client never supplies them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(tracing): centralize TRACING_SCENARIOS const + inject AiGenerationService via trpc ctx - New `packages/const/src/llmGenerationTracing.ts` exports `TRACING_SCENARIOS` + `TracingScenario` type โ€” the single directory where every known scenario name lives. Adds `@lobechat/const` as a workspace dep on llm-generation- tracing so `TRACING_SCENARIO_REGISTRY` can reference the same literals. - Callers (FollowUpActionService, InputEditor) replace `'follow_up'` / `'input_completion'` string literals with `TRACING_SCENARIOS.FollowUp` / `.InputCompletion`, so a typo or a rename fails the type-check instead of silently drifting on the row. - `AiGenerationService` is now injected into the `aiChatProcedure` ctx middleware alongside `aiChatService`; `outputJSON` consumes it via `ctx.aiGenerationService` instead of new-ing it inside the handler. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โœจ feat(llm-generation-tracing): add lt/llm-tracing CLI + drop local-only storage_key - Add `lt` / `llm-tracing` CLI under @lobechat/llm-generation-tracing with `list` (recent records, --scenario filter, --json) and `inspect` (by tracing_id prefix or latest, --full, --json). - `FileTracingStore.save` now returns `{ key: null }` so dev DB rows leave `storage_key` empty instead of recording a non-resolvable local path; S3 store remains the source of truth for the real key. Add helpers `findByTracingId` / `getLatest` used by the CLI. - Wire `agentId` and `topicId` into `input_completion` tracing metadata from the chat input auto-complete call site. - Default `FileTracingStore` whenever NODE_ENV=development (drop the ENABLE_LLM_GENERATION_TRACING_LOCAL opt-in env var). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ๐Ÿ’„ style(llm-generation-tracing): prettier CLI output (tree + colors) Mirror the @lobechat/agent-tracing viewer style: - Inline ANSI color helpers (dim/bold/cyan/magenta/green/yellow/red). - Compact single-line header with id, scenario, version, model, status, time โ€” replaces the multi-line bullet list. - Tree structure with `โ”œโ”€`/`โ””โ”€` connectors instead of `โ”€โ”€ section โ”€โ”€` banners. - input arrays render per-message (role + char count + preview) rather than dumping raw JSON. - Small single-key outputs (e.g. `{ completion: "ๆ€Žไนˆๆ ท" }`) collapse to inline `key: "value"`. - `lt list` switches to a colored, properly padded table. Default view stays compact; --full expands system_prompt / input / schema bodies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * โ™ป๏ธ refactor(llm-generation-tracing): split `tracing` config out of `metadata` `options.metadata` was overloaded โ€” half tracing-specific structured fields (scenario / promptVersion / schemaName / agentId / topicId / ...), half free-form jsonb passthrough. Callers couldn't tell which was which, and the inputHint was always auto-extracted (useless when the prompt wraps the user's text in a template). This commit introduces a dedicated `tracing` option: - Add `TracingOptions` to @lobechat/llm-generation-tracing โ€” the typed shape callers import (agentId / topicId / inputHint / scenario / promptVersion / schemaName / systemPrompt / parentTracingId / metadata). - Add loose `tracing?: Record<string, unknown>` to GenerateObjectOptions and StructureOutputParams / StructureOutputSchema so the field flows through the runtime + TRPC. - Tracing hook now reads `context.options.tracing` for structured fields; it still falls back to `metadata.trigger` for the cross-cutting trigger string (ModelRuntime itself uses metadata.trigger for timing logs, so trigger stays on metadata). - Service `record()` accepts an explicit `inputHint`; otherwise falls back to auto-extraction from the first user message. Always truncated. - Free-form jsonb fields move to `tracing.metadata` (was unknown-key passthrough on `metadata`). - Call sites updated: - FollowUpAction now passes `tracing: { scenario, promptVersion, schemaName, topicId }` (previously `metadata`). - InputCompletion now passes `tracing: { agentId, topicId, inputHint: input, scenario, promptVersion, schemaName }` โ€” `inputHint` is the user's actual typed text, not the wrapper prompt's first user message. - `aiChat.outputJSON` router forwards both metadata and tracing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update inputCompletion.ts * ๐Ÿ› fix(llm-generation-tracing): stop duplicating provider into the row's metadata jsonb `provider` is already a first-class column on the `llm_generation_tracing` row, so auto-stamping it into the `metadata` jsonb column on every call was pure noise. The hook now writes the caller-supplied `tracing.metadata` verbatim โ€” empty/undefined when the caller had nothing to add. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>17 ๅฐๆ—ถๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰
chore: clean up LOBE-XXX code annotations (#15135) * chore: clean up LOBE-XXX annotations from codebase comments - Remove ใ€LOBE-XXXใ€‘ bracket markers - Remove LOBE-XXXX references from inline comments - Clean up test descriptions containing LOBE identifiers - Preserve linear.app URLs and code-level regex patterns - Generated: 2026-05-23 02:30:09 * ๐Ÿ› fix(tests): restore () in arrow callbacks broken by annotation cleanup The LOBE-XXX annotation cleanup script over-matched `(LOBE-XXXX', () =>` and stripped the callback `()`, leaving invalid syntax like `describe(..., => {` and `it(..., async => {` across 24 test files. This caused parse failures in Test Packages, Test Desktop App, Test Database lint, and Test App shard runs. Restoring `()` / `async ()` unblocks the suites while keeping the ticket-text cleanup intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ๐Ÿ› fix(hintFormat-test): restore label + ellipsis in stripMarkdownLinks fixture The annotation cleanup stripped `LOBE-8516` from a markdown-link's *label* (`[LOBE-8516](/task/T-1)` โ†’ `[](/task/T-1)`), which then survived `stripMarkdownLinks` because the pattern requires non-empty link text โ€” the test expected the link to disappear and asserted equality on a LOBE-free output. The same line also lost a `.` from the trailing `...` indicator in both input and expected strings. Substitute a neutral Chinese label (`ๅ‘ๅธƒ่ฎกๅˆ’`) so the link continues to exercise the multi-link substitution path, and restore the full `...` ellipsis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@lobehub.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>18 ๅฐๆ—ถๅ‰