Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ac66355
feat(batcher): add same-sender short-window message batching
69gg May 2, 2026
0c24801
docs(architecture): wire MessageBatcher into system + sequence diagrams
69gg May 2, 2026
e83e428
fix(batcher): hold strong refs to timer flush tasks; flush_all awaits…
69gg May 2, 2026
e3f804b
feat(batcher): allow max_window_seconds=0 to disable hard cap
69gg May 2, 2026
78ce050
feat(batcher): add speculative pre-fire with cancellable in-flight LLM
69gg May 2, 2026
dfc6978
docs(batcher): expose speculative state in API/WebUI and tighten README
69gg May 2, 2026
d707a51
fix(batcher): avoid clobbering new bucket when T1 finalizing path com…
69gg May 2, 2026
6c3d7c5
fix(batcher): cancel speculative flush task when inflight not yet reg…
69gg May 2, 2026
a69c75a
fix(batcher): harden speculative prefire races
69gg May 2, 2026
df83fc9
fix(batcher): retry failed speculative prefire
69gg May 2, 2026
71f9c4a
fix(batcher): drain queued batches on shutdown
69gg May 2, 2026
a5d7c4d
fix(ai): defer meme search after text replies
69gg May 2, 2026
137a486
fix(prompt): align current input batch with message batcher
69gg May 2, 2026
665a473
feat(probes): include all skill directories in stats
69gg May 2, 2026
d6e1c43
fix(memory): include batched input in end records
69gg May 2, 2026
1f09ea4
refactor(end): remove legacy summary parameters
69gg May 2, 2026
c32ec65
docs(changelog): add v3.4.0 release notes
69gg May 2, 2026
05e27fb
docs(changelog): clarify v3.4.0 notes
69gg May 2, 2026
2fed91f
docs(changelog): remove file link from v3.4.0 notes
69gg May 2, 2026
21bda6c
ci(release): build notes from changelog
69gg May 2, 2026
e49c6a0
chore(version): bump version to 3.4.0
69gg May 2, 2026
4a5d05e
feat(webui): show changelog on about page
69gg May 3, 2026
5b95f5b
feat(prompts): refine digital persona and naga relation boundaries
69gg May 3, 2026
b06a536
docs(changelog): 添加相关说明
69gg May 3, 2026
fde72df
feat(render): 添加 HTML 渲染结果缓存,避免重复渲染
69gg May 4, 2026
4b1a96a
refactor(commands): 合并 lsadmin/addadmin/rmadmin 为 admin 子命令
69gg May 4, 2026
e35f57f
fix(ai): 为 fire-and-forget task 显式注册异常回调,抑制未检索异常警告
69gg May 4, 2026
89aa4f6
docs(changelog): refine v3.4.0 release notes with detailed change ent…
69gg May 4, 2026
be2e305
refactor(skills): rename auto_pipeline to pipelines, flatten director…
69gg May 4, 2026
d5e4f81
fix(ai): 模型返回文本但未调用工具时不应结束对话
69gg May 4, 2026
841357e
docs(config): add prompt_cache_enabled comments to config.toml.example
69gg May 4, 2026
ca49569
fix: missing_tool_call_retries limit, batcher flush recovery, flush_o…
69gg May 4, 2026
bc24ff0
refactor: render cache 异步化、配置化与 review 收尾
69gg May 4, 2026
f7c254a
chore: update NagaAgent submodule and remove deprecated asset
69gg May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 4 additions & 75 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,46 +38,10 @@ jobs:
- name: Install Python dependencies
run: uv sync --group ci -p ${{ env.PYTHON_VERSION }}

- name: Validate release tag matches package version
- name: Validate release tag, build versions, and changelog
env:
TAG_NAME: ${{ github.ref_name }}
run: |
uv run python - <<'PY'
import os
import pathlib
import re
import sys
import tomllib

tag_name = os.environ["TAG_NAME"]
expected_version = tag_name.removeprefix("v")

pyproject = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
project_version = str(pyproject["project"]["version"]).strip()

init_text = pathlib.Path("src/Undefined/__init__.py").read_text(encoding="utf-8")
match = re.search(r'__version__\s*=\s*"([^"]+)"', init_text)
if match is None:
raise SystemExit("Could not find __version__ in src/Undefined/__init__.py")
init_version = match.group(1).strip()

errors: list[str] = []
if project_version != init_version:
errors.append(
f"Version mismatch: pyproject.toml={project_version}, src/Undefined/__init__.py={init_version}"
)
if project_version != expected_version:
errors.append(
f"Tag/version mismatch: tag={tag_name}, expected package version={expected_version}, actual={project_version}"
)

