Skip to content

release: v1.2.6 — 全量代码审计修复、滚动与流式渲染优化、MCP 协议升级 - #21

Merged
LangLang03 merged 6 commits into
masterfrom
dev
Aug 14, 2026
Merged

release: v1.2.6 — 全量代码审计修复、滚动与流式渲染优化、MCP 协议升级#21
LangLang03 merged 6 commits into
masterfrom
dev

Conversation

@LangLang03

@LangLang03 LangLang03 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

版本

  • v1.2.6 正式版1.2.6-rc.21.2.6,versionCode 30 → 31)
  • 完整更新日志见 update.md(覆盖 1.2.5 起全部 21 个提交的变更)

功能与改进

  • MCP 协议升级(2025-03-26) - 自定义 HTTP 工具支持新版 MCP 协议:会话初始化 + Mcp-Session-Id 会话管理、Mcp-Protocol-Version 协议头
  • 工具调用去重 - 原生与文本工具调用按签名去重,同一调用不会执行两次
  • 工具调用上限提示 - 达到总工具调用上限时给出明确终止提示(多语言)
  • 进度监听器透传 - ToolContext 新增进度监听器 accessor 并透传给工具执行器
  • 学习上下文改进 - 学习模式关闭时改用手动记忆构建学习上下文
  • 响应头暴露 - SimpleHttpClient 捕获并暴露响应头(如 MCP 会话头)
  • Kimi 模型兼容层 - 新增 Kimi/Moonshot 推理策略兼容层(温度下限 1)
  • memory_update 可见性 - 工具在学习模式开启后可见
  • 死代码清理 - 删除从未调用的 MemoryExtractionService(587 行)及专属模板/字符串

聊天滚动体验优化(嵌套滚动冲突修复)

  • 共享方向感知 BoundedScrollView:ui-theme),保守抢占 + 按方向动态交还手势
  • 思考区、工具卡(Generic/Shell/Agent/AgentPipeline/Read)滚动死区与内滚被抢占问题全部修复

工具卡片流式渲染性能优化

  • ToolCallCardView.updateContent 增量更新钩子(默认回退 bind,向后兼容)
  • ToolCallBlockView 结构/内容签名拆分:仅内容变化不再销毁重建整棵视图树
  • Read/Generic/Agent/AgentPipeline 卡片流式增量更新

全量代码审计修复(高危 2 项、中危 30 项)

