Custom models auto-saved by ensureCustomProvidersAndModels carry only
capabilities:['chat'], so inferReasoning was returning reasoning=false
even for models like qwen3:8b whose names match the reasoning heuristic.
The UI still showed thinking levels via getSupportedThinkingLevels, but
selections were silently ignored by the backend.
Fix: for non-builtin (builtin=false) model definitions, also apply the
name/provider heuristic as a fallback alongside the capabilities check.
Builtin catalog models remain authoritative.
Adds a regression test covering the exact findModelFn shape produced by
ensureCustomProvidersAndModels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StatusBar model button: show model name instead of provider, click opens
settings directly (scrolls to default model section), remove dropdown
- SessionIndexSection: replace N serial pluginQuery calls with single batch
API (GET /_web/sessions/index-stats), add IntersectionObserver lazy loading
- Full-stack implementation: CLI route, Electron IPC/Worker, frontend adapters
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add 'default' option (no reasoning params sent, model uses native behavior)
- Add 'auto' option for models that natively support it (deepseek_hybrid, kimi, doubao)
- Expand model recognition with regex coverage for 15+ model families
- Add Gemini 2.5+ thinking level support via native Google provider
- Simplify deepseek_hybrid options to ['default', 'off', 'auto'] aligned with Cherry Studio
- Use primary theme color for thinking selector instead of hardcoded blue
- Update docs with thinking control architecture section
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a lightbulb button next to the model switcher in ChatStatusBar
that lets users choose the thinking intensity for each reasoning model,
with the choice remembered per model slot (configId:modelId).
Key changes:
- packages/core/src/ai/thinking.ts (new): per-model-type thinking level
table inspired by cherry-studio's MODEL_SUPPORTED_REASONING_EFFORT.
Covers Qwen/GLM (on/off), DeepSeek V4 (high/max), o-series
(low/medium/high), gpt-5.x (minimal–xhigh), Kimi/Doubao/Grok and a
default bucket. Exports getSupportedThinkingLevels, isReasoningModel,
getThinkingCompat.
- packages/node-runtime/src/ai/llm-builder.ts: inferReasoning now calls
getThinkingCompat() which sets compat.supportsReasoningEffort and
compat.thinkingLevelMap so pi-ai can inject the correct request param
(reasoning_effort for OpenAI/DeepSeek, enable_thinking for Qwen/GLM).
- packages/node-runtime/src/ai/agent/types.ts + core.ts: AgentCoreOptions
gains an optional thinkingLevel; core.ts:93 uses clampThinkingLevel()
(from pi-ai) to honour it safely.
- Full transparent pass-through: desktop Agent → IPC handler → preload
→ agentApi.runStream; CLI HTTP route → runServerAgent → runAgentCore.
- src/stores/prompt.ts: thinkingLevels Record<configId:modelId,
ThinkingLevel> persisted in backendPersist; getThinkingLevel/
setThinkingLevel helpers.
- src/stores/aiChat.ts: reads current model slot and passes remembered
level to both sendMessage and editCurrentRoundOnly runStream calls.
- ChatStatusBar.vue: UPopover thinking selector appears only for
reasoning models, shows only the levels that model supports, highlights
current choice, hides for non-reasoning models.
- 46 tests passing (26 new in thinking.test.ts + 20 in llm-builder).
Scope: openai-completions path only (Qwen, DeepSeek, OpenAI, xAI, Kimi,
Doubao, SiliconFlow, OpenRouter…). Anthropic/Gemini native thinking is
out of scope for this version.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the "Reasoning Model" and "Disable Thinking Mode" toggles from
the model config dialog. These switches were injecting `enable_thinking`
(a Qwen/DashScope-specific request parameter) into all providers,
causing 400 errors on OpenAI, Anthropic, Gemini, and other official
services.
Replace with automatic inference in buildPiModel:
- reasoning: true when the model's catalog capabilities include
'reasoning'; falls back to a model-name heuristic for unlisted
custom/self-hosted models (qwen3, qwq, deepseek-r, o1/o3, etc.).
- compat.thinkingFormat 'qwen': only set when reasoning is true AND
the model is from the qwen provider or the model id contains
"qwen"/"qwq". All other providers get compat: undefined and will
never receive enable_thinking.
This means official reasoning models (o3, deepseek-reasoner, etc.) now
automatically get thinkingLevel:'medium', and self-hosted Qwen3
models continue to work as before — without any manual toggle.
Behavior change: models with 'reasoning' in their catalog capabilities
now default to thinkingLevel:'medium' (previously always 'off' unless
the manual switch was enabled).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- insertMessageAfter: update active_message_id when appending to tail
so the new reply is on the active path after reload
- editAndOverwriteAll: add hasConfig + abort checks before
deleteMessagesFrom to prevent data loss when LLM is unconfigured or
a concurrent request starts
- editCurrentRoundOnly: defer all DB mutations until after stream
completes; on abort/error only in-memory state is restored
- sync in-memory parentId after relinking so follow-up edits send the
correct historyLeafMessageId
Co-authored-by: Cursor <cursoragent@cursor.com>
Remove the message branching/tree system in favor of a simpler editing model:
- Default: edit a message and only regenerate the current round's AI response
- Optional: overwrite all subsequent messages when editing
Key changes:
- Backend: add updateMessageContent, deleteAndRelinkMessage, insertMessageAfter,
deleteMessagesFrom, forkConversation; remove createMessageBranch, switchMessageBranch
- Platform layer: add IPC/HTTP handlers for new methods across Electron and CLI
- Frontend store: refactor editMessageAndRegenerate with two modes
- UI: replace branch navigation with "overwrite subsequent" checkbox,
auto-resizing edit textarea
- Fix insertMessageAfter missing debug_context from pendingDebugContextMap
- Update tests to cover new editing methods
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix apiKeySet reporting false positives for local no-key configs by
resolving the actual key via resolveApiKey and excluding placeholders
- Prevent editing a config from reusing an incompatible API key when
provider or connection mode has changed
- Pass summary strategy to single-session generation for consistency
Co-authored-by: Cursor <cursoragent@cursor.com>
Support two summary strategies (brief/standard) for AI-generated session
summaries. Users can choose their preferred detail level in Settings > AI >
Session Index. Standard (default) produces richer summaries covering people,
events, and key details.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Implement generateSummary/checkCanGenerateSummary in FetchSessionIndexAdapter
via existing HTTP endpoints instead of stubs that returned instantly
- Add checkCanGenerate service function in summary-service.ts
- Add POST /_web/sessions/:id/summaries/check-can-generate route
The page froze because stub methods returned synchronously, causing a tight
loop of hundreds of Vue reactive updates without any network delay.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace @mariozechner/pi-ai and pi-agent-core with @earendil-works scope
- Adapt to AgentTool.execute() params type change (Record → unknown)
- Migrate Agent constructor to use initialState for systemPrompt/tools/messages
- Update onPayload callback signature to include model parameter
- Filter finalMessages with isPiMessage() for new AgentMessage union type
- Use state.errorMessage instead of deprecated state.error
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add "type": "module" to all workspace package.json files
- Replace __dirname with import.meta.url-based resolveCliPath() helper
- Convert stream-json/stream-chain CJS named imports to default + destructure
for ESM compatibility
- Use createRequire() for @node-rs/jieba native module loading in ESM context
Co-authored-by: Cursor <cursoragent@cursor.com>
After pull-sync imports new messages, generate session index incrementally
instead of full regeneration. This preserves existing sessions and their
summaries, only processing newly imported (unindexed) messages.
Adds generateIncremental method across the full stack:
- core: already had generateIncrementalSessionIndex
- node-runtime: expose generateIncrementalIndex
- CLI: add POST /_web/sessions/:id/generate-incremental-index route
- Electron: add session:generateIncremental IPC handler
- Frontend: add generateIncremental to SessionIndexAdapter interface
Extract shared business logic from monolithic web.ts into reusable
services in @openchatlab/node-runtime (session, member, index, summary,
export), using SessionRuntimeAdapter to abstract platform differences.
Split web.ts into 12 domain-specific route modules under routes/web/.
Co-authored-by: Cursor <cursoragent@cursor.com>
MiniMax models embed thinking content as <think> tags in the content
field rather than using a separate reasoning field. Add a streaming
think-tag parser that detects these tags in real-time during streaming
and routes them to the thinking UI instead of displaying raw tags.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add backendPersist Pinia plugin for declarative preferences.json persistence
- Move Preferences types to @openchatlab/shared-types (single source of truth)
- Migrate sessionGapThreshold from localStorage to config.toml [ui]
- Migrate chatlab_filter_history from localStorage to preferences.json
- Fix stale localStorage locale read in i18n/index.ts
- Simplify usePreferencesSync (172 → 75 lines) by delegating to plugin
Adding a new backend-synced preference now only requires 3 changes:
1. shared-types interface 2. node-runtime DEFAULTS 3. store backendPersist.pick
When a desktop app user installs the CLI, automatically detect existing
Electron data directory (including custom paths via storage.json),
repoint config.toml, and migrate system data (ai, settings, cache, etc.)
to ~/.chatlab/. Block CLI execution with actionable instructions when
Electron was used but databases cannot be located.
Move Electron-specific code (main, preload, shared) and configs
(electron.vite, electron-builder) into apps/desktop/ to prepare
for multi-platform support (Web Browser standalone).
- Create apps/desktop/ with dedicated package.json and tsconfig
- Split Electron deps (electron, electron-updater, better-sqlite3,
etc.) from shared frontend deps in root package.json
- Declare better-sqlite3 and @node-rs/jieba in packages/node-runtime
- Add stream-json to packages/parser where it belongs
- Update all path aliases (@electron/* → apps/desktop/*) in tsconfig,
vite configs, and relative imports in preload/main
- Keep slim root tsconfig.node.json as shared base for packages
- Update CI/CD release workflow for new directory layout
- Fix pre-existing type errors (pi-ai Model generic, SQLLabTab
LLMChatStreamChunk, filter.test.ts empty methods)
- Extract getPrivateChatMemberAvatar to @openchatlab/core
- Extract getExportSessionData (meta/members/messages SQL) to @openchatlab/core
- Extract loadSessionMessages for summary generation to @openchatlab/core
- Replace inline SQL in summary buildDeps with shared core query functions
- Extract createCompressionLlmAdapter factory to @openchatlab/node-runtime
- Simplify server and electron compression adapter creation via shared factory
- Extract formatAIError to @openchatlab/node-runtime, eliminate duplicate in Electron and Server
- Extract stripAvatarFields to @openchatlab/core
- Extract FTS message search SQL to core (searchMessagesWithFtsAsync)
- Remove redundant sync searchMessagesWithLike from Electron worker
- Extract runSimpleLlmStream to @openchatlab/node-runtime for shared
Electron/Server streaming with thinking_start/delta/end event handling
- Simplify Electron IPC ai.ts by delegating to shared stream function
- Add thinking/thinkingDone fields to LlmStreamChunk and preload types
- Add collapsible thinking process display in SQL LAB with auto-scroll
- Implement Web chatStream via SSE with proper error propagation
- Add /_web/cache/save-to-downloads and show-in-folder server routes
- Add cacheApi shim in Web registry for server-side file operations
- Increase SQL LAB maxTokens to 4096 to accommodate reasoning models
- Detect finishReason:length and show token limit error message
- Add i18n keys for thinking/thinkingProcess/errorTokenLimit (4 locales)
Move AI-related translations (tools, agent prompts, LLM, summary) from
Electron-specific locale files into @openchatlab/node-runtime, enabling
single-source maintenance across Electron and Server.
- Add packages/node-runtime/src/ai/i18n/ with createAiTranslate() and
bundled locale data (zh-CN, en-US, ja-JP, zh-TW)
- Electron locale files now compose: Electron-specific keys + shared AI
translations from node-runtime
- Server agent.ts switches to shared buildSystemPrompt with evidence
policy, data snapshot injection, ownerInfo and mentionedMembers support
- Server route fetches dataSnapshot from getChatOverview before agent run
- Extract JSON cache CRUD + overview/members compute to node-runtime/cache
- Functions now accept DatabaseAdapter instead of better-sqlite3 Database
- Electron sessionCache.ts becomes pure re-export
- Updated all Electron callers to wrap DB with BetterSqliteAdapter
- Extract migration definitions (v1-v4) to shared getChatDbMigrations() with DI for tokenizer
- DatabaseManager.openWritable() auto-runs pending migrations
- Electron migrations.ts becomes thin wrapper delegating to shared definitions
Platform-agnostic temp DB management for merge operations using
DatabaseAdapter. Includes TempDbWriter (streaming write), TempDbReader
(streaming read + MergerDataSource adapter), exportSessionToJson,
and cleanup helpers.
Platform-agnostic incremental import with dual-path dedup
(platformMessageId + content hash), member upsert, meta patching,
FTS sync, and injectable dependencies (IncrementalImportDeps).
Platform-agnostic streaming import with batched transactions, deferred
indexes, nickname history tracking, FTS indexing, and format fallback.
All Electron-specific dependencies replaced with injectable interfaces
(StreamImportDeps, ImportLogger, ImportProgressCallback).
Move the entire RAG module (~2458 lines) from electron/main/ai/rag/ to
@openchatlab/node-runtime. Includes chunking, embedding, vector store,
and semantic pipeline.
Key changes:
- Config operations use injected aiDataDir instead of getAiDataDir()
- Logger uses injectable RagLogger interface
- LLM config provided via getLLMConfig/getAssistantConfig callbacks
- Deduplicated cosineSimilarity into shared utils.ts
- Electron rag/index.ts is now a thin wrapper (~128 lines)
Move performance logging from electron/main/worker/core/perfLogger.ts to
@openchatlab/node-runtime. Log directory is now caller-provided instead
of derived from Electron paths. Electron wrapper provides the path.
Reduces Electron perfLogger from ~181 lines to ~28 lines.
Move core FTS operations (create, build, rebuild, insert, search) to
@openchatlab/node-runtime working with DatabaseAdapter. Electron fts.ts
becomes a thin adapter that adds session-level DB connection management.
Reduces Electron fts.ts from ~207 lines to ~90 lines of pure wrappers.
Move the core data writing logic (meta/member/message insertion with
member_name_history tracking) from electron/main/database/core.ts to
@openchatlab/node-runtime. Electron importData() now delegates to the
shared function via BetterSqliteAdapter, reducing ~130 lines to ~10.
This enables both Electron and Server to share the same import logic
including name history tracking.
Platform-agnostic skill CRUD manager with dependency injection for file
system ops, builtin skill resources, and content hashing. Supports init,
CRUD, builtin import/reimport, cloud markdown import, and AI skill menu
generation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Platform-agnostic AssistantManager with dependency injection for file
system ops, builtin configs, and ID generation. Supports init, CRUD,
builtin import/reimport/reset, and cloud markdown import.
Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce ConfigStorage abstraction and LLMConfigStore class with
dependency injection for i18n, UUID generation, and auth profile hooks.
All CRUD operations (add/update/delete/slots) are platform-agnostic.
Electron can implement ConfigStorage with fs + getAiDataDir.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move platform ID collision detection, normalization, conflict detection,
member merge, and message dedup-sort from electron/main/merger to a
shared package with zero Node.js dependencies.
Also fix pre-existing unused-variable TS errors in summary test.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move session summary generation (573 lines) to shared package with
SummaryDeps injection for DB/LLM/i18n. Pure algorithms (isValidMessage,
filterValidMessages, splitIntoSegments, prompt builders) are now shared.
Electron summary/index.ts implements SummaryDeps and delegates.
Added 14 unit tests covering message filtering, segmentation,
summary generation, and eligibility checks.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move AgentEventHandler, TokenUsage, AgentRuntimeStatus, AgentStreamChunk
to packages/node-runtime/src/ai/agent/event-handler.ts.
Token estimation and event-to-chunk mapping are now shared.
Electron agent/types.ts re-exports from node-runtime.
Added 8 unit tests covering token estimation, tool tracking, usage cloning.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move buildSystemPrompt from electron/main/ai/agent/ to shared package.
i18n t() is now injected via BuildSystemPromptOptions parameter.
Types OwnerInfo, MentionedMember, SkillContext are shared.
Electron prompt-builder.ts injects its own t() and delegates.
Added 8 unit tests with mock translation function.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move tokenizeForFts and tokenizeQueryForFts from electron/main/nlp/ to
packages/node-runtime/src/nlp/fts-tokenizer.ts. Uses the already-shared
getJieba() internally. Electron ftsTokenizer.ts now re-exports.
Added 9 unit tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move remote LLM API operations into shared module with optional
headers and logger injection. Electron wraps with UA headers and
aiLogger; server re-exports directly.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move URL normalization helpers and PiModel construction into a shared
llm-builder module. Electron and server both delegate to the core
builder, with Electron adding optional headers and provider validation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move the file-based AI logging implementation from Electron-specific
code to a shared AiLogger class in @openchatlab/node-runtime. Both
Electron and Server/CLI now use the same logger writing to
~/.chatlab/logs/ai/. Server-side agent, tool adapter, and compression
now produce the same structured log files as Electron.
The 4-file preprocessor wrapper directory was entirely re-exports from
@openchatlab/node-runtime after the pipeline unification. Replace all
consumers with direct imports and add logger injection support to
PreprocessingPipelineOptions so Electron retains aiLogger integration.
Unify the duplicated preprocessing logic between Electron (wrapWithPreprocessing)
and Server (applyPreprocessing) into a single applyPreprocessingPipeline() function
in @openchatlab/node-runtime. Both platforms now delegate to the same implementation,
eliminating ~50 lines of redundant code and fixing a redundant token counting bug
in the Server version.