Skip to content

feat: 并发防重复执行与工具增强#33

Merged
69gg merged 26 commits intomainfrom
bugfix/repeat-tool-call
Feb 17, 2026
Merged

feat: 并发防重复执行与工具增强#33
69gg merged 26 commits intomainfrom
bugfix/repeat-tool-call

Conversation

@69gg
Copy link
Owner

@69gg 69gg commented Feb 17, 2026

核心功能

并发防重复执行(进行中摘要)

  • 对私聊与 @机器人 场景在首轮前预占位,并在后续请求注入"进行中的任务"上下文
  • 减少"催促/追问"导致的重复任务执行
  • 支持通过 features.inflight_summary_enabled 一键开关
  • 新增 models.inflight_summary 模型配置

统计功能增强

  • 强化分析管道和数据摘要
  • 改进 Token 使用统计

工具改进

  • send_text_file:新增轻量级单文件传递工具
  • get_current_time:升级支持农历信息
  • 绘图工具:修复 URL 解析,切换默认模型
  • Bilibili 工具:切换官方接口并加入 WBI 重试
  • 主工具白名单:支持主工具按白名单暴露给 Agent
  • Agent 互调用:按 allowed_callers 裁剪互调可见性

配置与提示词

  • 外部化摘要提示词模板
  • 强化反重复任务防护
  • TTY 输出默认关闭(opt-in)
  • 修复心理委员关键词彩蛋导致的 AI 误判

文档更新

  • 更新 CLAUDE.md 和 README.md,补充新功能说明
  • 添加配置项文档

其他改进

  • 支持更多文件扩展名
  • 分条发送消息以模拟真人
  • 修复 API 相关问题

Open with Devin

69gg added 23 commits February 16, 2026 11:23
在关键词自动回复写入历史时增加系统前缀标记,并仅在群聊且开关开启时向模型注入机制说明。这样可让 AI 正确识别该类消息来自系统彩蛋而非其主动决策。
在工具注册阶段按调用方过滤 call_*,实现看不见就不能点并保留执行期权限校验;同时修复 web_agent callable 配置中的 naga_code_analysis_agent 拼写。
统一要求完整 Cookie,B站下载/搜索/用户信息改为官方接口并在失败时自动补签 WBI。补充用户信息的可选字段参数,提升风控场景下的稳定性与可观测性。
在 AgentToolRegistry 中复用 callable.json 机制,让 skills/tools 下的工具可按 allowed_callers 暴露给子 Agent,且默认不声明时仍仅主 AI 可见并保持本地同名工具优先。将 get_current_time 迁移为共享主工具并移除各 Agent 重复实现,同时补充相关文档以降低维护成本。
Provide async-safe temp-cache uploads with target inference and a configurable 512KB default limit, and steer multi-file tasks to code_delivery_agent to reduce runtime overhead.
Track in-progress tasks per chat and inject them into prompt context to avoid duplicate tool/agent execution during concurrent message gaps.
Move inflight summary and title-generation prompts into res/prompts with file-based loading and safe fallbacks to simplify maintenance.
Validate externalized prompt template placeholders at runtime and add detailed debug logs for inflight summary lifecycle and prompt injection decisions.
Prioritize in-flight task context and enforce a strict triage matrix so follow-up nudges do not retrigger duplicate business tool execution.
Create per-chat inflight placeholder before the initial LLM request for @/private triggers to close the concurrent gap that caused duplicate task execution.
Add features.inflight_summary_enabled (default true) to toggle inflight placeholder injection and async summary generation for anti-duplicate handling.
Improve /stats robustness with safer prompt rendering, richer call-type/model summary dimensions, bounded time-range parsing, and clearer timeout/fallback analysis messaging.
Add README guidance for inflight summary controls, observability keywords, and /stats range+timeout behavior, plus config docs for inflight summary toggles.
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines 995 to 1002

Choose a reason for hiding this comment

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

🟡 Fire-and-forget asyncio.create_task drops task reference, risking silent exception loss

At src/Undefined/ai/client.py:995, asyncio.create_task(self._enqueue_inflight_summary_generation(...)) is called without storing the returned task reference. Python's official documentation warns: "Save a reference to the result of this function, to avoid a task disappearing mid-execution."

Detailed Explanation

While CPython's event loop internally keeps references to scheduled tasks (preventing GC during execution), any unhandled exception from the task will only produce a warning log rather than being properly tracked. The project itself follows a reference-storing pattern elsewhere — for example, self._mcp_init_task = asyncio.create_task(init_mcp_async()) at src/Undefined/ai/client.py:232 and the self._track_inflight_task(inflight_task) pattern in src/Undefined/services/queue_manager.py:407.

The practical impact is mitigated because _enqueue_inflight_summary_generation has its own internal try/except handling, so exceptions won't propagate. However, if the coroutine fails before reaching that handler (e.g., due to a synchronous error in argument preparation), the exception would be silently swallowed with only a "Task exception was never retrieved" warning.

Prompt for agents
In src/Undefined/ai/client.py around line 995, store the task reference returned by asyncio.create_task and track it, similar to how _mcp_init_task is stored at line 232 or how _track_inflight_task is used in queue_manager.py. For example, add the task to a set of background tasks on the AIClient instance and use add_done_callback to remove completed tasks and log any exceptions.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

69gg and others added 3 commits February 17, 2026 15:51
Replace threading.Lock with asyncio.Lock to prevent event loop blocking
in async environment. Add performance metrics and optimize GC triggering.

Changes:
- Replace threading.Lock with asyncio.Lock in InflightTaskStore
- Convert all methods to async (upsert_pending, mark_ready, clear_*, list_for_chat)
- Update all call sites to use await
- Optimize GC: trigger by interval (60s) or threshold (100 entries) instead of every operation
- Add performance metrics: total_upserts, total_mark_ready, total_clears, total_queries,
  total_gc_runs, total_expired_cleaned, anti_duplicate_hits, current_entries
- Improve logging: upgrade key operations to info level, truncate request_id display
- Add comprehensive unit tests (23 test cases covering basic ops, concurrency, TTL/GC, metrics, edge cases)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- wbi.py: add length check to prevent IndexError when WBI keys are too short
- client.py: add exception handler to fire-and-forget inflight summary task

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…witches

- 将进行中摘要功能拆分为两个独立开关:
  - inflight_pre_register_enabled(预注册占位,默认启用)
  - inflight_summary_enabled(摘要生成,默认禁用)
- 重构 Bilibili WBI 请求逻辑:
  - 提取公共函数到 wbi_request.py
  - 优化视频信息获取流程,减少重复请求
  - 修复 WBI 缓存时间戳更新 bug
- 更新相关文档和配置示例

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@69gg 69gg merged commit b8b6828 into main Feb 17, 2026
1 check passed
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 13 additional findings in Devin Review.

Open in Devin Review

Comment on lines +723 to +726
updated = self.ai.set_inflight_summary_generation_result(
request_id,
action_summary,
)

Choose a reason for hiding this comment

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

🔴 Missing await on async set_inflight_summary_generation_result call

In _execute_inflight_summary_generation, the call to self.ai.set_inflight_summary_generation_result(...) at line 723 is missing await. This method is declared async def at src/Undefined/ai/client.py:585 and internally awaits self._inflight_task_store.mark_ready(...). Without await, the coroutine is never executed, so the inflight task store is never updated with the generated summary.

Root Cause and Impact

Because set_inflight_summary_generation_result is async, calling it without await returns a coroutine object instead of a bool. A coroutine object is always truthy, so if updated: on line 727 always evaluates to True, and the log message "更新完成" is emitted even though no update occurred.

The actual mark_ready call inside the store never runs, meaning inflight task records are never promoted from pending to ready status. The summary text generated by the model is silently discarded. Users will only ever see the pending display text ("正在处理消息…") and never the enriched ready text ("正在处理消息…(动作摘要)"), defeating the purpose of the summary generation feature.

Additionally, the unawaited coroutine will trigger a RuntimeWarning: coroutine ... was never awaited at garbage collection time.

Suggested change
updated = self.ai.set_inflight_summary_generation_result(
request_id,
action_summary,
)
updated = await self.ai.set_inflight_summary_generation_result(
request_id,
action_summary,
)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +796 to +799
should_pre_register = (
inflight_summary_enabled
and self._should_pre_register_inflight(pre_context, question)
)

Choose a reason for hiding this comment

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

🔴 Pre-registration is gated on inflight_summary_enabled, contradicting documented independent control

The should_pre_register flag at line 796-798 requires inflight_summary_enabled to be True before pre-registration can occur. Since inflight_summary_enabled defaults to False, the pre-registration feature (inflight_pre_register_enabled, default True) is effectively dead out-of-the-box.

Detailed Explanation

The code at src/Undefined/ai/client.py:796-798:

should_pre_register = (
    inflight_summary_enabled
    and self._should_pre_register_inflight(pre_context, question)
)

This means both inflight_summary_enabled AND inflight_pre_register_enabled must be true for pre-registration to work. But the documentation (README.md line 82, config.toml.example lines 269-283, and the PR description) explicitly states these two switches are "独立控制" (independently controlled):

  • inflight_pre_register_enabled (default true) — pre-register placeholder
  • inflight_summary_enabled (default false) — generate action summary

With the default config (inflight_summary_enabled=false, inflight_pre_register_enabled=true), users expect pre-registration to work but it silently does nothing. The anti-duplicate-execution feature, which is the primary goal of this PR, is non-functional unless users also enable inflight_summary_enabled.

The fix should check inflight_pre_register_enabled independently for pre-registration, and only gate summary generation on inflight_summary_enabled.

Suggested change
should_pre_register = (
inflight_summary_enabled
and self._should_pre_register_inflight(pre_context, question)
)
should_pre_register = self._should_pre_register_inflight(
pre_context, question
)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Comments