文件读取(file_read

  • 修复 KB 分页读取只返回第一行(行边界对齐搜索方向反转)
  • end_kb 放开 50KB 硬限制,可按文件大小分页读取(单次跨度 1MB 上限)
  • 总行数/起始行号改为 64KB 分块缓冲统计(大文件不再卡死);幻影空行号、行数 off-by-one 修复

Shell 执行

  • 修复 Terminal Provider 模式输出只返回最后一行(回调覆盖改累积)
  • 超时击杀整个进程树(/proc ppid 链)+ 抑制过期回调;正常完成不误杀 nohup & 后台任务
  • readFile 32MB 上限防 OOM;Terminal Provider 定位开放插件(不设签名/包名校验,兼容 GPLv3 重签名分发)

安全

  • UrlPolicy 私网判定改字面 IPv4(10.evil.com 等公网域名不再放行明文 HTTP)
  • SimpleHttpClient 响应体 32MB 上限、3 参 download 上限生效、int 溢出修复
  • 图片下载/响应上限(12MB/24MB)真正生效;Markdown data:image base64 5MB 上限 + 降采样
  • 错误日志 summary 补脱敏;.linecode 导入 fileName 消毒 + 规范路径校验(防 ../ 穿越)
  • 中文图片标签 [图片] 不再被截字(两处)

上下文压缩

  • 修复 Codex 压缩路径不重置 Token 基线导致的反复过度压缩

并发与稳定性

  • SshConnectionPool 并发首借竞态(连接/锁泄漏、IllegalMonitorStateException
  • ToolRegistry 读写锁(后台 reload 与渲染读取竞争)
  • 会话持久化移出主线程(单线程后台 + latest-wins 合并 + 切换前等待)
  • 工具调用预算只计实际执行(暂停/拒绝不再空耗额度);Agent 内部调用并入全局上限
  • Skill 安装/创建、聊天导出、存储统计移出主线程;MCP headers 主线程快照

UI 与细节

  • MainChatView 屏幕缓存上限 12(动态 screen id 泄漏 View/Context)
  • 存储页刷新按钮误触发返回修复;压缩模型查询按钮实时读取凭据
  • Agent 分区标题 %2$s 参数错误修复(三语言)
  • PDF 导出宽度换行丢字符修复;无障碍点击只点第一个匹配节点;SKILL.md 有界读取

测试

  • 新增 BoundedScrollViewTestToolCallBlockViewSignatureTestToolCallAgentViewLayoutSignatureTestMemoryRankerRagInjectionTest
  • ToolBuiltinsTest 加强至 23 例(KB 分页多行、中间页连续行号、end_kb>50)
  • 全量单元测试通过 + :app:assembleDebug / :terminal-provider:assembleDebug BUILD SUCCESSFUL

Summary by Sourcery

Release v1.2.6 with protocol, tooling, performance, and safety improvements across chat, tools, and infrastructure.

New Features:

  • Upgrade MCP HTTP tools to the latest protocol with session initialization and version headers.
  • Introduce a memory tool group with memory_update gated on Learning Mode.
  • Add a Kimi/Moonshot reasoning strategy layer with temperature clamping and thinking controls.

Bug Fixes:

  • Ensure file_read paging returns full line-numbered content across large files and mid-file chunks, with corrected range metadata.
  • Fix terminal shell and SSH command streaming so all output lines are delivered, and enforce robust process timeout handling without killing background jobs.
  • Harden URL private-network detection to only treat literal RFC1918/loopback IPv4 hosts as private, avoiding unsafe cleartext allowances for public domains.
  • Prevent context compaction from repeatedly over-compressing by resetting token usage tracking after Codex/Responses API compaction calls.
  • Eliminate race conditions and lock misuse in SSH connection pooling, tool registry reloads, and conversation persistence.
  • Correct PDF export line breaking so no characters are dropped, and fix various UI text, accessibility, and screen caching issues.

Enhancements:

  • Move conversation persistence, skill install/create, storage stats, and export operations off the main thread with single-thread/background execution and proper error surfacing.
  • Enforce size limits on HTTP responses, image API payloads, Markdown data URIs, and Skill markdown reads to avoid OOM and large-resource abuse.
  • Expose HTTP response headers from SimpleHttpClient for callers such as MCP session management.
  • Integrate Agent internal tool-call accounting into the global tool budget and add clear tool-call limit messaging.
  • Improve scrolling and rendering behavior in chat tooling UIs and memory ranking injection, backed by new tests.

Build:

  • Bump app versionName to 1.2.6 and versionCode to 31 for the stable release.

Documentation:

  • Update README, CLAUDE guide, and prompt template documentation to reflect the removal of memory extraction templates and the new learning-context and memory_update behavior.

Tests:

  • Extend tool, protocol, and UI tests including ToolBuiltinsTest for file_read paging, OpenAiCompatibleProtocol reasoning tests, MemoryRankerRagInjectionTest, and new scroll/render signature tests.

LangLang03 and others added 5 commits August 13, 2026 14:18
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
MemoryExtractionService.extractAndStore 从未被调用(原设计由 MainCoordinator 调度,
实际未接线),连带删除其专属依赖:
- memory/skill-extraction 两个 prompt 模板注册(PromptTemplateRepository ID + 定义 +
  title/desc 字符串 + asset 文件)
- memory_extraction_json_only 字符串(3 个 locale)
- 对应单测 MemoryExtractionServiceTest
- CLAUDE.md/README 中的相关引用同步更新
- 版本号 1.2.6-rc.2 -> 1.2.6(正式版),versionCode 30 -> 31
- 全量代码审计修复:高危 2 项、中危 30 项
  - file_read KB 分页只返回第一行 / end_kb 放开 / 分块统计
  - Shell 输出只回最后一行 / 超时进程树击杀 / readFile 上限
  - UrlPolicy 字面 IPv4 私网判定 / SimpleHttpClient 响应体上限
  - 图片下载与响应上限生效 / Markdown base64 防 OOM / 中文标签截字
  - 错误日志脱敏 / 导入路径穿越防护
  - Codex 压缩 Token 基线重置 / SSH 连接池竞态 / ToolRegistry 读写锁
  - 持久化后台化 / 工具预算只计实际执行 / Agent 计数并入全局上限
  - 主线程 IO 迁移(Skill 安装/导出/统计)/ 屏幕缓存上限 / UI 细节修复
- update.md: v1.2.6 正式版日志(含 1.2.5 起全部 21 个提交的变更)
@sourcery-ai

sourcery-ai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

Release v1.2.6 formal build focusing on protocol/tooling features, scroll/stream rendering UX, and a broad security/stability audit (file_read, shell/SSH, HTTP/image limits, context compaction, concurrency, and UI).

Sequence diagram for shell execution timeout and process-tree kill

sequenceDiagram
    actor User
    participant ShellExecuteTool
    participant TerminalProviderService

    User->>ShellExecuteTool: executeViaTerminalProvider(command, cwd, timeoutMs)
    ShellExecuteTool->>TerminalProviderService: executeShell(command, cwd, timeoutMs, callback)
    TerminalProviderService->>TerminalProviderService: executor.submit(shell task)
    TerminalProviderService->>TerminalProviderService: process.start()
    TerminalProviderService->>TerminalProviderService: processRef.set(process)
    par StreamOutput
        TerminalProviderService->>callback: onOutput(line) [!finished]
        TerminalProviderService->>callback: onOutput(line) [!finished]
    end
    alt completes before timeout
        TerminalProviderService->>callback: onComplete(exitCode) [!finished]
        TerminalProviderService->>TerminalProviderService: process.destroy()
    else timeout
        TerminalProviderService->>TerminalProviderService: future.cancel(true)
        TerminalProviderService->>TerminalProviderService: killProcessTree(processRef.get())
        TerminalProviderService->>TerminalProviderService: finished.compareAndSet(false, true)
        TerminalProviderService->>callback: onError("命令执行超时")
    end
Loading

Sequence diagram for async latest-wins conversation persistence

sequenceDiagram
    actor User
    participant GenerationFlowController
    participant ConversationPersistenceController
    participant ConversationStore
    participant LearningContextStore

    User->>GenerationFlowController: send message / tools complete
    GenerationFlowController->>ConversationPersistenceController: persistCurrentConversation()
    ConversationPersistenceController->>ConversationPersistenceController: build ConversationRecord
    ConversationPersistenceController->>ConversationPersistenceController: persistSnapshot = conversation
    ConversationPersistenceController->>ConversationPersistenceController: persistScheduled?
    alt first persist
        ConversationPersistenceController->>ConversationPersistenceController: persistScheduled = true
        ConversationPersistenceController->>ConversationPersistenceController: persistExecutor.execute(runPersist)
    else subsequent persists
        ConversationPersistenceController->>ConversationPersistenceController: update persistSnapshot (latest-wins)
    end
    ConversationPersistenceController->>ConversationStore: saveConversation(snapshot) (background)
    alt learning mode enabled
        ConversationPersistenceController->>LearningContextStore: indexConversation(snapshot.getProjectId(), snapshot)
    end
    User->>ConversationPersistenceController: applyConversation(newConversation)
    ConversationPersistenceController->>ConversationPersistenceController: awaitPendingPersist()
    ConversationPersistenceController->>ConversationStore: (all queued writes finished before switch)
Loading

Sequence diagram for global tool-call budget including Agent internal tools

sequenceDiagram
    actor User
    participant GenerationFlowController
    participant AgentExecutionController
    participant ModelClient

    User->>GenerationFlowController: startInitialModelRequest(...)
    GenerationFlowController->>AgentExecutionController: resetExecutedAgentToolCalls()
    GenerationFlowController->>ModelClient: complete(..., messages)
    ModelClient-->>GenerationFlowController: toolCalls
    GenerationFlowController->>GenerationFlowController: effectiveUsedToolCalls(mainFlowUsed)
    GenerationFlowController->>GenerationFlowController: generationController.canExecuteToolCalls(selectedModel, effectiveUsed, toolCalls.size())
    alt over limit
        GenerationFlowController->>GenerationFlowController: generationController.toolLimitMessage(selectedModel, effectiveUsed, toolCalls.size())
        GenerationFlowController->>ModelClient: (no further tool execution)
    else within limit
        GenerationFlowController->>AgentExecutionController: runAgentTool / runAgentPipelineTool(..., toolCallBudget)
        AgentExecutionController->>AgentExecutionController: accumulateExecutedAgentToolCalls(result.getToolCallCount())
        AgentExecutionController-->>GenerationFlowController: ToolResult / AgentRunResult
        GenerationFlowController->>GenerationFlowController: effectiveUsedToolCalls(updatedMainFlowUsed)
        GenerationFlowController->>ModelClient: continueModelAfterTools(..., effectiveUsed)
    end
Loading

File-Level Changes

Change Details Files
Strengthened shell execution and terminal provider safety, correctness, and timeout handling.
  • Track Process via AtomicReference and guard callbacks with finished flag to avoid post-timeout output/errors.
  • Implement killProcessTree via /proc stat scanning, processPid reflection, and Os.kill to terminate timed-out command trees while preserving nohup-style background children on normal exit.
  • Add MAX_READ_FILE_BYTES limit (32MB) to TerminalProvider readFile to avoid OOM on huge files.
terminal-provider/src/main/java/cn/lineai/terminalprovider/TerminalProviderService.java
feature-tool/src/main/java/cn/lineai/tool/builtin/ShellExecuteTool.java
Made ToolRegistry thread-safe to avoid concurrent modification when reloading extensions while tools are read.
  • Introduce ReentrantReadWriteLock and guard all mutations and reads of tools, displayCategoryCache, and extensionStore.
  • Wrap reloadExtensions and removeExtensionTools in write locks; getters and selectors use read locks.
feature-tool/src/main/java/cn/lineai/tool/ToolRegistry.java
Fixed and hardened file_read pagination, line numbering, and performance for large files.
  • Relax end_kb to file size with MAX_KB_RANGE span limit (1MB) and updated parameter description.
  • Align chunk start/end to full lines using RandomAccessFile and direction-correct newline search.
  • Replace per-byte scanning with 64KB buffered countNewlines, adjust lastByteIsNewline handling, and fix phantom trailing line numbering.
  • Add tests covering multi-line pages, mid-file chunks, end_kb>50 semantics, and exact numbered output expectations.
feature-tool/src/main/java/cn/lineai/tool/builtin/FileReadTool.java
feature-tool/src/test/java/cn/lineai/tool/ToolBuiltinsTest.java
Upgraded OpenAI-compatible reasoning strategies with Kimi/Moonshot-specific handling and maintained DeepSeek behavior.
  • Register KimiReasoningStrategy in ReasoningStrategyRegistry and have it clamp temperature to ≥1.0 while managing thinking/keep flags.
  • Narrow MoonshotReasoningStrategy.matches to GLM/Zhipu/bigmodel/MiMo, removing Kimi-matching from it.
  • Ensure OpenAiCompatibleProtocol applies reasoning options for default completion calls and add tests for Kimi, GLM, and DeepSeek behavior.
feature-model/src/main/java/cn/lineai/ai/protocol/OpenAiCompatibleProtocol.java
feature-model/src/main/java/cn/lineai/ai/protocol/reasoning/KimiReasoningStrategy.java
feature-model/src/main/java/cn/lineai/ai/protocol/reasoning/MoonshotReasoningStrategy.java
feature-model/src/test/java/cn/lineai/ai/protocol/OpenAiCompatibleProtocolTest.java
Hardened URL/HTTP/image handling and response-size limits to improve security and robustness.
  • Change UrlPolicy private network detection to literal RFC1918/loopback IPv4 parsing (no hostname prefix hacks).
  • Add MAX_RESPONSE_BODY_BYTES to SimpleHttpClient, enforce in readStream/readBytes, use bounded download defaults.
  • Enforce MAX_RESPONSE_BYTES in ImageResponseParser for JSON responses and MAX_DOWNLOAD_BYTES in ImageApiClient for binary image downloads.
  • Limit Markdown data:image base64 payload size and downsample large bitmaps by computed inSampleSize.
core-security/src/main/java/cn/lineai/security/UrlPolicy.java
core-security/src/main/java/cn/lineai/security/SimpleHttpClient.java
feature-tool/src/main/java/cn/lineai/tool/builtin/ImageResponseParser.java
feature-tool/src/main/java/cn/lineai/tool/builtin/ImageApiClient.java
markdown/src/main/java/cn/lineai/ui/markdown/MarkdownImageView.java
Moved several heavy operations off the main thread and improved concurrency semantics for persistence, SSH, storage, sharing, MCP, and compression catalog queries.
  • Introduce single-threaded Executor with latest-wins snapshot semantics for conversation persistence; add awaitPendingPersist before switching conversations.
  • Fix SshConnectionPool race on first borrow using putIfAbsent plus lock guarding, and safe unlock on release.
  • Run Skill create/install, chat exports, storage stats load, MCP tool queries, and compression model catalog fetch on background threads with main-thread dispatch for UI updates/errors.
  • Make ModelCompressionSectionView query use dynamic ValueSource for base URL/API key, not stale copies.
feature-ssh/src/main/java/cn/lineai/ssh/SshConnectionPool.java
app/src/main/java/cn/lineai/mvp/ConversationPersistenceController.java
app/src/main/java/cn/lineai/mvp/ExtensionManagementController.java
app/src/main/java/cn/lineai/mvp/MainControllerInitializer.java
app/src/main/java/cn/lineai/ui/component/StorageManagementScreenView.java
app/src/main/java/cn/lineai/mvp/ShareController.java
app/src/main/java/cn/lineai/ui/component/McpExtensionEditScreenView.java
app/src/main/java/cn/lineai/ui/component/ModelCompressionSectionView.java
app/src/main/java/cn/lineai/ui/component/ModelAddScreenView.java
Tightened global tool-call budgeting and integrated Agent internal tool calls into the limit accounting.
  • Track executedAgentToolCalls in AgentExecutionController and accumulate counts for Agent and AgentPipeline tools (sync/async).
  • Reset agent tool-call counter at each new generation start and compute effectiveUsedToolCalls combining main flow and Agent usage.
  • Adjust GenerationFlowController tool-limit checks, ToolContext counters, and pending-tool resume paths so only actually executed tools consume budget; paused/declined calls no longer count.
app/src/main/java/cn/lineai/mvp/agent/AgentExecutionController.java
app/src/main/java/cn/lineai/mvp/GenerationFlowController.java
Adjusted learning-context/memory tooling, removed unused MemoryExtractionService, and exposed memory_update under a gated tool group.
  • Remove MemoryExtractionService, its prompt templates, tests, and strings; update CLAUDE/README documentation to describe new learning context behavior.
  • Introduce memory tool group in ToolSettingsRepository, gate visibility of memory_update by learning mode flag via applyLearningModeGate, with unit tests.
  • Add MemoryRankerRagInjectionTest to document/verify MemoryRanker ranking and recency behavior; keep SKILL.md reading bounded via SkillFileManager readStream limit.
app/src/main/java/cn/lineai/mvp/MainDependencies.java
feature-model/src/main/assets/prompts/memory-extraction-template.txt
feature-model/src/main/assets/prompts/skill-extraction-template.txt
data/src/main/java/cn/lineai/data/repository/PromptTemplateRepository.java
app/src/test/java/cn/lineai/data/repository/PromptTemplateRepositoryTest.java
app/src/main/java/cn/lineai/data/repository/ToolSettingsRepository.java
app/src/test/java/cn/lineai/data/repository/ToolSettingsRepositoryTest.java
app/src/test/java/cn/lineai/data/repository/MemoryRankerRagInjectionTest.java
app/src/main/java/cn/lineai/data/service/SkillFileManager.java
CLAUDE.md
README.md
README_CN.md
data/src/main/res/values/strings.xml
data/src/main/res/values-zh/strings.xml
Improved UI behavior and accessibility: fixed screen caching leaks, agent tools headers, storage refresh, PDF export, Markdown image labels, and accessibility click semantics.
  • Bound MainChatView screen cache to SCREEN_CACHE_MAX and explicitly remove oldest views from parent to avoid View/Context leaks.
  • Change agent tools/MCP section headers to use "selected" label and correct format strings in three locales.
  • Fix storage screen refresh button to actually reload stats asynchronously, not trigger back navigation.
  • Fix PdfRenderer line wrapping logic to avoid dropping characters when splitting on width and newline; adjust MarkdownInlineRenderer and MarkdownImageView image labels so full localized tag is shown without truncated closing bracket.
  • Ensure LineCodeAccessibilityService clickFirstClickableNode only clicks the first clickable node while recycling all nodes.
app/src/main/java/cn/lineai/ui/MainChatView.java
app/src/main/java/cn/lineai/ui/component/AgentExtensionEditScreenView.java
app/src/main/java/cn/lineai/ui/component/StorageManagementScreenView.java
feature-share/src/main/java/cn/lineai/share/format/PdfRenderer.java
markdown/src/main/java/cn/lineai/ui/markdown/MarkdownInlineRenderer.java
markdown/src/main/java/cn/lineai/ui/markdown/MarkdownImageView.java
app/src/main/java/cn/lineai/service/LineCodeAccessibilityService.java
app/src/main/res/values/strings.xml
app/src/main/res/values-zh/strings.xml
app/src/main/res/values-ru/strings.xml
Improved error logging and versioning: redacted error summaries, bumped app version, and adjusted error messages.
  • Run ErrorLogRedactor.redact on error summary before persisting logs; continue redacting details field.
  • Add skill_install_failed localized strings and show Toast via ExtensionManagementController.Host.showSkillError on failures.
  • Update app/build.gradle releaseVersionName to 1.2.6 and versionCode to 31, and sync update.md version section to final release name.
data/src/main/java/cn/lineai/log/ErrorLogRepository.java
app/src/main/java/cn/lineai/mvp/MainControllerInitializer.java
app/src/main/res/values/strings.xml
app/src/main/res/values-zh/strings.xml
app/src/main/res/values-ru/strings.xml
app/build.gradle.kts
update.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The asynchronous skill creation/installation methods in ExtensionManagementController now always return null despite a SkillRecord return type; consider changing these to return void or a more appropriate async result to avoid misleading callers.
  • ConversationPersistenceController creates its own single-thread ExecutorService without any shutdown; if controllers are recreated over the app lifecycle, consider sharing a long-lived executor or explicitly managing shutdown to avoid leaking threads.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The asynchronous skill creation/installation methods in ExtensionManagementController now always return null despite a SkillRecord return type; consider changing these to return void or a more appropriate async result to avoid misleading callers.
- ConversationPersistenceController creates its own single-thread ExecutorService without any shutdown; if controllers are recreated over the app lifecycle, consider sharing a long-lived executor or explicitly managing shutdown to avoid leaking threads.

## Individual Comments

### Comment 1
<location path="app/src/test/java/cn/lineai/data/repository/MemoryRankerRagInjectionTest.java" line_range="15-24" />
<code_context>
+public final class MemoryRankerRagInjectionTest {
</code_context>
<issue_to_address>
**suggestion (testing):** Extend `MemoryRankerRagInjectionTest` to assert max-count limiting and relevance threshold behavior.

To more completely specify `MemoryRanker.rank`’s behavior, please also add:

- A case with more candidates than `maxCount`, asserting the list is truncated to `maxCount` and that ordering (by relevance/recency) is preserved.
- A case with a non-zero relevance threshold, asserting that only candidates above the threshold are returned.

These will make ranking semantics more robust against future changes to the heuristic.

Suggested implementation:

```java
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;

```

```java
public final class MemoryRankerRagInjectionTest {

    @Test
    public void maxCountLimitsNumberOfRankedCandidatesAndPreservesOrdering() {
        // 构造 4 个候选,其中两个与查询高度相关,两个仅用于测试截断与排序
        long now = System.currentTimeMillis();
        MemoryRanker.Candidate oldUnrelated = new MemoryRanker.Candidate(
                "c1", "很久以前的无关记忆:天气预报和网页搜索配置。", now - 1000L * 60 * 60, "");
        MemoryRanker.Candidate recentUnrelated = new MemoryRanker.Candidate(
                "c2", "刚刚发生的无关记忆:午饭吃了什么。", now - 1000L * 60, "");
        MemoryRanker.Candidate highRelevanceOlder = new MemoryRanker.Candidate(
                "c3", "当前项目不能使用 AndroidX,必须保持 Java 原生 View。", now - 1000L * 30, "");
        MemoryRanker.Candidate highRelevanceNewest = new MemoryRanker.Candidate(
                "c4", "项目升级到 AndroidX 后,需要更新所有传统 View 的适配。", now, "");

        List<MemoryRanker.Candidate> result = MemoryRanker.rank(
                Arrays.asList(oldUnrelated, recentUnrelated, highRelevanceOlder, highRelevanceNewest),
                "AndroidX 项目 View",
                2,              // maxCount:只返回两个候选
                0.0,            // relevance threshold:不过滤掉低相关,依赖排序与截断
                true);          // recency fallback 打开以保持既有行为

        // 截断到 maxCount
        assertEquals(2, result.size());
        // 排序按照相关度/新鲜度,最相关且最新的候选优先
        assertEquals("c4", result.get(0).getId());
        assertEquals("c3", result.get(1).getId());
    }

    @Test
    public void relevanceThresholdFiltersOutLowRelevanceCandidates() {
        long now = System.currentTimeMillis();
        MemoryRanker.Candidate unrelated = new MemoryRanker.Candidate(
                "u1", "与 AndroidX 完全无关的记忆:天气预报和网页搜索配置。", now - 1000L * 60 * 60, "");
        MemoryRanker.Candidate weakMatch = new MemoryRanker.Candidate(
                "w1", "曾经考虑过升级,但没有提到 AndroidX,只是泛泛而谈项目升级。", now - 1000L * 30, "");
        MemoryRanker.Candidate strongMatch = new MemoryRanker.Candidate(
                "s1", "当前项目已经切换到 AndroidX,需要更新所有旧的 View 实现。", now, "");

        List<MemoryRanker.Candidate> result = MemoryRanker.rank(
                Arrays.asList(unrelated, weakMatch, strongMatch),
                "AndroidX 项目 View 升级",
                10,             // 足够大的 maxCount,不触发截断
                0.5,            // relevance threshold:仅返回相关度足够高的候选
                true);          // recency fallback 打开以保持既有行为

        // 不相关或弱相关的候选应被过滤,只留下强匹配候选
        assertEquals(1, result.size());
        assertEquals("s1", result.get(0).getId());
        // 结果中的候选应该是与查询最相关的记忆
        assertTrue(result.get(0).getText().contains("AndroidX"));
    }

```

These changes assume `MemoryRanker.rank` has the signature:
`rank(List<MemoryRanker.Candidate> candidates, String query, int maxCount, double relevanceThreshold, boolean enableRecencyFallback)` and that `MemoryRanker.Candidate` exposes `getId()` and `getText()` accessors.

If your actual `rank` signature differs (e.g., different parameter order, missing threshold parameter, or different fallback flag type), you will need to:
1. Adjust the arguments passed to `MemoryRanker.rank` in both new tests to match the real signature.
2. If relevance threshold is configured elsewhere (e.g., via configuration object), pass that object instead of the raw `double` shown here and update assertions accordingly.
3. If `Candidate` does not have `getId()`/`getText()` methods, replace those calls with the appropriate accessors or fields used in your existing tests.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +15 to +24
public final class MemoryRankerRagInjectionTest {

@Test
public void matchingMemoryCandidateIsRankedFirstWithPositiveRelevance() {
MemoryRanker.Candidate unrelated = new MemoryRanker.Candidate(
"u1", "天气预报和网页搜索配置。", System.currentTimeMillis(), "");
MemoryRanker.Candidate match = new MemoryRanker.Candidate(
"m1", "当前项目不能使用 AndroidX,必须保持 Java 原生 View。",
System.currentTimeMillis(), "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Extend MemoryRankerRagInjectionTest to assert max-count limiting and relevance threshold behavior.

To more completely specify MemoryRanker.rank’s behavior, please also add:

  • A case with more candidates than maxCount, asserting the list is truncated to maxCount and that ordering (by relevance/recency) is preserved.
  • A case with a non-zero relevance threshold, asserting that only candidates above the threshold are returned.

These will make ranking semantics more robust against future changes to the heuristic.

Suggested implementation:

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
public final class MemoryRankerRagInjectionTest {

    @Test
    public void maxCountLimitsNumberOfRankedCandidatesAndPreservesOrdering() {
        // 构造 4 个候选,其中两个与查询高度相关,两个仅用于测试截断与排序
        long now = System.currentTimeMillis();
        MemoryRanker.Candidate oldUnrelated = new MemoryRanker.Candidate(
                "c1", "很久以前的无关记忆:天气预报和网页搜索配置。", now - 1000L * 60 * 60, "");
        MemoryRanker.Candidate recentUnrelated = new MemoryRanker.Candidate(
                "c2", "刚刚发生的无关记忆:午饭吃了什么。", now - 1000L * 60, "");
        MemoryRanker.Candidate highRelevanceOlder = new MemoryRanker.Candidate(
                "c3", "当前项目不能使用 AndroidX,必须保持 Java 原生 View。", now - 1000L * 30, "");
        MemoryRanker.Candidate highRelevanceNewest = new MemoryRanker.Candidate(
                "c4", "项目升级到 AndroidX 后,需要更新所有传统 View 的适配。", now, "");

        List<MemoryRanker.Candidate> result = MemoryRanker.rank(
                Arrays.asList(oldUnrelated, recentUnrelated, highRelevanceOlder, highRelevanceNewest),
                "AndroidX 项目 View",
                2,              // maxCount:只返回两个候选
                0.0,            // relevance threshold:不过滤掉低相关,依赖排序与截断
                true);          // recency fallback 打开以保持既有行为

        // 截断到 maxCount
        assertEquals(2, result.size());
        // 排序按照相关度/新鲜度,最相关且最新的候选优先
        assertEquals("c4", result.get(0).getId());
        assertEquals("c3", result.get(1).getId());
    }

    @Test
    public void relevanceThresholdFiltersOutLowRelevanceCandidates() {
        long now = System.currentTimeMillis();
        MemoryRanker.Candidate unrelated = new MemoryRanker.Candidate(
                "u1", "与 AndroidX 完全无关的记忆:天气预报和网页搜索配置。", now - 1000L * 60 * 60, "");
        MemoryRanker.Candidate weakMatch = new MemoryRanker.Candidate(
                "w1", "曾经考虑过升级,但没有提到 AndroidX,只是泛泛而谈项目升级。", now - 1000L * 30, "");
        MemoryRanker.Candidate strongMatch = new MemoryRanker.Candidate(
                "s1", "当前项目已经切换到 AndroidX,需要更新所有旧的 View 实现。", now, "");

        List<MemoryRanker.Candidate> result = MemoryRanker.rank(
                Arrays.asList(unrelated, weakMatch, strongMatch),
                "AndroidX 项目 View 升级",
                10,             // 足够大的 maxCount,不触发截断
                0.5,            // relevance threshold:仅返回相关度足够高的候选
                true);          // recency fallback 打开以保持既有行为

        // 不相关或弱相关的候选应被过滤,只留下强匹配候选
        assertEquals(1, result.size());
        assertEquals("s1", result.get(0).getId());
        // 结果中的候选应该是与查询最相关的记忆
        assertTrue(result.get(0).getText().contains("AndroidX"));
    }

These changes assume MemoryRanker.rank has the signature:
rank(List<MemoryRanker.Candidate> candidates, String query, int maxCount, double relevanceThreshold, boolean enableRecencyFallback) and that MemoryRanker.Candidate exposes getId() and getText() accessors.

If your actual rank signature differs (e.g., different parameter order, missing threshold parameter, or different fallback flag type), you will need to:

  1. Adjust the arguments passed to MemoryRanker.rank in both new tests to match the real signature.
  2. If relevance threshold is configured elsewhere (e.g., via configuration object), pass that object instead of the raw double shown here and update assertions accordingly.
  3. If Candidate does not have getId()/getText() methods, replace those calls with the appropriate accessors or fields used in your existing tests.

- ExtensionManagementController/ExtensionController: 异步 skill 创建/安装方法返回类型 SkillRecord -> void(此前恒返回 null 误导调用方),保留 throws 声明兼容调用方 try/catch
- ConversationPersistenceController: 持久化 executor 改为进程级静态共享(控制器重建不再重复创建线程),daemon 线程无需显式关闭
- MemoryRankerRagInjectionTest: 新增 maxCount 截断 + 无关候选过滤两个用例
@LangLang03
LangLang03 merged commit 663d668 into master Aug 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant