| โจ 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 literal | 3 ๅคฉๅ |
| โป๏ธ 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 calls | 3 ๅคฉๅ |
| 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 snapshot | 1 ๅคฉๅ |
| โจ 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 prompts | 3 ๅคฉๅ |
| โป๏ธ 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 url | 1 ไธชๆๅ |
| โจ 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: Cursor | 1 ไธชๆๅ |
| ๐ 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 load | 3 ๅคฉๅ |
| โจ 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 prompts | 3 ๅคฉๅ |
| โจ 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 prompts | 3 ๅคฉๅ |
| โจ 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 prompts | 3 ๅคฉๅ |
| ๐ 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 attachment | 4 ๅคฉๅ |
| โจ 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 case | 2 ไธชๆๅ |
| ๐ 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 tokens | 2 ๅคฉๅ |
| โจ 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 ๅฐๆถๅ |