if errors:
raise SystemExit("\n".join(errors))

print(
f"Validated release version {project_version} from tag {tag_name}, pyproject.toml, and src/Undefined/__init__.py"
)
PY
run: uv run python scripts/release_notes.py validate --tag "$TAG_NAME"

- name: Cache Ruff
uses: actions/cache@v4
Expand Down Expand Up @@ -572,9 +536,6 @@ jobs:
with:
fetch-depth: 0

- name: Fetch tags
run: git fetch --tags --force

- name: Download build artifacts
uses: actions/download-artifact@v4
with:
Expand All @@ -583,47 +544,15 @@ jobs:

- name: Build release notes
shell: bash
run: |
TAG_NAME="${{ github.ref_name }}"
TAG_TYPE=$(git cat-file -t "$TAG_NAME")
TAG_CONTENT=""
if [ "$TAG_TYPE" = "tag" ]; then
TAG_CONTENT=$(git tag -l --format='%(contents)' "$TAG_NAME")
TAG_CONTENT=$(echo "$TAG_CONTENT" | sed '/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----/d')
fi
PREV_TAG=$(git describe --tags --abbrev=0 "${TAG_NAME}^" 2>/dev/null || git rev-list --max-parents=0 HEAD)
if [ -n "$TAG_CONTENT" ]; then
echo "$TAG_CONTENT" > tag_message.txt
printf '\n---\n\n' >> tag_message.txt
else
: > tag_message.txt
fi
cat >> tag_message.txt <<'NOTES'
## 📝 Detailed Changes
NOTES
FEAT_COMMITS=$(git log ${PREV_TAG}..${TAG_NAME} --grep='^feat' --pretty=format:'* %s (%h)' 2>/dev/null || true)
if [ -n "$FEAT_COMMITS" ]; then
echo -e "\n### 🚀 Features" >> tag_message.txt
echo "$FEAT_COMMITS" >> tag_message.txt
fi
FIX_COMMITS=$(git log ${PREV_TAG}..${TAG_NAME} --grep='^fix' --pretty=format:'* %s (%h)' 2>/dev/null || true)
if [ -n "$FIX_COMMITS" ]; then
echo -e "\n### 🐛 Bug Fixes" >> tag_message.txt
echo "$FIX_COMMITS" >> tag_message.txt
fi
OTHER_COMMITS=$(git log ${PREV_TAG}..${TAG_NAME} --grep='^feat\|^fix' --invert-grep --pretty=format:'* %s (%h)' 2>/dev/null || true)
if [ -n "$OTHER_COMMITS" ]; then
echo -e "\n### 🛠 Maintenance & Others" >> tag_message.txt
echo "$OTHER_COMMITS" >> tag_message.txt
fi
run: python3 scripts/release_notes.py notes --tag "${{ github.ref_name }}" --output release_notes.md

- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create ${{ github.ref_name }} release-downloads/* \
--title "Undefined ${{ github.ref_name }}" \
--notes-file tag_message.txt
--notes-file release_notes.md

publish-pypi:
name: Publish Python package to PyPI
Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ Use `<attachment uid="..."/>` for both images and files. The legacy `<pic uid=".
Remote attachments are cached only up to `[attachments].remote_download_max_size_mb`; larger items, or all remote items when the value is `0`, are registered as URL references with `source_ref` instead of downloaded file content.

### Auto processing pipelines
Automatic extraction pipelines live under `src/Undefined/skills/auto_pipeline/pipelines/<name>/` and use `config.json + handler.py`. Slash commands have higher priority; when a command is dispatched, automatic pipelines and AI auto-reply are skipped. Command inputs and command outputs should be recorded in message history so later AI turns can see the result. For non-command messages, all pipelines detect in parallel and all matches process in parallel before AI auto-reply. Outputs should go through `MessageSender`, which writes history and automatically registers local CQ media or uploaded files as session attachment UIDs.
Automatic extraction pipelines live under `src/Undefined/skills/pipelines/<name>/` and use `config.json + handler.py`. Slash commands have higher priority; when a command is dispatched, automatic pipelines and AI auto-reply are skipped. Command inputs and command outputs should be recorded in message history so later AI turns can see the result. For non-command messages, all pipelines detect in parallel and all matches process in parallel before AI auto-reply. Outputs should go through `MessageSender`, which writes history and automatically registers local CQ media or uploaded files as session attachment UIDs.

### Same-sender short-window message batching
Consecutive messages from the same sender within `[message_batcher].window_seconds` are merged into a single AI invocation, so the AI sees the whole batch as `<message>` blocks and decides per-intent (independent request vs. correction/interruption). Pokes always bypass; an at-bot message arriving while a buffer already exists is processed individually so it is not blocked; a first at-bot message that opens the buffer routes the eventual batch through the mention lane. History writes remain unchanged. Configure under `[message_batcher]` (`enabled`, `window_seconds`, `strategy`, `max_window_seconds`, `max_messages_per_batch`, `group_enabled`, `private_enabled`, `pre_send_seconds`, `allow_cancel_after_send`); details in [docs/message-batching.md](docs/message-batching.md). Optional speculative pre-fire (`0 < pre_send_seconds < window_seconds`) dispatches the current batch to the LLM early once the user has been silent for `pre_send_seconds`; new messages can cancel the in-flight call as long as it has not yet sent any reply.

### User identification in prompts
The system prompt now includes a rule: **recognize and address users by their QQ ID (`sender_id`)** because nicknames can change. When needing to address a user, use the latest nickname obtained via `group.get_member_info(brief=true)`. Observations recorded in cognitive memory should always include the QQ ID, e.g., “QQ号12345678(昵称张三)做了某事”.
Expand Down
37 changes: 29 additions & 8 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ graph TB
InjectionAgent["InjectionResponseAgent<br/>注入响应生成<br/>[injection_response_agent.py]"]
end

CommandDispatcher["CommandDispatcher<br/>命令分发器<br/>• /help /stats /lsadmin<br/>• /addadmin /rmadmin<br/>• /bugfix /faq<br/>[services/command.py]"]

CommandDispatcher["CommandDispatcher<br/>命令分发器<br/>• /help /stats /admin<br/>• /bugfix /faq<br/>[services/command.py]"]

MessageBatcher["MessageBatcher<br/>同 sender 短时合并<br/>• 按 (scope, sender_id) 分桶<br/>• T1=window_seconds 结束 batch<br/>• T2=pre_send_seconds 投机预发送<br/>• 拍一拍/buffer 内 @bot 旁路<br/>• 首条 @bot 整批走 mention 队列<br/>[services/message_batcher.py]"]

subgraph QueueSystem["车站-列车 队列系统 (services/)"]
AICoordinator["AICoordinator<br/>AI 协调器<br/>• Prompt 构建<br/>• 队列管理<br/>• 回复执行<br/>[ai_coordinator.py]"]
QueueManager["QueueManager<br/>队列管理器<br/>[queue_manager.py]"]
Expand Down Expand Up @@ -121,7 +123,7 @@ graph TB

subgraph CommandsLayer["平台指令 (skills/commands/)"]
Cmd_Core["核心指令<br/>• help • stats"]
Cmd_Admin["管理指令<br/>• addadmin<br/>• rmadmin • lsadmin"]
Cmd_Admin["管理指令<br/>• admin (ls/add/del)"]
Cmd_FAQ["FAQ 指令<br/>• faq (ls/view/search/del)"]
Cmd_Fun["娱乐指令<br/>• bugfix"]
end
Expand Down Expand Up @@ -226,6 +228,8 @@ graph TB
GitHubSender -->|"发送图片卡片"| OneBotClient

MessageHandler -->|"3. 自动回复"| AICoordinator
AICoordinator -->|"3.1 入桶等待合并"| MessageBatcher
MessageBatcher -->|"3.2 flush 合并批次"| AICoordinator
AICoordinator -->|"创建上下文"| RequestContext
AICoordinator -->|"入队"| QueueManager
QueueManager -->|"分发"| ModelQueues
Expand Down Expand Up @@ -319,7 +323,7 @@ graph TB

class User,Admin,OneBotServer,LLM_API external
class Main,ConfigLoader,ConfigHotReload,ConfigModels,OneBotClient,Context,WebUI core
class MessageHandler,SecurityService,InjectionAgent,CommandDispatcher,AICoordinator message
class MessageHandler,SecurityService,InjectionAgent,CommandDispatcher,MessageBatcher,AICoordinator message
class AIClient,PromptBuilder,ModelRequester,ToolManager,MultimodalAnalyzer,SummaryService,TokenCounter,Parsing ai
class ToolRegistry,AgentRegistry,AgentToolRegistry,IntroGenerator skills
class RequestContext,ContextFilter,ResourceRegistry,HistoryManager,MemoryStorage,EndSummaryStorage,CognitiveService,CognitiveJobQueue,CognitiveHistorian,CognitiveVectorStore,CognitiveProfileStorage,FAQStorage,ScheduledTaskStorage,TokenUsageStorage storage
Expand All @@ -342,6 +346,7 @@ sequenceDiagram
participant MH as MessageHandler
participant SS as SecurityService
participant CD as CommandDispatcher
participant MB as MessageBatcher
participant AC as AICoordinator
participant QM as QueueManager
participant AI as AIClient
Expand Down Expand Up @@ -370,7 +375,7 @@ sequenceDiagram
CD-->>OB: 返回结果
OB->>U: 发送响应
else 非命令消息
MH->>MH: 并行检测 skills/auto_pipeline 管线
MH->>MH: 并行检测 skills/pipelines 管线
opt 命中自动提取管线
MH->>MH: 并行处理全部命中的自动提取
MH->>OH: 发送视频/卡片/PDF
Expand All @@ -380,7 +385,22 @@ sequenceDiagram
end
%% AI处理流程
MH->>AC: handle_auto_reply()

alt 拍一拍 / buffer 内 @bot
AC->>QM: 立即按优先级入队
else 普通消息进合并桶
AC->>MB: submit(BufferedMessage)
MB-->>MB: TYPING: 重置 T1/T2 静默计时
opt T2=pre_send_seconds 到期
MB->>AC: handle_batched_dispatch(投机批次 + BatchDispatchToken)
AC->>QM: 提前入队抢时间
end
MB-->>MB: T1=window_seconds 到期结束 batch
opt 未启用投机或尚未预发送
MB->>AC: handle_batched_dispatch(最终批次)
AC->>QM: 按首条触发的优先级入队
end
end

%% 上下文创建
AC->>AC: 创建 RequestContext
AC->>ST: 保存历史记录
Expand Down Expand Up @@ -831,8 +851,8 @@ description: 从 PDF 文件中提取文本和表格,填写表单。当用户

1. **外部实体层**:用户、管理员、OneBot 协议端 (NapCat/Lagrange.Core)、大模型 API 服务商
2. **核心入口层**:main.py 启动入口、配置管理器 (config/loader.py)、热更新应用器 (config/hot_reload.py)、OneBotClient (onebot.py)、RequestContext (context.py)、Runtime API Server (api/app.py → api/routes/ 路由子模块)
3. **消息处理层**:MessageHandler (handlers.py)、SecurityService (security.py)、CommandDispatcher (services/command.py)、AICoordinator (ai_coordinator.py)、QueueManager (queue_manager.py)、自动处理管线 (skills/auto_pipeline/)、Bilibili/arXiv/GitHub 解析与发送模块
自动提取由 `AutoPipelineRegistry` 并行检测、并行处理全部命中的管线;发送结果写入历史后继续进入 AI 自动回复。
3. **消息处理层**:MessageHandler (handlers.py)、SecurityService (security.py)、CommandDispatcher (services/command.py)、MessageBatcher (services/message_batcher.py)、AICoordinator (ai_coordinator.py)、QueueManager (queue_manager.py)、自动处理管线 (skills/pipelines/)、Bilibili/arXiv/GitHub 解析与发送模块
自动提取由 `PipelineRegistry` 并行检测、并行处理全部命中的管线;发送结果写入历史后继续进入 AI 自动回复。
4. **AI 核心能力层**:AIClient (client.py)、PromptBuilder (prompts.py)、ModelRequester (llm.py)、ToolManager (tooling.py)、MultimodalAnalyzer (multimodal.py)、SummaryService (summaries.py)、TokenCounter (tokens.py)
5. **存储与上下文层**:MessageHistoryManager (utils/history.py, 10000条限制)、MemoryStorage (memory.py, 置顶备忘录, 500条上限)、EndSummaryStorage、CognitiveService + JobQueue + HistorianWorker + VectorStore + ProfileStorage、MemeService + MemeWorker + MemeStore + MemeVectorStore (表情包库)、FAQStorage、ScheduledTaskStorage、TokenUsageStorage (自动归档)
6. **技能系统层**:ToolRegistry (registry.py)、AgentRegistry、6个 Agents、11类 Toolsets
Expand All @@ -847,6 +867,7 @@ description: 从 PDF 文件中提取文本和表格,填写表单。当用户
* **非阻塞发车**:实现了可配置节奏的非阻塞调度循环(默认 **1Hz**)。列车按节奏出发,带走一个请求到后台异步处理。
* **高可用性**:即使前一个请求仍在处理(如耗时的网络搜索),新的请求也会按时被分发,不会造成队列堵塞。
* **优先级管理**:支持四级优先级(超级管理员 > 私聊 > 群聊@ > 群聊普通),确保重要消息优先响应。
* **关停收敛**:`MessageHandler.close()` 会先 flush `MessageBatcher`,再调用 `QueueManager.drain()` 等待已入队请求和在途请求自然完成,最后才停止队列处理器,避免缓冲消息只入队未执行。

### 6个智能体 Agent

Expand Down
Loading
Loading