[pull] main from lobehub:main#495
Open
pull[bot] wants to merge 3935 commits into
Open
Conversation
✨ feat(tool): add word wrap toggle to tool arguments display Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 🐛 fix(cli): remove stale cron entry from generated man page The cron command was removed from program.ts but the generated man page still listed it. Regenerated via bun run man:generate. * 🔖 chore(cli): release 0.0.15 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 🐛 fix(portal): allow TodoList to scroll when expanded content exceeds max-height
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 🐛 fix(tasks): route 1–N hotkey to the open submenu instead of defaulting to status
The base-ui SubmenuTrigger doesn't propagate antd's `onTitleMouseEnter`, so
the hover ref in the right-click context menu never updated and every number
press fell back to the status submenu. The standalone Priority/Status tag
dropdowns also showed 1–N hints without binding any handler at all.
- Detect the currently open submenu via `data-popup-open` + a per-submenu
`data-task-submenu` marker on the icon; numbers are ignored when no
submenu is open.
- Install a keydown listener on TaskPriorityTag / TaskStatusTag while their
dropdown is open so the hint numbers actually fire.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 🐛 fix(scheduler): keep Continuous unchanged while editing Max runs
Clearing the Max runs input previously emitted maxExecutions=null, which the
form re-interpreted as Continuous and auto-checked the checkbox mid-edit
(disabling the input before the user could type the replacement number).
Track Continuous as its own state derived from the persisted prop. On clear
we hold the input empty locally without touching Continuous or emitting,
and unrelated emits fall back to the persisted value so they can't flip the
checkbox either.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(tasks): always show comment Send button and unify action labels
- Make the Send button visible by default in CommentInput / FeedbackInput
(greyed out when empty) so the field reads as an input instead of vanishing
affordance.
- Align topic action menu labels to Title Case (Stop Run / Open Run /
Copy Topic ID / Copy Operation ID / Copy Link) to match the rest of the
Action microcopy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ⚡ perf(scheduler): seed SchedulerForm from props once and own state locally
The previous prop→state useEffects re-synced every time the parent prop
updated, which during the async updateSchedule → refreshTaskDetail roundtrip
clobbered the user's in-flight edits with stale store values — felt awful
on rapid changes.
Drop the three sync useEffects and seed local state from props only at
mount via a lazy useState initializer. The form now owns its values
optimistically; cross-task safety comes from `key={taskId}` on the
parent so the form remounts cleanly when switching tasks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(scheduler): Notion-style timezone picker — drop underscores, offset on the right
Underscored labels like 'America/New_York (EST/EDT, UTC-5/-4)' read poorly in
the dropdown. Split each option into `label` (underscore → space) and `offset`,
and render the row with the city on the left and a subtle gray offset on the
right, in line with how Notion's timezone picker presents this.
IANA `value` keeps the underscore so cron and Drizzle stay happy. Search now
filters by the human label only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(scheduler): keep zone abbreviations in the timezone offset column
Show 'EST/EDT · UTC−5/−4' instead of just 'UTC−5/−4' so users can recognize
the zone by its common abbreviation alongside the offset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(scheduler): drop awkward ':30' suffix from hourly summary
'Every hour:00' / 'Every 2 hours:30' read like glitched concatenations. Cron
storage always rounds to 0 or 30 minutes, so call out the non-zero case as
'at half past' and stay implicit on the top of the hour.
- Every hour
- Every hour at half past
- Every 2 hours
- Every 2 hours at half past
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(scheduler): collapse advanced settings by default
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ⚡ perf(tasks): coalesce post-write refresh and add timezone search
Two follow-up fixes for the AgentTasks scheduler popover.
##### Optimistic schedule writes, single coalesced refresh
Rapid edits in the scheduler form (toggling daily/hourly/weekly, weekday
chips, time, etc.) each triggered `taskService.update` + a full
`internal_refreshTaskDetail` per call. With overlapping requests the
refreshes returned intermediate server state and bounced TaskTriggerTag /
summary text away from the user's latest choice.
- Add `#withCoalescedRefresh` on the task config slice: it tracks a per-task
pending-writes count and only fires `internal_refreshTaskDetail` after the
LAST in-flight write settles.
- Give `updateSchedule` an optimistic `internal_dispatchTaskDetail` so
external readers see the new pattern/timezone/maxExecutions immediately.
- Route both `updateSchedule` and `setAutomationMode` through the coalescer.
##### Timezone picker — search input at the top
The dropdown had antd's implicit type-into-trigger search, which most users
miss. Add a `SearchBar` inside `dropdownRender`, filter the options against
label/value/offset locally, and show an empty state when nothing matches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(scheduler): weekday chips only show background when selected
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 🐛 fix(tasks): dispatch optimistic schedule under nested 'schedule' field
`TaskDetailData` exposes schedule as `schedule.{pattern,timezone,maxExecutions}`,
not flat columns. The previous optimistic dispatch used the DB-style flat keys,
which broke type-check and would never reach the in-memory selectors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(tasks): drop Cmd+Backspace shortcut on the Delete menu item
Header dropdown only advertised the hotkey (no handler), and the right-click
context-menu handler is gone too — keeps the visual claim honest and
removes the irreversible-by-keystroke footgun.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ✅ test(agent-signal): pin `now` in proposal activity tests to fixture window
Two cases relied on the real system clock; once today crossed the
fixture's default `expiresAt` (2026-05-12), pending proposals were
classified as expired and the assertions broke.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 💄 style(tasks): hide '#' placeholder icon for heterogeneous agent topics
Claude Code / Codex topics aren't chat topics in the usual sense, so the
fallback HashIcon in the sidebar row reads as noise. Skip it when the
current agent has a heterogeneousProvider.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 🧪 test(tasks): provide agentMap in TopicItem store mock
`isCurrentAgentHeterogeneous` walks through `currentAgentConfig` which
indexes `s.agentMap[agentId]`. Extend the mocked store state to include
an empty `agentMap` so the selector resolves to `undefined` (= not
heterogeneous) instead of throwing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r Anthropic 5MB limit (#14711) * 🐛 fix(utils): cap image binary at 3.75MB so base64 payload stays under Anthropic's 5MB limit Anthropic enforces the 5MB image cap on the base64-encoded payload, not the binary file. Base64 inflates by ~4/3, so a 4.7MB binary file becomes 6.27MB once encoded and trips `messages.*.content.*.image.source.base64: image exceeds 5 MB maximum`. The previous MAX_IMAGE_BYTES of 5MB matched against file.size, letting these images through compression untouched. Lower the threshold to floor(5MB * 3/4) ≈ 3.75MB in both the frontend canvas compressor and the server-side Sharp fallback so the progressive shrink loop keeps going until the base64 payload is safely under the cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(utils): tighten image binary cap to 3MB for extra base64 headroom Drop MAX_IMAGE_BYTES from 3.75MB (exact 5MB-base64 boundary) to a flat 3MB so the encoded payload lands around 4MB — clear of any per-provider rounding or jitter at the 5MB hard limit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…): unified structure of service, unified tool, unified name and concepts (#14699)
…14696) * ♻️ refactor(spa): use __DEV__ define instead of process.env.NODE_ENV The Vite `__DEV__` define and its global type declaration are already in place (plugins/vite/sharedRendererConfig.ts, src/types/global.d.ts). Replace `process.env.NODE_ENV` checks across SPA-only files with the `__DEV__` boolean so the bundler can statically eliminate dev-only branches in production builds. Server-side files (app/, server/, libs/next, libs/trpc, libs/better-auth, envs, instrumentation) and modules that are also imported by Next.js SSR pages (e.g. components/Loading/BrandTextLoading) are intentionally left untouched to avoid runtime `__DEV__ is not defined` errors. * fix(vitest): define __DEV__ and related constants for test environment Vitest runs outside the Vite SPA build pipeline, so the __DEV__ define injected by sharedRendererDefine was not available during tests. This caused ReferenceError: __DEV__ is not defined in any test file that transitively imports code using the __DEV__ constant. Add a block to vitest.config.mts that mirrors the SPA defines: - __DEV__: true (test is not production) - __CI__: mirrors process.env.CI - __ELECTRON__/__MOBILE__: false (not testing platform-specific code) * fix: replace missed isDevEnv reference with __DEV__ in AgentMockDevtools
…#14695) * 🐛 fix(desktop): focus onboarding auth success state * 🐛 fix(desktop): reset pendingLoginMethod on auth failure/cancel paths Clear pendingLoginMethod in authorizationFailed, authorizationProgress cancelled, and remoteServerSyncError handlers to prevent users getting stuck without a Get Started path when a re-auth attempt fails but a prior authorization is still valid. * Delete src/routes/(desktop)/desktop-onboarding/features/LoginStep.test.tsx --------- Co-authored-by: Innei <inbox@innei.in>
* ✨ Add default agent model setting * 💄 Refine service model assignments UI * 💄 Clarify optional service model features
#14713) * ✨ feat(builtin-tool-task): expose lobe-task to users and add schedule config The task tool is now generally available — flip it from a scenario-only internal tool to a user-toggleable recommended skill, and let the LLM configure recurring execution (cron or heartbeat) via createTask / editTask. - Drop `discoverable: false` + `hidden: true` from TaskManifest registration - Add `lobe-task` to RECOMMENDED_SKILLS so it stays installed by default - Remove the USER_HIDDEN_BUILTIN_TOOL_IDS allowlist (only contained lobe-task); update selectors and AgentTool to stop filtering it out - Extend createTask / createTasks / editTask with `automationMode`, `schedulePattern`, `scheduleTimezone`, `heartbeatInterval`; editTask also accepts `maxExecutions` - Route schedule columns through taskService.update and maxExecutions through taskService.updateConfig (server merges into tasks.config.schedule); refresh detail once at the end of editTask Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(builtin-tool-task): split schedule config into dedicated setTaskSchedule tool editTask was the wrong place for schedule fields — schedule needs its own verb so the LLM (and any future human-in-the-loop review) can audit cron / heartbeat changes separately from generic field edits, and createTask should stay a pure "make a task" verb without automation knobs. - Drop automationMode / schedulePattern / scheduleTimezone / heartbeatInterval from createTask + createTasks, and drop them plus maxExecutions from editTask - Add new `setTaskSchedule(identifier, automationMode?, schedulePattern?, scheduleTimezone?, heartbeatInterval?, maxExecutions?)` API with its own manifest entry, executor method, types, i18n key, and inspector - Schedule columns still route through taskService.update; maxExecutions still routes through taskService.updateConfig (server merges into tasks.config.schedule) — same wiring, just moved into the dedicated tool - Update systemRole to advertise setTaskSchedule + keep editTask description clean of schedule mentions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a client-side feature flag override panel that lives behind a floating button in dev builds. Overrides are persisted to localStorage and merged into useServerConfigStore.featureFlags so existing flag consumers see the toggled value without any callsite changes. The panel is gated by NODE_ENV plus a localStorage opt-in (LOBE_DEV_FEATURE_FLAG_PANEL_ENABLED = "1"); prod builds tree-shake the entire feature.
💄 style: standardize header action icons to DESKTOP_HEADER_ICON_SMALL_SIZE Unify icon sizing across sidebar and header action buttons by replacing hardcoded sizes and DESKTOP_HEADER_ICON_SIZE with DESKTOP_HEADER_ICON_SMALL_SIZE for consistent visual density. Affected components: - SideBarHeaderLayout back button - ToggleLeftPanelButton default size - BackButton default size - Agent sidebar header chevron - InboxButton notification icon
* fix: update Task page placeholder copy * fix: update Task page placeholder copy (en-US)
… document tree (#14714) - Insert pending rows immediately on create folder/document, with optimistic SWR mutation that rolls back on server error - Auto-focus rename input on newly created items via onPendingInserted callback - Defer rename commits for pending rows until the server create resolves, then rename against the real row id - Optimistic recursive delete closes the confirm modal instantly, removes target + descendants from the tree, and rolls back on failure - Fix folder path canonicalization in ExplorerTree rename lookup (toCanonicalTreePath ensures trailing slash for folders) - Export getItemPathFromEventPath for composed-path–based item resolution - Add unit tests for toCanonicalTreePath and ExplorerTree event helpers
…g-tree changes (#14716) ✨ feat(review-panel): hover revert button to discard per-file working-tree changes Add a hover-revealed Undo icon to each file row in the Review panel's unstaged view. Clicking opens a Popconfirm; confirming runs a new `git.revertGitFile` IPC that restores the file from HEAD (or unstages + deletes when the path doesn't exist at HEAD, covering staged-add and untracked entries). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4708) Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: image
…#14724) * 💄 style: shrink desktop header icons and tighten sidebar/home density Switches all desktop header action icons from DESKTOP_HEADER_ICON_SIZE to DESKTOP_HEADER_ICON_SMALL_SIZE, and tightens vertical gaps in the home sidebar, recents list, and nav header layout for a denser, calmer look. * ♻️ refactor(agent-tasks): migrate task menus and scheduler select to @lobehub/ui base-ui - TaskPriorityTag / TaskStatusTag: replace antd Dropdown with base-ui DropdownMenu and adopt the ContextMenuItem / MenuInfo typings. - useTaskItemContextMenu: drop the DOM data-attribute submenu marker in favour of an internal activeSubmenuRef tracked via onOpenChange. - TaskScheduleConfig / SchedulerForm: swap @lobehub/ui Select for the base-ui Select and replace the custom SearchBar dropdownRender with antd Select showSearch for timezone filtering. * ♻️ refactor(review): migrate review dropdowns to @lobehub/ui base-ui DropdownMenu Swap the antd Dropdown trios (mode picker, base-ref picker, more menu) in the agent working-sidebar Review pane for the base-ui driven DropdownMenu, matching the recent task menus / scheduler migration. Also tighten the sidebar header paddingInline from 16 to 4 to align with the surrounding density polish. * 🐛 fix(tasks): replace unsupported onOpenChange with onTitleMouseEnter in context menu
…14728) 🌐 i18n: add missing translations for task-schedule and review keys across 16 locales Adds 14 missing i18n keys to all non-zh-CN locales (ar, bg-BG, de-DE, es-ES, fa-IR, fr-FR, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-BR, ru-RU, tr-TR, vi-VN, zh-TW): chat.json (11 keys): - taskSchedule.summary.everyNHoursHalfPast - taskSchedule.summary.hourlyHalfPast - taskSchedule.timezoneSearchEmpty - taskSchedule.timezoneSearchPlaceholder - workingPanel.review.revert (and 7 sub-keys) plugin.json (1 key): - builtins.lobe-task.apiName.setTaskSchedule setting.json (2 keys): - serviceModel.modelAssignments.title - serviceModel.optionalFeatures.title These were added in recent commits but the automated i18n sync had not yet propagated them to non-Chinese locales.
…text menu (#14727) * ✨ feat(markdown): render <user_feedback> task prompt blocks as a card `buildTaskRunPrompt` wraps the user's pre-run comments in a `<user_feedback>` block alongside `<task>`. The Task plugin captured `<task>` into a card, but `<user_feedback>` had no plugin and leaked into the chat as raw XML. Because CommonMark only treats tag names matching `[a-zA-Z][a-zA-Z0-9-]*` as html, the underscore in `user_feedback` puts the opening/closing tags inside a `paragraph` as plain text — so the new remark plugin walks paragraph children rather than html nodes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(task-card): drop standalone status row + Agent/Parent/Topics, inline semantic status badge The status/Priority row, Agent, Parent and Topics fields aren't useful when the task card is rendered inside the topic chat drawer (the drawer already exposes that context). Move the task status to a compact badge beside the identifier and reuse `taskDetail.status.*` for the label so "scheduled" reads as "Scheduled" / "已排期". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(user-feedback): compact one-line header + left-border quote-style card Slims the card down to a single 12px header line ("User feedback · N comments") with a small 12px icon, and wraps the whole block in a subtle fill + 2px left-border accent so it reads as a quoted aside and visually separates from the task card that follows in the same user message body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(user-feedback): drop fill + radius, render as plain left-rail blockquote The filled card competed visually with the unstyled task block that sits beside it in the same message body. Reducing to a 2px left-rail quote without background or border-radius lets both blocks read as parts of the same user message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(user-feedback): collapsible card with task-style head + bottom divider Default-collapsed `<details>` whose summary mirrors the task title row (32px icon + bold label + small count badge), with a bottom split-line that doubles as a divider between the user feedback head and the task card that follows in the same message body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(user-feedback): strip default markdown details card chrome @lobehub/ui Markdown applies bg + padding (0.75em 1em) + box-shadow + border-radius to every nested <details>, which made the user_feedback head read as a wide standalone card sitting awkwardly on top of the inline task title. Override the chrome (with !important — the lib selector wins on specificity otherwise) so the head sits flat in the message body, with only the bottom split line separating it from the task that follows. The lib's right-side disclosure chevron is kept. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(user-feedback): match task card's 12px symmetric divider spacing Add a 12px margin-bottom so the gap below the user_feedback bottom rule mirrors the 12px above it, matching the symmetric 12px the task card already uses around its own internal divider. Without this, the user_feedback rule sat flush against the T-31 row while the next rule below T-31 had a 12px gap on both sides — visually uneven. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(task-card): drop status badge from task title row The task drawer header and the schedule strip on the task detail page already convey status; surfacing it again on the task card inside the chat body just added noise. Drop the badge along with the now-unused KNOWN_STATUSES / isKnownStatus / TaskStatusIcon / useTranslation plumbing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(tasks): add "Run now" item to task card context menu Available only for backlog and completed tasks; mirrors the inbox-agent fallback used by the detail-page Run Now action. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(topic-list): preserve `#` icon placeholder for heterogeneous agents Returning null for the icon slot collapsed the row layout, so titles on heterogeneous-agent topics (Claude Code, Codex, …) no longer aligned with sibling rows. Render the same HashIcon with visibility:hidden so the box is preserved without showing the glyph. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…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>
… locale (#14730) * 🐛 fix(task-card): localize date format independent of dayjs global locale Task card was rendering "5月 12" under English UI because t('time.formatThisYear') returned the English "MMM D" format, but dayjs's global locale was still zh-cn, making MMM resolve to the Chinese short month name. Thread the i18n language into formatTaskItemDate so the date is rendered with the same locale as the format string, decoupling it from dayjs's global state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(task-card): import missing GenericItemType + type Run now onClick Pre-existing CI regression from #14727 surfacing on every PR: the Run now context menu satisfies-clause references GenericItemType without importing it, and the onClick lacks a MenuInfo annotation, so tsgo widens the divider literal's `type` to `string` and rejects the whole context menu array. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntionHandler, stepPresentation (#14441) * ♻️ refactor(agent-runtime): extract CompletionLifecycle Pull terminal-state handling out of AgentRuntimeService into a dedicated class: - buildLifecycleEvent (was buildCompletionLifecycleEvent) - emitSignalEvents (was emitCompletionSignalEvents) - dispatchHooks (was dispatchCompletionHooks) - extractErrorMessage These four methods formed one cohesive vertical: build the lifecycle event payload, emit completion AgentSignal source events, dispatch onComplete/onError hooks, and write error back onto the assistant message row. extractErrorMessage was a private helper used by all three plus by the trace-snapshot finalize call site, so it becomes a public method on the class. Call sites in executeStep / executeSync change from `this.{emit|dispatch|extract...}` to `this.completionLifecycle.{...}`. Tests: extractErrorMessage.test.ts → CompletionLifecycle.test.ts, instantiating CompletionLifecycle directly instead of going through AgentRuntimeService — drops a pile of unrelated mocks. AgentRuntimeService.ts: 2084 → 1918 (-166). All 81 agentRuntime tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(agent-runtime): extract HumanInterventionHandler Pull the 165-line `handleHumanIntervention` method out of AgentRuntimeService into its own class, splitting the three branches (approve / rejectAndContinue / rejectAndHalt) into private methods so each fits in one screen. Routing in `process()` now reads top-to-bottom: detect approval, then rejection, then unsupported humanInput. The handler depends only on `serverDB` (for the messagePlugins lookup) and `messageModel` (for tool/plugin updates) — much narrower than AgentRuntimeService's full surface, so the extracted unit is easier to unit-test in isolation. Drop the unused `runtime: AgentRuntime` parameter from the public API: the original method threaded it through but never called it. Tests: handleHumanIntervention.test.ts → HumanInterventionHandler.test.ts — same 17 cases, but instantiate the handler directly instead of constructing a full AgentRuntimeService with 11 module mocks. Tighter arrange step, same coverage. AgentRuntimeService.ts: 1918 → 1742 (-176). All 81 agentRuntime tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(agent-runtime): extract step presentation builder Pull the ~150-line `phase`-branching block out of executeStep into a pure `buildStepPresentation` function. The block did three things in sequence: derive content/reasoning/toolsCalling/toolsResult from the runtime step result, build a one-line stepSummary for logging, and assemble the StepPresentationData DTO consumed by afterStep hooks / snapshot recorder / callbacks. The function takes only the stepResult and an executionTimeMs; no service state needed. Comes with a `formatTokenCount` helper for the log line (12345 → 12.3k, 2_500_000 → 2.5m). executeStep keeps the log call inline (one line, references presentation fields directly) and reads `content` / `toolsCalling` off presentation for downstream tracking + truncation logic. 13 new unit tests: phase=tool_result (json + string + isSuccess paths), phase=tools_batch_result, done event, llm_result with content/reasoning/ tools, empty fallback, cumulative usage zero-fallback, stepUsage forwarding, and formatTokenCount edges. AgentRuntimeService.ts: 1742 → 1601 (-141). All 94 agentRuntime tests pass (was 81, +13 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…14584) 🐛 fix: prevent synthetic scroll from shrinking spacer
* ✨ feat(cc): render Linear MCP tool calls with branded inspector CC emits Linear MCP tools as `mcp__claude_ai_Linear__<verb>_<noun>` — the default inspector and the collapsed summary surface those raw names, which read as `Mcp__claude_ai_ Linear__get_issue` after title-casing. Adds a generic Linear MCP inspector that: - Shows the monochrome Linear logomark + "Linear" product prefix - Renders the action as a single pill split into action / value halves (e.g. `Get issue | id: LOBE-8743`) - Detects `parentId` and surfaces it with a CornerLeftUp icon, either in the chip's value half (when parent is the primary arg) or as a secondary badge after the chip (mirrors the parent visual used by AgentTask UI) - Hard-caps chip text at 60 chars so long comment bodies / search queries don't push the row off-screen Also humanizes the collapsed-workflow summary via a `formatLinearMcpShortLabel` helper exported from `@lobechat/builtin-tool-claude-code/client`, so the bundle row reads "Linear · Get issue" instead of the raw tool name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(cc): render WebSearch and WebFetch tool calls with custom inspector CC's web tools were falling through to the generic tool UI because `ClaudeCodeApiName` and the render/inspector registries hadn't been extended. Adds dedicated inspector (query/url chip) and result card (text for search, markdown for fetched pages) for both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(cc): isolate Linear MCP label helper to avoid antd-style mock break `Group.test.tsx` mocks `antd-style` with only `createStaticStyles`. The previous wiring imported `formatLinearMcpShortLabel` through the `@lobechat/builtin-tool-claude-code/client` barrel, which transitively loads `LinearMcp.tsx` → `@lobechat/shared-tool-ui/styles` → `keyframes`, crashing the mock. Splits the pure label utilities (LINEAR_MCP_PREFIX, parseToolName, staticLabelFor, formatLinearMcpShortLabel, LINEAR_MCP_TOOL_NAMES) into `linearMcpLabels.ts` with no React/antd-style imports, exposes it as `@lobechat/builtin-tool-claude-code/client/labels`, and switches the consumer in `toolDisplayNames.ts` to that subpath. The inspector component keeps importing the same helpers locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 ui(hetero): land manual workflow expand at full level Heterogeneous agent workflows often run 40+ tool calls. When the user collapsed the workflow and clicked the header to re-expand, it landed at the height-capped `semi` state and hid most of the chain. Now we infer a "fully expanded experience" from `defaultWorkflowExpandLevel` — any phase opting into `full` routes the manual expand straight to `full` instead of the legacy `semi` cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eartbeat (#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>
…tantGroup (#14859) * ✨ feat(hetero-agent): emit externalSignal on Monitor-callback steps + reader-side SignalCallbacksNode LOBE-8998 Phase 1 — data-layer work. Adapter detects repeated tool_results on the same tool_use.id (Monitor stdout pushes etc.) and tags the next stream_start(newStep) with an externalSignal peer field. Executor stamps metadata.signal on the new assistant message. conversation-flow MessageCollector / ContextTreeBuilder collect signal-tagged toolless assistants into a SignalCallbacksNode appended inside AssistantGroup children. UI rendering deferred to a follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): keep parentId chain alive across toolless middle steps LOBE-8993: when a CC step produced only text (e.g. Monitor stdout drove Claude to reply without invoking a tool), the next step's parentId fell back to the previous assistant. MessageCollector only walks the assistant → tool → assistant zigzag, so each Monitor stdout line split into its own bubble. Carry the most recent tool result_msg_id across step boundaries via a `lastToolMsgIdEver` tracker so toolless middle steps still chain back to the originating tool result. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(chat-ui): render SignalCallbacks block inside AssistantGroup for Monitor-style callbacks Adds the UI layer of LOBE-8998. FlatListBuilder snapshots signal-callback groups onto the virtual AssistantGroup message via UISignalCallbacksBlock (new typed field on UIChatMessage) and marks each callback message processed so it does NOT render as a separate top-level bubble. AssistantGroup reads the field and renders a collapsible <SignalCallbacks> component under the main Group content, one block per source tool. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): detect Monitor callbacks via system task lifecycle instead of repeat tool_result The previous detection model (count repeat tool_result per tool_use.id) was based on a wrong assumption — Monitor's stdout pushes are NOT delivered as additional tool_result events for the same tool_use.id. Verified against a real `claude -p` trace: Monitor emits ONE tool_result (the initial "Monitor started" ack), then each subsequent stdout line triggers a `system init` + new `message_start` cycle within the same CLI process. The actual lifecycle signal is `system task_started` (long-running tool registers) followed by `system task_notification` (terminal). New detection: a `message_start` that opens a new turn WITHOUT a preceding `user` event, while at least one task is active, is a signal callback. `task_started` records `{task_id → tool_use_id}`; `task_notification` drops it. Verified against the recorded CC trace: 5/5 reactive turns get tagged with correct sequence and source tool, the natural confirmation turn and the post-task summary turn are correctly excluded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(hetero-agent): keep CC post-task summary in same group + dedicated Monitor inspector (LOBE-8998) The post-task summary turn (fired after `system task_notification` ends a long-running tool) was spawning its own AssistantGroup because the collector only followed the first non-signal toolless sibling under a tool_result — it never saw the summary that came after the SignalCallbacks. Adapter now stamps `signal.type = 'task-completion'` on the summary turn so the collector keeps it inside the same group, rendered AFTER the SignalCallbacks accordion (initial reply → callbacks → summary, in creation order). Also adds a dedicated `MonitorInspector` (lucide `Monitor` icon, chip shows description / command, trailing timeout label) so the Monitor tool call line stops falling back to the generic `claude-code > Monitor` display, and tightens the Flexbox spacing around SignalCallbacks + taskCompletions inside the AssistantGroup so the three sections read as one connected reply rather than disconnected blocks. Adapter: arm `pendingTaskCompletion` on `task_notification` (last-task- wins), consume it on the next natural `message_start`, clear on `result` so it never leaks across LLM runs. Tests: adapter (74) + executor (56) + conversation-flow (126) all green. Verified end-to-end in Electron with a 5-tick Monitor run — single AssistantGroup with the natural narrative inside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(conversation-flow): skip signal callbacks when locating the group tail `findLastNodeInAssistantGroup` blindly took `toolNode.children[0]` when walking past a tool, so for the common `[signal callback, next tool-using assistant]` order the tail landed on the callback (a leaf) and `findNextAfterTools` returned null — truncating the AssistantGroup and omitting follow-up messages after the real last assistant. Mirror the signal-skip already used in `collectAssistantGroupMessages` (LOBE-8998). --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…l schema (#14740) * fix(gemini): strip enum from non-STRING types in tool schema * fix(gemini): handle nullable types and definitions recursion in schema sanitizer Addresses review feedback on #14740 for LOBE-8661: 1. Preserve nullable string enums (type: ['string', 'null']) - Replace strict type equality checks with isStringType/isObjectType helpers that handle both single-string and array types. - Apply to both sanitizeGeminiSchema and convertOpenAISchemaToGoogleSchema. 2. Recurse into definitions/$defs schema maps - When a tool schema stores non-compliant enum/required inside definitions/$defs and references it with $ref, the walker now visits these schema maps as well. Test coverage: 6 new cases for nullable type preservation and definitions/$defs recursion. * 🐛 fix(test): wrap sanitizeGeminiSchema inputs in valid JSON Schema The 3 cases were passing bare property maps directly to the sanitizer, which only recurses through `properties`/`items`/combinators/`$defs` — so the inner `enum`/`required` were never visited and assertions failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Arvin Xu <arvinxx@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emplate (#14874) * ♻️ refactor(agent-settings): drop Meta and Documents tabs Remove the 助理信息 (Meta) and 文档 (Documents) tabs from the agent profile/settings UI. Default chat-settings tab falls back to Opening for non-inbox agents. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(agent-chat): restore inputTemplate field in Chat Preferences Add back the User Input Preprocessing (inputTemplate) form field that was removed in 2.0. The pipeline (InputTemplateProcessor, i18n, types) was kept intact when the UI was dropped — only the form entry is added back. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…man topic status (#14870) * ✨ feat(cc): support TaskCreate / TaskUpdate / TaskList tools (CC 2.1.143+) Add adapter accumulator, inspectors and Todos panel for CC's imperative task trio that replaces TodoWrite. TaskUpdate's status flip is surfaced as a per-call chip ("Completed: Read hosts") and the Todos panel header mirrors that label, with subject resolved from pluginState by CC-assigned task id. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(cc): escape-toggle AskUserQuestion + waitingForHuman topic status AskUserQuestion intervention — mode-exclusive escape hatch: - Mirror `lobe-user-interaction`'s "Or type directly" toggle: form picks and the freeform reply are mutually exclusive, not stacked. Default view shows the multi-choice options; clicking "Or type directly" swaps the body to a single TextArea, and "Back to options" returns. - Submit sends either per-question picks OR `{ __freeform__: <text> }` (never both). Bridge formatter (`AskUserMcpServer.formatAnswerForCC`) forwards the text verbatim to CC when `__freeform__` is the payload, bypassing the `User answers:\n- <q>: <a>` framing — keeps the model prompt clean when the user opts out of the structured form. - Draft persistence resumes the user back into escape mode when `__freeform__` is non-empty; an empty draft starts in form mode. Timeout fallback respects escape mode: non-empty text submits as-is rather than being discarded for option-1-of-each defaults. - Render swaps to a single "user reply" card with the typed text when `__freeform__` is present; otherwise renders the Q&A pairs as before. Topic status `waitingForHuman`: - Add new enum value to `ChatTopic` status — TS-only widening (the drizzle `text({enum})` is not a `pgEnum`, no migration needed) — wired through types + zod router schema. - Sidebar topic row renders a warning-colored Hand icon when an intervention is pending so the waiting state reads from the topic list. - `heterogeneousAgentExecutor` flips status to `waitingForHuman` when an AskUser intervention is raised and back to `running` once the bridge resolves; `conversationControl.submitHeteroIntervention` also flips back to `running` after the user submits / skips / cancels. The natural `runtime_end → writeTopicStatus('active')` takes over. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(explorer-tree): drop doubled outline on selected file rows Add `--trees-selected-focused-border-color-override: transparent` to both ExplorerTree consumers (working-sidebar Files + AgentDocuments). `@pierre/trees` draws an outline via `::before` on focused+selected rows that visually fights with the filled `--trees-selected-bg` highlight — the existing `--trees-border-color-override: transparent` only controls structural borders, not this focus outline. Keyboard focus ring on unselected rows stays intact (a11y). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4880) Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…kill (#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>
…ol dispatch (#14817) * 🐛 fix(agent-runtime): honor per-tool timeout end-to-end for client tool dispatch (LOBE-8436) Server BLPOP was hardcoded to 60s and ignored the LLM-supplied `timeout` in `tool_call.arguments`, so long-running shell commands consistently failed with a server-side timeout while the desktop runner was still happily executing. Renderer also never raced its own deadline, leaving it free to hang past the server budget. Plumb a per-tool timeout through the full chain: - New `resolveToolTimeoutMs` (server) — priority: `args.timeout` > `manifest.api[apiName].defaultTimeoutMs` > 120s global default, clamped to [1s, 800s] (cloud function ceiling). - `dispatchClientTool` accepts `timeoutMs` in ctx; constants moved into `resolveToolTimeout.ts`. Default 60→120s, max 270→800s. - `RuntimeExecutors` calls the resolver at both client-dispatch sites (single + batch) using the LLM-parsed args and the effective manifest. - `LobeChatPluginApi` (types + context-engine) gains `defaultTimeoutMs?: number` so tool authors declare per-API budgets. - `LocalSystemManifest` sets per-API defaults: runCommand 120s, read/write/edit/list 30s, grep/glob/search/move 60s, killCommand 10s. - `local-file-shell/runner.ts` internal kill cap raised 600→800s to match the server ceiling. - Renderer `clientToolExecution.ts` rewritten to (1) race executor against `executionTimeoutMs - 500ms`, abort the operation's AbortController, and send `client_executor_timeout` on overrun; (2) read `gatewayConnections[operationId]` live on every send so reconnects between dispatch and result are picked up; (3) wrap in try/finally with an exactly-once `sent` guard so every `tool_execute` yields exactly one `tool_result` even on logic gaps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(test): drop unused @ts-expect-error and tighten timeout assertion CI lint failed on tsgo: an `@ts-expect-error` directive in `resolveToolTimeout.test.ts` was unused (the field's `unknown` value type happily accepts a string at compile time), and the `sendToolResult.mock.calls[0][0]` access in `clientToolExecution.test.ts` tripped TS2493/TS2532 because vitest typed `calls` as an empty tuple. Cast the test-only string value through `unknown` for the resolver defense check; merge the budget assertion into the `toHaveBeenCalledWith` matcher via `expect.stringContaining('2000ms')` so we never index into `mock.calls` by hand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o /client (#14888) * 🐛 fix(local-system): forward all grepContent params + move executor to /client The local-system executor was reducing the agent's full grepContent params ({pattern, glob, output_mode, -i/-n/-A/-B/-C, multiline, head_limit, type, scope, ...}) down to {directory, pattern} before handing them to the runtime. `directory` isn't recognized by the IPC layer (which expects path/scope), so cwd silently fell back to process.cwd() (= apps/desktop/ in dev), and with glob/-i/output_mode all stripped grep matched anything containing the pattern across the whole tree — explaining LOBE-8666's dist/main/index.js + tsconfig.tsbuildinfo leaks. Also audited the rest of the executor layer: - listFiles: forward `limit` (was silently dropped → manifest default of 100 always won). - getCommandOutput: forward `filter` (was silently dropped → no regex filter ever applied to streamed output). - runCommand: mirror `run_in_background` → `background` so ComputerRuntime.RunCommandState.isBackground reflects reality (the IPC handler reads run_in_background directly, so the command itself ran in background — only the state field was wrong). Structure: moved src/executor/ → src/client/executor/ to match the other builtin-tool packages (task / lobe-agent / knowledge-base) and consolidate renderer-only code under /client. Dropped the `./executor` package subpath; consumers now import from `…/client`. Defensive: also added a resolveSearchPath helper in apps/desktop's contentSearch module that reads params.scope as a fallback for params.path, so any non-executor caller (direct IPC, future Gateway path) that passes `scope` still gets routed correctly instead of falling through to process.cwd(). Regression coverage: - grepContent full forwarding (LOBE-8666 case + all optional flags) - listFiles.limit forwarding - getCommandOutput.filter forwarding - runCommand.run_in_background → background mirror - resolveSearchPath fallback semantics (3 cases in base.test.ts) Verified end-to-end via Electron CDP — tool.invokeBuiltinTool with the LOBE-8666 params returns 9 clean .ts matches (no dist/, no .tsbuildinfo); listFiles {limit:3} returns 3 files (totalCount 10); runCommand {run_in_background:true} reports state.isBackground=true. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(desktop): readFile fails with `protocol.registerSchemesAsPrivileged should be called before app is ready` Two-part fix for a regression where reading any text/JSON/source file via the local-system `readFile` tool surfaced an Electron protocol error in the response content. The error fired *after* `stat()` succeeded (so missing-file ENOENT was unaffected), making it look like the file couldn't be parsed. ## Root cause Stack trace (instrumented `read.ts` to capture it): ``` Error: protocol.registerSchemesAsPrivileged should be called before app is ready at new App (apps/desktop/dist/main/index.js:105339:21) at Module.<anonymous> (apps/desktop/dist/main/index.js:105615:11) at Module._compile (...) ``` `Module._compile` on `dist/main/index.js` means the main bundle is being freshly evaluated as a CJS module — re-running its top-level `var app = new App(); …; app.bootstrap();` after the real Electron-launched App was already ready. Triggering chain: agent calls `readFile` → main runs `loadFile(path)` from `@lobechat/file-loaders` → `getFileLoader('txt')` → `await import('./text')`. The lazy text-loader chunk back-references the main bundle for the shared util `detectUtf16NoBom`: ```js // dist/main/text-Cbmlmtca.js const require_index = require("./index.js"); // ← re-evaluates main … const variant = require_index.detectUtf16NoBom(buffer); ``` Electron's main entry is not in Node's CJS module cache (it's bootstrapped separately), so this `require("./index.js")` triggers a fresh compile of the main bundle — re-running `new App()` and `protocol.registerSchemesAsPrivileged` *after* `app.whenReady()`, which is illegal per Electron's API contract. Introduced by #14602 (`fix(local-system): guard readFile against binary blobs and oversized output`): adding `isBinaryContent.ts` made `detectUtf16NoBom` shared between the main bundle (via `sniffBinaryFile`) and the lazy text chunk, so rolldown placed it in main and rewrote the text chunk's call as a `require_index.detectUtf16NoBom`. Identical class of bug previously fixed for the `debug` package in #11827. ## Fix 1. **`packages/file-loaders/src/loaders/index.ts`** — TextLoader was lazy-imported for no real benefit. It's a 10KB module whose only deps are `node:fs/promises` and a tiny utf-16 detect util — nothing like the multi-MB parsers (pdfjs-dist, xlsx, mammoth) that the lazy pattern was designed for. Make it a static import; `getFileLoader('txt')` returns it synchronously. Result: the text chunk disappears entirely, removing this back-reference at the source. 2. **`apps/desktop/electron.vite.config.ts`** — defensive `manualChunks` rules so any future shared symbol doesn't recreate the same trap: - `vendor-file-loaders-utils` for the three small text/binary detection utils (`detectUtf16` / `isBinaryContent` / `isTextReadableFile`). Explicitly enumerated to avoid catching `parser-utils.ts`, which pulls in xmldom/yauzl/concat-stream (≈900KB) and belongs in the docx/pptx chunks instead. - `vendor-jszip` for JSZip — same root cause for `.docx` reads: the docx chunk had `require_index.require_lib()` (JSZip) back-referencing main. Both ends now share the vendor chunk; no main re-eval. Follows the project precedent set by #11827 for `debug`. ## Verification (live Electron via CDP) Bundle inventory before/after: | Chunk | Before | After | | --- | --- | --- | | `text-*.js` | 9.7KB (back-refs main) | (gone, inlined into main) | | `vendor-file-loaders-utils-*.js` | n/a | 18KB | | `vendor-jszip-*.js` | n/a | 899KB | | `docx-*.js` back-refs | `require_index.require_lib` | none | End-to-end via `tool.invokeBuiltinTool('lobe-local-system', 'readFile', …)`: | File | Before | After | | --- | --- | --- | | `.md` / `.json` / `.ts` | `Error accessing or processing file: protocol.registerSchemesAsPrivileged should be called before app is ready` | real file content | `grep -o 'require_index\\.[a-zA-Z_]*' dist/main/*-*.js | sort -u` → empty. All 61 file-loaders tests pass; all 64 builtin-tool-local-system tests pass.
…14292) * ✨ feat: agent-documents index — hide web crawls + new table format The default `<agent_documents_index>` was injecting every progressive document — including hundreds of web-crawled snapshots (~73% of all agent docs in production). The result was a low-signal list dominated by duplicate page titles, plus zero metadata for the LLM to rank by. This revamp: - Hides `source_type=web` documents from the default index. Header surfaces the count and points the LLM at `listDocuments(sourceType= 'web')` to enumerate them when needed. - Renders the index as a fixed-width table with TITLE / ID / SIZE / UPDATED columns. Rows are sorted by recency (most-recent first). Empty docs render as `empty` to discourage retry reads. - Adds `sourceType` and `updatedAt` to the `AgentContextDocument` contract; client mapping populates both from the DB row. - Adds `sourceType: 'all' | 'file' | 'web'` parameter to the listDocuments tool/TRPC; service-layer filter applies before shaping the LLM response. - Renames `target` → `scope` on listDocuments + createDocument (manifest, types, runtime, system role, TRPC, client service, call sites, tests). `target="currentTopic"` becomes `scope="currentTopic"` everywhere. Coverage: inline snapshot tests in `packages/context-engine/src/providers/__tests__/AgentDocumentInjector.test.ts` pin the rendered output for the three load cases (mixed user docs, web-hidden header, empty doc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(test): update listDocuments mock assertion for sourceType default The agent-documents listDocuments runtime now forwards sourceType (defaulting to 'all'), so the spy receives two positional args. * 📝 docs(builtin-tool-local-system): bump documented runCommand max timeout to 800000ms --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fix (#14898) FullNameStep is the classic branch's first step; its back button called goToPreviousStep, which no-ops at step 1 — a dead link ever since the telemetry/language steps were extracted into the shared prefix. Route it back to ResponseLanguageStep, and let CommonOnboardingPage re-enter the shared prefix when an explicit `?step` is present (a bare `/onboarding` still resumes the branch).
…preview (#14896) * ✨ feat(hetero-cc): surface project skills in working sidebar + markdown preview When the active agent is a heterogeneous Claude Code session, the Space tab now lists skills discovered under `<cwd>/.agents/skills/` (with a fallback to `<cwd>/.claude/skills/`). Each row shows the skill's frontmatter name, file count, and a chevron to expand a peek at the bundle contents; clicking the name opens `SKILL.md` in the LocalFile portal, and clicking a child file opens that file directly. The LocalFile portal also gets a Preview / Raw toggle for `.md` / `.mdx` files — frontmatter is now parsed and the YAML block stripped from the rendered markdown body (no more `name: x description: y` reading as a wall of body text). The portal tab strip distinguishes SKILL.md tabs by showing the skill name with the Skills icon instead of the generic filename, and falls back to a file icon for all other open files. Markdown content gets its own scroll container so the Preview pane scrolls correctly. The space-tab AgentDocuments group is hidden for hetero CC sessions so the panel focuses on skills. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(hetero-cc): default to Space tab for hetero sessions Hetero CC right-panel now defaults to the Space tab (where the Skills module lives) when there's no prior stored tab choice. Non-hetero sessions keep the existing review/files/resources fallback order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(hetero-cc): surface cumulative progress on Task inspector rows TaskCreate / TaskUpdate-with-status inspector rows now lead with the same ProgressRing (from pluginState.todos) and a `completed/total` chip, so a mixed create/update column reads as one continuous progress gauge instead of bare-text per-row signals. The verb in the label still carries the per-row status. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(hetero-cc): project skills in slash menu + skills panel polish Surfaces `.agents/skills/` SKILL.md entries as a new `projectSkill` ActionTag category in the chat input's `/` menu so users can invoke project skills the same way CC does internally. The chip serializes to literal `/<skill-name>` on send, leaving CC's own skill resolution untouched (no system prompt injection). Side-panel polish bundled in: the Space-tab Skills list expands as a real directory tree, the LocalFile portal renders SKILL.md frontmatter as a metadata card (reusing parseSkillMarkdownMetadata), and skill rows use the secondary→colorText hover pattern. Also passes `data.root` (the exact root listProjectSkills approves) to openLocalFile so previews never hit the workspace-root mismatch 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>
… fix multi-replica parent_id breaks (#14897) * 🐛 fix(hetero-agent): restore tools/model from DB at ingest refresh to fix multi-replica parent_id breaks In prod a topic with 11 step boundaries produced 4 assistants whose parentId pointed at the previous assistant instead of the previous tool message — same in-memory state.toolState gets reset at the end of every handleStepStart, so if the next step's tools_calling lands on a different replica, this replica stays empty and the following step boundary falls back to currentAssistantMessageId. Two of the four also had model=null/provider=null for the same reason: handleTurnMetadata only cached lastModel/lastProvider in memory. Adopt DB as authoritative at the ingest() refresh: replace state.toolState wholesale when DB has more tools or more result_msg_ids than memory, and restore state.lastModel/lastProvider from the refreshed assistant row. Also extend handleTurnMetadata to persist model/provider to DB (previously only metadata.usage was written), so the refresh path has something to recover from. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): never mark unresolved restored tools as persisted Three sites that hydrate `state.toolState` from DB-side `assistant.tools[]` were unconditionally pushing every id into `persistedIds`: - `ingest()` refresh (newly added in the prior commit on this branch) - `loadOrCreateState` (cold replica boot) - `syncAssistantPointerForAdvancedStep` `persistToolBatch` writes `tools[]` in Phase 1 BEFORE creating the `role:'tool'` row in Phase 2 and backfilling `result_msg_id`. A replica that hydrates between those two phases sees an unresolved id; marking it as persisted then causes a follow-up retry of the same tools_calling event to fall out of `freshForCreate`, skip Phase 2, and rewrite the unresolved `tools[]` unchanged — leaving the tool permanently without a tool message / result_msg_id. Restore only ids whose `result_msg_id` is already set. Unresolved ids stay re-createable so the BatchIngester's outer retry can complete the write. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…14883) * 🐛 fix(agent): stop auto-collapsing right working panel on chat mount ChatConversation had a mount effect that forcibly toggled showRightPanel off whenever status init completed, so switching to a new topic (which remounts the route subtree) would close the user's Workspace panel. Drop the effect and default showRightPanel to false instead — the persisted user preference is now the single source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(agent): keep right-panel toggles usable before status hydration INITIAL_STATUS.showRightPanel now defaults to false, which means WorkingPanelToggle / ToggleRightPanelButton / ParamsPanelToggle render their "open" button during the pre-hydration window. But updateSystemStatus bails early while isStatusInit is false, so the very first click was silently dropped and the panel stayed closed even after hydration when storage was empty. Defer rendering these toggles until isStatusInit flips true so a click can never land in the no-op window. Also fix the action.test.ts > toggleRightPanel > should toggle chat sidebar case, which was passing only because the old default was true; it now hydrates the store before asserting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(agent): stop overwriting working-sidebar tab when reopening panel WorkingPanelToggle unconditionally set storedTab='review' on every click, so any Space/Files preference the user had clicked previously got clobbered the next time they re-opened the right panel — most visibly on hetero CC sessions where the intended default is Space. The toggle now just toggles the panel open; the sidebar's own resolveActiveTab handles defaulting (hetero → Space, otherwise → last explicit click, then Review/Files based on local-system availability). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… skill menu (#14901) * ♻️ refactor(chat-input): adopt native submenu header/footer slots for skill menu The skill menu in the Plus dropdown pinned its search bar and stats footer as faux menu items held by position:sticky CSS hacks (data-fixed-menu-footer / data-skill-menu-search / data-skill-stats). @lobehub/ui 5.14.0 adds native header/footer slots to submenu popups, so move the search bar and stats row onto those slots and drop the hacks. * ♻️ refactor(knowledge-controls): integrate footer into useControls and update PlusAction to utilize new structure Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
…not just last turn (#14904) Previously the magic signature was only applied when the last message was a tool message and only to functionCall parts after the last user message. This missed cross-provider scenarios (e.g. OpenAI GPT-5 → Gemini switch) where historical tool_calls lack thoughtSignature, causing Gemini API warnings: Function call is missing a thought_signature in functionCall parts. Now we unconditionally iterate all model-role contents and add the magic signature to any functionCall part that doesn't have one, ensuring Gemini's thought signature validator is always satisfied regardless of conversation history origin. See LOBE-8662
…14903) * 💄 style(chat-input): switch action tag chips to icon + colored label Replace the filled Tag chip with an inline icon + colored label so skill and command references read like prose instead of UI badges. - Use SkillsIcon for skill / projectSkill (both green via colorSuccess) - Use TerminalIcon for command (cssVar.purple token, theme-aware) - Use WrenchIcon for tool (cssVar.colorInfo) - Preserve selection outline on .selected for the editor Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(chat-input): rename ActionTagView to ActionMention The component no longer renders a Tag chip — it renders an inline icon with colored label representing a mentioned/inserted action reference. "Mention" matches how these are inserted in the editor (via slash menu or @-mention) and reads better in the user-message renderer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(chat-input): drop borders on @mention and @topic chips @-mention (from `@lobehub/editor`) and @-topic refer chips both had outlined borders; switch them to a borderless filled look so they sit quietly inline with surrounding text — matching the new ActionMention. - `ReferTopicView`: `variant="outlined"` → `variant="filled"` - Add `mentionFilledClassName` (`.editor_mention { border: none }`) and apply it on both the editor (`InputEditor` className) and the rendered user message (`RichTextMessage` LexicalRenderer className) so input and read-back look the same. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(agent-sidebar): allow message channel for Claude Code hetero agents Codex and other hetero providers still hide the channel entry; Claude Code agents can now use it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(chat-input): satisfy strict types for icon map and mention className CI failures from the previous commits: - `ActionMention` typed CATEGORY_ICON as `ComponentType<any>` which is a superset of `LucideIcon | FC<any> | ReactNode` accepted by `<Icon>` — narrow to `FC<any>` so SkillsIcon and lucide icons type-check. - `mentionFilledClassName` was a `SerializedStyles` from `css\`\``; wrap in `cx()` so it serializes to a `string`, which `LexicalRenderer`'s `className` prop requires. - Update `Nav.test.tsx` mock to expose the new `currentAgentHeterogeneousProviderType` selector that landed in 89d7515. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): keep reasoning state live during gateway streaming The gateway event handler only accumulated reasoning text into `message.reasoning` without ever creating a `type: 'reasoning'` operation, so `isMessageInReasoning` was always `false`. The Thinking UI then rendered the "已深度思考" completed title and stayed collapsed for the entire stream. Mirror `StreamingHandler`'s lifecycle: start a reasoning sub-op on the first thinking chunk and end it on text / tools_calling / stream_end / stream_start (next step) / agent_runtime_end / error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rMessages hotkey (#14906) * 🐛 fix(conversation): animate only the last assistant block markdown streaming Switch `withMarkdownStreamingState` from disabling the first block to disabling every block except the last one. The previous logic let middle blocks keep `animated=true` during generation, so any remount mid-stream replayed the typewriter from scratch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🔥 chore(hotkey): remove clearCurrentMessages shortcut Drop the Alt+Shift+Backspace binding from the chat scope. The eraser button in ActionBar still works; only the keyboard shortcut, registry entry, hotkey i18n and docs row are gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# 🚀 LobeHub Release (20260518) **Release Date:** May 18, 2026 **Since v2.1.58:** 208 merged PRs · 209 commits · 16 contributors > v2.2.0 introduces the **Chief Agent Operator** — an agent that runs itself end-to-end. It self-iterates against its own output, assembles sub-agent teams on demand through the heterogeneous runtime, and drives a unified task system that knows when to pause for a human. Self-review, AssistantGroup, and tasks/scheduling all converge into one operator surface. --- ## ✨ Highlights ### 🎩 Chief Agent Operator - **Self-iteration exits Lab** — Agent Signal's self-review pipeline ships proposal actions straight into briefs and auto-executes the approved follow-ups, with prompts hardened against eval. The operator now critiques and re-runs its own work without a human in the loop. (#14769, #14583, #14647, #14882) - **Auto-formed agent teams** — Heterogeneous AssistantGroup gains Monitor-style signal callbacks, read-only SubAgent threads with breadcrumb headers, and a thread switcher. The operator dispatches sub-agents and you can step into any branch to see what the team is doing. (#14859, #14658, #14845, #14715) - **Task system as the operator's runway** — Claude Code surfaces task tools, AskUserQuestion freeform notes, and a dedicated `waitingForHuman` topic status; `lobe-task` exposes `setTaskSchedule`; the scheduler is hardened (maxExecutions cap, sub-10min heartbeat block, race-free SchedulerForm). Long-running operator runs no longer go silent and stop themselves when human input is needed. (#14870, #14639, #14713, #14865, #14853) ### 🚀 Cloud & runtime - **Cloud Claude Code V3** — Repo picker, GitHub token flow, and sandbox-aware context bring cloud-hosted Claude Code to feature parity with local; cloud sandbox completion now triggers the task lifecycle end-to-end. (#14568, #14822, #14681) - **Heterogeneous agent multi-replica safety** — Subagent threads, ingest refresh, and parallel-tool counts now survive replica swaps without losing parent_id or rolling back tool state. (#14897, #14631, #14806, #14838) - **Built-in tool lifecycle hooks** — `onBeforeCall` / `onAfterCall` land on the built-in tool runtime; sub-agent dispatch moves to `lobe-agent`; self-iteration aligns with the shared inspector pattern. (#14719, #14715, #14827) - **Knowledge base RAG unified** — Client and server share one `KnowledgeBaseSearchService`; KB files preserved on `NoSuchKey` instead of silently lost. (#14673, #14501) ### 💬 Workspace experience - **Home daily brief + recommendations** — The home screen opens with a linkable welcome, paired input hint, and a recommendations module sourced from the operator's hetero action library. (#14589, #14645, #14770) - **Chat mode + redesigned action bar** — The chat input gains a Chat/Agent mode toggle and a re-pitched action bar with icon-and-color action tag chips. (#14774, #14903, #14846) - **Documents tree, optimistic** — Document tree creates, deletes, and inline renames now apply optimistically; the agent-documents index hides web crawls and switches to a table layout. (#14714, #14292) - **Branded MCP inspectors** — Linear MCP tool calls render with the same branded inspector as the built-in Linear skill; CC MCP and built-in skills now share inspector code. (#14864, #14884) - **Bot identity gating** — Device tools are gated by sender identity, the activator bypass is closed, and Slack mpim plus Discord DM regressions are fixed. (#14634, #14664, #14733) --- ## 🏗️ Core Agent & Signal Pipeline ### Self-iteration & Agent Signal - Self-iteration graduates out of Lab, with service, tool, name, and concept structure unified across `agent-signal`, `prompts`, `database`, and `builtin-tool-self-iteration`. (#14699, #14769) - Self-review now proposes actions to briefs and auto-executes the approved set, with eval-verified prompt hardening. (#14583, #14657, #14647) - Self-iteration built-in tool aligns with the shared runtime + inspector patterns. (#14827) - Agent Signal prompts adapt their response language and avoid blocking agent execution. (#14890, #14775, #14882) - Receipt descriptions now carry an Agent Signal marker, and self-review hinted skill documents route correctly. (#14764, #14895) ### Heterogeneous agent runtime - Subagent threads render read-only with a breadcrumb header and thread switcher; SUBAGENT badge dropped, indentation tightened. (#14658, #14845, #14783) - Multi-replica safety: ingest refresh restores tools/model from DB to fix parent_id breaks; new-step assistants sync across replicas; subagent-tagged events no longer leak into the main gateway handler. (#14897, #14631, #14838) - Fetch-triggering events are deferred to keep parallel tool counts from rolling back. (#14806) - AskUserQuestion is wired for Claude Code, with auto-decline disabled and a freeform note input on the cloud side; `waitingForHuman` is a first-class topic status. (#14639, #14629, #14870) - AssistantGroup gains Monitor-style signal callbacks; project skills surface in the working sidebar and markdown preview. (#14859, #14896) - Cloud Claude Code V3 — repo picker, GitHub token, sandbox context; credentials alert and disabled input when not configured. (#14568, #14822) - Cloud sandbox completion now triggers the task lifecycle end-to-end. (#14681) ### Agent runtime & context engine - Built-in tool runtime gets `onBeforeCall` / `onAfterCall` lifecycle hooks. (#14719) - `CompletionLifecycle`, `HumanInterventionHandler`, and `stepPresentation` are extracted from the runtime monolith. (#14441) - Per-tool timeout is honored end-to-end for client tool dispatch. (#14817) - Compression budget accounts for `tool_calls`, reasoning content, and tool defs; `call_llm` forwards tools into the budget. (#14813, #14837) - Pre-flight context check now fails fast for OpenAI-compatible providers. (#14824) - Malformed `tool_call` names are recovered instead of finishing the step silently. (#14577) - Sub-agent dispatch moves from `lobe-gtd` to `lobe-agent`. (#14715) - Hidden built-in tools now appear in the system prompt @-mention list. (#14823) ### Agent tracing & operations - New `agent_operations` table and runtime persistence for every hetero-agent operation. (#14416, #14736) - `signOperationJwt` issues 4-hour signed operation tokens. (#14586) - S3 trace snapshots are zstd-compressed; DB `trace_s3_key` aligns with the `.json.zst` suffix; legacy `.json` fallback preserved on fetch. (#14807, #14860, #14826) --- ## 📱 Platform & Integrations ### Bot / Channels - Device tools are gated by sender identity. (#14634) - Activator bypass closed and device-access checks converged. (#14664) - Slack mpim supported; Discord DM regression fixed; Slack connect + slash commands repaired. (#14733, #14591) - Bot channels, bot watch, bot callback service, and system bot reliability fixes. (#14847, #14796, #14570, #14784, #14649) - Online Messager scaffolding. (#14755) ### Onboarding - Home daily brief with linkable welcome and paired input hint. (#14589) - Recommendations module sourced from the hetero agent action library. (#14645) - Chat onboarding passes request triggers via metadata and preserves the resume request. (#14770, #14798) - Discovery turn progress gated by phase, with a reminder on stalled discovery. (#14842, #14833) - FullNameStep back button rejoins the shared prefix; ModeSwitch hidden in production. (#14898, #14760) - Agent marketplace folds into the web onboarding tool. (#14578, #14672) - Onboarding interests stored as keys instead of free text; early-exit skips marketplace and drops CJK prompts. (#14624, #14598) ### Model providers - Gemini 3.1 Flash-Lite cards; Gemini schema sanitizer drops non-compliant `enum` / `required`; zero `cachedContentTokenCount` handled in usage conversion. (#14604, #14740, #14567) - DeepSeek-V4 model cards and pricing restored to official rates. (#14110, #14911) - ernie-5.1 and spark-x2-flash support; Grok 4.3 `reasoning_effort` support. (#14643, #14731, #14642) - SiliconCloud catalog synced with API; duplicates removed; reasoning params adjusted. (#14464) - Minimax derives `max_tokens` from context window to avoid `ExceededContextWindow`. (#14814) - aihubmix uses the full models endpoint for a complete list; stale empty-apiKey test dropped. (#14511, #14669) - Stream parse errors are enriched with provider + model context. (#14636) - Visual content parts are consumed in the server runtime; video image references move to a JSON object. (#14637, #14900) - Google function call magic `thoughtSignature` now attaches to every part, not just the last turn. (#14904) - Service model assignments settings added; model extend-param options removed. (#14712, #14607) ### Built-in tools & knowledge base - `lobe-task` exposes `setTaskSchedule`; task scheduler hardened (maxExecutions cap, sub-10min heartbeat blocked, SchedulerForm race fix, rapid automation-mode toggle stabilized). (#14713, #14865, #14853, #14801) - KnowledgeBaseSearchService shares RAG runtime across client and server. (#14673) - KB files preserved on `NoSuchKey` and orphan documents/tasks cleaned. (#14501) - Document tree gets optimistic create/delete + inline rename. (#14714) - agent-documents index hides web crawls and switches to a table layout. (#14292) - `lobe-clarify` and SKILL.md frontmatter parsing/edit validation are unified. (#14566) - AnalyzeVisualMedia inspector + Portal HTML preview refactor; HTML preview restored for AssistantGroup messages. (#14777, #14811) - Branded inspector shared between CC MCP and built-in Linear skill. (#14884, #14864) --- ## 🖥️ CLI & User Experience ### Chat & Conversation - Chat mode toggle and redesigned chat input action bar. (#14774) - Action tag chips switch to icon + colored label; ActionDropdown closes on sibling-open and focus-out; submenu uses native header/footer slots. (#14903, #14802, #14901) - Action bar padding equalized around the send button; skeleton shows in action bar while config loads. (#14846, #14656) - `useCmdEnterToSend` is respected in thread & task inputs; send button enables after pasting into thread/comment input. (#14850, #14816) - TopicChatDrawer state preserved during close animation. (#14803) - Only the last assistant block animates during markdown streaming. (#14906) - Right working panel no longer auto-collapses on chat mount; home agent config fetched so knowledge toggles reflect in UI. (#14883, #14834) ### Tasks - Task scheduler, hotkey, comment, and TodoList polish. (#14707) - Add Subtask button & card baseline aligned; activity card stop run; task agent manager polish. (#14848, #14559, #14569) - Task template skeleton CLS reduced; task page placeholder copy refreshed. (#14788, #14704) - Task agent model snapshotted into `task.config` at create time. (#14670) - User-feedback card, task card polish, and Run-now context menu in markdown. (#14727) - Inline skill auth in recommended task templates. (#14676) ### Navigation & Layout - Tab bar gains a Chrome-style divider between inactive tabs. (#14892) - SideBarDrawer & header layout polish; nav ActionIcon sizing unified; TodoList encapsulation improved. (#14762, #14692) - Desktop header icons, sidebar density, and task menus polished. (#14724) - Standardized header action icon sizes. (#14717) - Chat topic title length increased; copy session ID added to topic dropdown menu. (#14659, #14595) - Heterogeneous agent topic rows regain indentation. (#14783) ### Other polish - Usage token details shortened; tool execution time formatted as `Xmin Ys`. (#14849, #14641) - Tool arguments display gets word-wrap toggle; long tool-call params wrap instead of truncate. (#14706, #14640) - Editor stops showing per-line placeholder once content is present. (#14852) - Visible divider between queued messages; intervention confirmation bar polished. (#14593, #14587) - Credit top-up copy refreshed; auth captcha retry copy refreshed; brief recommendations layout polished. (#14821, #14561, #14871) --- ## 🔧 Tooling & Developer Experience - Dev-only feature flag override panel. (#14565) - `__DEV__` define replaces `process.env.NODE_ENV` in the SPA. (#14696) - Agent-settings drops Meta/Documents tabs and restores `inputTemplate`. (#14874) - `local-system` forwards all `grepContent` params and moves the executor to `/client`. (#14888) - `lobe-task` and `setTaskSchedule` exposed. (#14713) - Memory user-memory benchmark agent config and source-id extraction schemas. (#14779, #14778) - CLI man page drops stale cron entry; `clearMessages` hotkey removed. (#14709, #14906) - Skill docs simplified; cloud heteroContext gains sandbox TTL + public-repo fork push guide. (#14785, #14761) --- ## 🔒 Security & Reliability - **Security:** Sensitive comments and examples sanitized from the production JS bundle. (#14557) - **Security:** Inactive OIDC access rejected. (#14674) - **Security:** CASC `new Function()` template replaced with safe string builders. (#14751) - **Security:** Sign-in captcha flow removed in favor of safer flow. (#14573) - **Security:** Desktop local file previews restricted to safe roots. (#14789) - **Security:** Image binary capped at 3.75 MB so base64 payload stays under the Anthropic 5 MB limit. (#14711) - **Reliability:** Neon/Node pools get error listeners to prevent Lambda crashes. (#14606) - **Reliability:** `paradedb.match(...)` replaces hardcoded normalizer in memory search. (#14590) - **Reliability:** `PlaceholderVariablesProcessor` errors carry diagnostic context. (#14741) - **Reliability:** File storage upload checks are serialized; multiple account link bug fixed. (#14829, #14562) - **Reliability:** `ScrollShadow` replaced with `ScrollArea` to fix a React infinite render loop (error code 185). (#14689) - **Reliability:** Embedding token cap enforced — long memory queries are limited and truncated before search. (#14757) - **Reliability:** Embed binary blob guard + oversized output cap in `local-system.readFile`. (#14602) - **Reliability:** Windows npm CLI shims resolved before spawning agents. (#14772, #14720) - **Reliability:** Vite pinned to 8.0.12 to avoid the rolldown 1.0.1 preload regression; desktop runtime externals split from native deps. (#14804, #14776) - **Reliability:** Old lobehub cron job removed; WeChat URL rules dropped from web crawler. (#14630, #14633) --- ## 👥 Contributors Huge thanks to **16 contributors** who shipped **208 merged PRs** this cycle. @hezhijie0327 · @sxjeru · @hardy-one · @Bianzinan · @brone1323 · @YuSaZh · @Wxh16144 · @arvinxx · @Innei · @tjx666 · @neko · @lijian · @rdmclin2 · @sudongyuer · @AmAzing129 · @rivertwilight Plus @lobehubbot for maintenance translations. --- **Full Changelog**: v2.1.58...v2.2.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )