Commit Graph

53 Commits

Author SHA1 Message Date
matevip
e46f176697 fix(agent): unify context assembly and preserve compacted history 2026-10-30 07:45:50 -04:00
matevip
ddbd37b51b fix: unify prompt budgets on AgentScope 2.0.4 2026-10-20 04:03:19 -04:00
matevip
507fceb2d0 feat(agent): strengthen decision facts and long-task context budgets 2026-10-14 02:16:21 -04:00
matevip
93ba4258e5 feat(runtime): migrate to Spring Boot 4, Spring AI 2 and AgentScope 2 (#660) 2026-10-12 05:41:24 -04:00
taobig
baeb3f982a feat(workspace): add opt-in member file isolation (#644) 2026-09-22 23:13:08 -04:00
matevip
3fbe5a18a1 fix(channels): expire stale query context in persistent conversations 2026-09-21 10:45:50 -04:00
mateaix
3a7bbe4645 preflight selected queued Goals before agent execution 2026-09-15 06:50:17 +08:00
mateaix
993be51e72 Retain selected Goal identity through approval lifecycle 2026-09-15 05:26:30 +08:00
matevip
7406fc99bf feat(execution): add persistent execution evidence in observe mode 2026-09-07 06:38:58 -04:00
matevip
bd41a71826 fix(cron): harden long-task reliability 2026-09-02 05:56:06 -04:00
taobig
2090d09704 fix(goal): preserve continuation instructions and verified progress
Keep autonomous prompts separate from persisted user messages while retaining
conversation history and queued user attachment routing. Carry verified
checklist evidence across segments and evaluate only changed criteria.

Cancel model streams without interrupting checkpoint database writes. Fence
late worker admission during shutdown, persist accepted queued messages and
attachments, and leave interrupted execution leases recoverable.

Add regressions for prompt selection, cumulative evidence, cancellation I/O,
queued input durability and shutdown recovery. Verify 250 focused tests,
200 real General Assistant conversation rounds, 12 checkpoints across 13
autonomous segments, and pause/resume/disconnect/restart boundaries.
2026-08-26 06:17:05 -04:00
taobig
2fa2e60170 feat(goal): persist continuous execution across bounded turns
- Add a database-backed supervisor with durable scheduling, fenced leases,
  cooldowns, bounded worker concurrency and expired-lease restart recovery.
- Default new goals to persistent execution with zero meaning unlimited
  cumulative budget; preserve legacy goals and explicit positive limits.
- Yield bounded graph segments to the supervisor instead of ending unfinished
  goals at graph-local continuation limits. Require persisted checklist
  evidence before accepting completion, including concurrent criterion edits.
- Share conversation admission across interactive and background execution;
  preserve partial replies, usage and queued user input during interruption.
- Persist Stop and missing-input pauses, respect approval boundaries, and
  commit resume and approval-denial transitions with correct transactions.
- Retry identifiable transient failures with backoff; retain visible pauses
  for budget limits and errors that require review instead of replaying tools.
- Expose owner-authorized execution status and reconnectable scheduling events;
  add H2, MySQL and Kingbase migrations, API types and bilingual documentation.

Validation: 298 focused backend tests passed, including persistence, restart
scheduling, approval races, cancellation, admission and existing runtime tests.
Frontend type checking, bundled-doc parity and ID precision checks passed.
V188 is registered and all three dialects have unique migration versions;
the migration-map audit still reports 95 pre-existing missing registrations.

Scope: single-backend native runtime. Recovery checks existing state before
repeating effects; this does not promise exactly-once external tool execution.
2026-08-26 05:46:15 -04:00
matevip
dc3506f748 feat(team): unify team run experience (#596) 2026-08-13 07:00:24 -04:00
mateaix
8dc6c64683 perf(agent): add progressive tool bridge 2026-08-09 19:39:45 +08:00
matevip
728ed53062 feat(skill): scope agent runtime skill resolution to the conversation workspace 2026-07-24 14:12:08 +08:00
matevip
6802fc1c6a feat(chat): context occupancy panel with per-source breakdown (#492) 2026-07-06 16:44:59 +08:00
MIST
e4dd08b5f4
feat(agent): 注意力锚定与环境感知——MCP 工具溯源 + skill 约束固定 + 事件通知 (#490)
* feat(agent): 注意力锚定与环境感知——MCP 工具溯源 + skill 约束固定 + 事件通知

## 背景

1. **MCP 工具跨服务器混淆**:MCP 工具名是 `mcp_<serverId>_<slug>_<hash6>`,serverId 是 19 位不可读 Snowflake。LLM 在多服务器任务中常把 slug 拼到错误 serverId 上重构出不存在的工具名,反复重试到 max iterations。
2. **长对话中 skill 约束丢失**:`load_skill` 返回的 SKILL.md 正文存在 messages 历史窗口里,被压缩管线(Soft Trim / Hard Clear / Pre-Prune / LLM Summary)销毁,约束彻底消失,agent 后续步骤违反约束。
3. **运行时环境变更对 agent 不可见**:MCP 服务器断连 / skill 更新发生在 agent 推理中途时,工具列表是 turn-start 快照,LLM 无法感知,继续调用已失效的工具。
4. **ledger 条目可被 LLM 反向覆盖**:Java 用 `auto_`/`pin_` 前缀让位给 LLM,但 LLM 没有反向保护——`progress_update(stepKey="auto_read_file")` 会覆盖 Java 写入的条目,保护是单向的。
5. **SkillManifestParser 从未填充 constraints 字段**:`KNOWN_KEYS` 未列入 `"constraints"`,导致约束被静默路由到 `extras`,所有依赖 `manifest.getConstraints()` 的代码都是死代码。

## 改动内容

### 文件改动

**新增文件(生产代码 4 个)**
- **`mateclaw-server/.../agent/runtime/EnvironmentNotification.java`** — 环境变更通知 record(type / message / timestamp)。
- **`mateclaw-server/.../agent/runtime/RunningConversationRegistry.java`** — 跟踪活跃会话 + 每会话有界通知队列(上限 10)+ TTL 定时清理(30 分钟未活跃的 handle 自动回收)。
- **`mateclaw-server/.../agent/runtime/EnvironmentEventRouter.java`** — 5 个 `@EventListener` 把 MCP/skill 事件翻译成中文 LLM 通知并广播。
- **`mateclaw-server/.../skill/event/SkillUpdatedEvent.java`** — skill 更新/启用/禁用/重扫描事件。

**新增文件(测试 5 个)**
- **`mateclaw-server/.../skill/manifest/SkillManifestConstraintsParsingTest.java`** — constraints 解析白盒测试(5 用例)。
- **`mateclaw-server/.../agent/progress/ProgressLedgerPrefixGuardTest.java`** — 前缀守卫 + 三类条目 + 并发 + 批量 auto-record 白盒(22 用例)。
- **`mateclaw-server/.../agent/runtime/RunningConversationRegistryTest.java`** — registry + router 生命周期 + TTL 清理白盒(24 用例)。
- **`mateclaw-server/.../agent/context/ContextCompressionLedgerSurvivalTest.java`** — 三类条目压缩存活黑盒(5 用例)。
- **`mateclaw-server/.../agent/graph/node/EnvironmentNotificationRenderingTest.java`** — 事件→通知→LLM 可见黑盒(14 用例)。

**修改文件(生产代码 14 个)**
- **`mateclaw-server/.../agent/progress/ProgressLedger.java`** — 增加 `pinned` map + `AUTO_RECORDED_PREFIX` 常量 + 三类条目区分;`mostRecentUpdate` 只看 regular 条目;`renderStaleReminder` 补 pending 计数。
- **`mateclaw-server/.../agent/progress/ProgressLedgerService.java`** — JSON 格式升级为 wrapper `{entries, pinned}`(向后兼容旧 flat-map);`upsert` 加 `auto_`/`pin_` 前缀守卫;`upsertPinned` / `upsertAutoRecorded` / `clearPinnedByPrefix` / `upsertAutoRecordedBatch`(批量版,一次 lock+load+save 处理 N 个工具响应);auto-recorded 4 参签名避免跨服务器键碰撞,有界=5。
- **`mateclaw-server/.../agent/graph/node/ActionNode.java`** — `load_skill` 后 `pinSkillConstraints` 把约束写入 pinned;工具调用后 `autoRecordToolCalls` 收集批量后一次 `upsertAutoRecordedBatch`(避免 N 次 lock+save 串行化);setter 注入保持测试构造器兼容。
- **`mateclaw-server/.../agent/graph/node/ReasoningNode.java`** — C4 注入:drain 通知 → `renderEnvironmentNotifications` → SystemMessage 加入 nonHistoryPrefix;helper 改 package-private 供黑盒测试。
- **`mateclaw-server/.../agent/AgentGraphBuilder.java`** — 系统提示增加 ProgressLedger Discipline 段(agent-3)+ Environment Change Notifications 段(agent-1);SkillCatalog 渲染器扫描约束加 🔒 锚点(agent-4);wire ActionNode setter + ReasoningNode registry。
- **`mateclaw-server/.../agent/AgentService.java`** — `withLifecycleSync` / `withLifecycleFlux` 入口 `safeRegister`、出口 `safeUnregister`,覆盖 Flux 抛错路径。
- **`mateclaw-server/.../skill/manifest/SkillManifest.java`** — 增加 `constraints` 字段(List<String>)。
- **`mateclaw-server/.../skill/manifest/SkillManifestParser.java`** — `KNOWN_KEYS` 加 `"constraints"`;builder 链加 `.constraints(stringList(fm.get("constraints")))`。
- **`mateclaw-server/.../skill/service/SkillService.java`** — 4 个改动点发布 `SkillUpdatedEvent`(rescan / update builtin / update non-builtin / toggle enable-disable)。
- **`mateclaw-server/.../tool/builtin/ProgressLedgerTool.java`** — `@Tool` 描述声明 `auto_`/`pin_` 前缀保留;`@ToolParam stepKey` 同步警告。
- **`mateclaw-server/.../tool/mcp/runtime/PrefixedNameToolCallback.java`** — 新增 3 参构造器,serverName 非空时描述前缀 `[MCP server: <name>]`,让 LLM 区分跨服务器同名工具。
- **`mateclaw-server/.../tool/mcp/runtime/McpClientManager.java`** — `wrapServerCallbacks` 透传 serverName 到 PrefixedNameToolCallback。
- **`mateclaw-server/.../agent/context/ConversationWindowManager.java`** — `PRUNE_EXEMPT_TOOLS` 加入 `load_skill`(A1)。
- **`mateclaw-server/.../agent/graph/executor/ToolExecutionExecutor.java`** — 工具不存在时 `buildMcpAwareNotFoundMessage` 跨服务器搜索同 slug/hash 候选,给出 ≤5 个建议名。

**修改文件(测试 1 个)**
- **`mateclaw-server/.../agent/progress/ProgressLedgerStaleReminderTest.java`** — 回归适配:reminder 文本现在包含 `pending` 计数。

### 测试

- `mvn -pl mateclaw-server -am test -Dtest='SkillManifestConstraintsParsingTest,ProgressLedgerPrefixGuardTest,RunningConversationRegistryTest,ContextCompressionLedgerSurvivalTest,EnvironmentNotificationRenderingTest,ProgressLedgerStaleReminderTest'`:70/70 通过
- `mvn -pl mateclaw-server -am test`(全量回归,含上面 6 个 + 12 个深挖影响类):0 失败 0 错误

### 安全性

- **前缀保留**:`ProgressLedgerService.upsert` 拒绝 `auto_`/`pin_` 前缀,LLM 无法覆盖 Java 管理的条目;`@Tool` 描述显式声明保留前缀。
- **事件路由异常隔离**:`EnvironmentEventRouter.broadcast` 全 try/catch,路由失败永不冒泡到 Spring 事件总线。
- **并发安全**:registry 用 `ConcurrentHashMap` + `ConcurrentLinkedQueue`;ledger upsert 用 per-conversation `ReentrantLock`;批量 auto-record 在单次 lock 内完成;`ProgressLedgerPrefixGuardTest.concurrentUpsertAndAutoRecordAreSafe` 锁定。
- **内存有界**:通知队列每会话上限 10(LRU 驱逐最老);auto-recorded 条目每会话上限 5(驱逐最老);registry 后台 TTL 清理(30 分钟未活跃的 handle 自动回收)。
- **绑定机制不受影响**:MCP/skill 的 agent 绑定(`mate_agent_tool` / `mate_agent_skill` 表)完全未被触碰;C3 通知广播是有意全量(非按 agentId 过滤),最坏情况是无关 agent 多收一条 SystemMessage(LLM 被告知"如无关可忽略")。

## 逐项验证

### 改动 1:SkillManifestParser 真正解析 constraints(深挖修复)

**文件**:`mateclaw-server/src/main/java/vip/mate/skill/manifest/SkillManifestParser.java:33-47,108`

| 项 | 内容 |
|---|---|
| 改了什么 | `KNOWN_KEYS` 集合加入 `"constraints"`;builder 链加入 `.constraints(stringList(fm.get("constraints")))`。 |
| 为什么 | 之前 `KNOWN_KEYS` 没列入 `"constraints"`,导致该键被静默路由到 `extras`,`manifest.getConstraints()` 永远返回空 list,下游 B2 pinSkillConstraints 和 agent-4 catalog 锚点全是死代码。 |
| 验证步骤 | 1. `cat test-fixtures/skill-with-constraints/SKILL.md`(如有)确认 frontmatter 有 `constraints: [...]`;2. 运行 `SkillManifestConstraintsParsingTest`。 |
| 预期结果 | `manifest.getConstraints()` 返回非空 list;test 5/5 通过。 |

### 改动 2:ProgressLedgerService 前缀守卫(深挖修复)

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/progress/ProgressLedgerService.java:132-137`

| 项 | 内容 |
|---|---|
| 改了什么 | `upsert()` 入口检查 key 是否以 `auto_` 或 `pin_` 开头,是则抛 `IllegalArgumentException`。 |
| 为什么 | 之前保护是单向的:Java 让位 LLM(auto 不覆盖 LLM 已有 entry),但 LLM 可以用 `progress_update(stepKey="auto_read_file")` 覆盖 Java 写入的条目,导致 auto-recorded 工具记录被改写。 |
| 验证步骤 | 1. `ProgressLedgerPrefixGuardTest.upsertRejectsAutoPrefix`;2. `ProgressLedgerPrefixGuardTest.upsertRejectsPinPrefix`。 |
| 预期结果 | 两个测试均抛 `IllegalArgumentException`;`ProgressLedgerTool` 的 `@Tool` 描述包含前缀保留声明。 |

### 改动 3:upsertAutoRecorded 4 参签名 + 批量化(深挖修复 + 性能优化)

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/progress/ProgressLedgerService.java:227-296`、`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ActionNode.java`(`autoRecordToolCalls`)

| 项 | 内容 |
|---|---|
| 改了什么 | `upsertAutoRecorded` 升级为 4 参签名 `(conversationId, toolName, displayName, resultSummary)`;新增 `upsertAutoRecordedBatch` 批量方法,一次 lock+load+save 处理 N 个工具响应;ActionNode 改为先收集 `List<AutoRecordEntry>` 再一次批量调用。 |
| 为什么 | 1. 跨服务器键碰撞:两个 MCP 服务器都暴露 `search` 工具 → `auto_search` 互相覆盖;2. 并行工具串行化:每个 ToolResponse 单独 lock+load+save 抵消并行收益。 |
| 验证步骤 | 1. `ProgressLedgerPrefixGuardTest.autoRecordedDifferentServersNoCollision`:两个服务器同名工具共存;2. `ProgressLedgerPrefixGuardTest.batchInsertProducesSameResultAsSequential`:批量与逐条结果一致;3. `ProgressLedgerPrefixGuardTest.batchInsertBoundedToMaxFiveEvenWithLargeBatch`:10 条批量插入后有界=5。 |
| 预期结果 | ledger 中同时存在 `auto_mcp_4_search_xxx` 和 `auto_mcp_7_search_yyy`;5 个并行工具调用从 5 次 lock+save 降为 1 次。 |

### 改动 4:B2 pinSkillConstraints——load_skill 后约束写入 pinned

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ActionNode.java`(`pinSkillConstraints`)

| 项 | 内容 |
|---|---|
| 改了什么 | `load_skill` 工具调用成功后,读取 `manifest.getConstraints()`,对每条约束调用 `progressLedgerService.upsertPinned(convId, "pin_<skillName>_<i>", constraintText, note)`。 |
| 为什么 | 把约束从 messages(会被压缩销毁)抽到 DB ledger.pinned(压缩免疫),解决"长对话中 skill 约束丢失"问题。 |
| 验证步骤 | 1. `ContextCompressionLedgerSurvivalTest.loadSkillBodyDestroyedByCompressionButConstraintsSurviveInLedger`;2. `ContextCompressionLedgerSurvivalTest.allThreeLedgerEntryTypesSurviveCompression`。 |
| 预期结果 | 压缩后 messages 中 load_skill 正文消失,但 `ledger.renderSnapshot()` 仍包含 `🔒 固定约束` 段,约束文本字节级保留。 |

### 改动 5:B5 autoRecordToolCalls——工具调用后批量自动记录

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ActionNode.java`(`autoRecordToolCalls`)

| 项 | 内容 |
|---|---|
| 改了什么 | ActionNode 处理 ToolResponse 后,收集所有有效条目到 `List<AutoRecordEntry>`,一次调用 `upsertAutoRecordedBatch`。 |
| 为什么 | 让 LLM 在长对话中即使忘记自己刚调用过什么工具,也能从 ledger 快照看到最近 5 次工具调用记录;批量调用避免 N 次 lock+save 串行化。 |
| 验证步骤 | `ProgressLedgerPrefixGuardTest.autoRecordedBoundedToMaxFive`:模拟 10 次工具调用,验证 auto 条目数等于 5。 |
| 预期结果 | ledger 中 auto 条目始终 ≤ 5,最老的被驱逐;5 个并行工具调用只需 1 次 DB roundtrip。 |

### 改动 6:C4 环境通知注入 nonHistoryPrefix

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java:755-765,1201-1215`

| 项 | 内容 |
|---|---|
| 改了什么 | ReasoningNode 每轮推理前 `registry.drain(conversationId)`,非空则 `renderEnvironmentNotifications` 渲染成 markdown 块,作为 SystemMessage 加入 nonHistoryPrefix。 |
| 为什么 | 让运行时环境变更(MCP 断连 / skill 更新)在下一轮推理立即可见,LLM 主动改路而不是反复重试失效工具。 |
| 验证步骤 | `EnvironmentNotificationRenderingTest.mcpConnectionLostEventEndToEnd_producesActionableLLMText`:注册会话 → 触发 `McpConnectionLostEvent(serverId=7)` → drain → render。 |
| 预期结果 | 渲染块包含 "📢 环境变更通知"、`mcp_7_` 前缀、"不要反复重试" 指令。 |

### 改动 7:A1 PRUNE_EXEMPT_TOOLS 加入 load_skill

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/context/ConversationWindowManager.java:96-120`

| 项 | 内容 |
|---|---|
| 改了什么 | `PRUNE_EXEMPT_TOOLS` 集合从 `{delegateToAgent, delegateParallel}` 扩展为 `{delegateToAgent, delegateParallel, load_skill}`。 |
| 为什么 | `load_skill` 返回的 SKILL.md 是 load-time 快照,skill 作者可能在执行期间更新,重载不保证恢复相同指令;且 50KB+ skill 重载昂贵。 |
| 验证步骤 | `ConversationWindowManagerToolPruningTest`(已有测试套件)。 |
| 预期结果 | load_skill 的 ToolResponseMessage 在 `pruneOldToolResultsForModelInput` / `compactAgedToolResponses` 阶段不被修剪。 |

### 改动 8:agent-2 PrefixedNameToolCallback 描述加 serverName 标签

**文件**:`mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/PrefixedNameToolCallback.java:55-80`、`mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/McpClientManager.java:201-300`

| 项 | 内容 |
|---|---|
| 改了什么 | 新增 3 参构造器 `(prefixedName, delegate, serverName)`,serverName 非空时描述前缀 `[MCP server: <name>]`;McpClientManager `wrapServerCallbacks` 透传 serverName。 |
| 为什么 | LLM 看到 `mcp_4_search_a1b2c3` 时无法知道这是哪个服务器的工具;多个 MCP 服务器都暴露 `search` 时,LLM 会混淆。加 `[MCP server: fetch-server]` 标签让 LLM 区分。 |
| 验证步骤 | 启动一个 MCP 服务器,在 agent 工具列表中观察工具描述是否包含 `[MCP server: <name>]` 前缀。 |
| 预期结果 | 每个 MCP 工具描述开头包含 `[MCP server: <serverName>]`;2 参构造器(无 serverName)保持向后兼容,描述不加前缀。 |

### 改动 9:ToolExecutionExecutor 工具不存在时跨服务器候选建议

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/graph/executor/ToolExecutionExecutor.java:1264-1340`

| 项 | 内容 |
|---|---|
| 改了什么 | "Tool not found" 错误信息升级:若请求名是 MCP 格式,搜索 `toolCallbackMap` 中 slug 或 hash6 匹配但 serverId 不同的候选,返回 ≤5 个建议。 |
| 为什么 | LLM 常把 slug 拼到错误 serverId 上重构出不存在工具名,反复重试到 max iterations。给候选建议后 LLM 可以直接复制正确名字。 |
| 验证步骤 | 1. 启动两个 MCP 服务器都暴露 `fetch` 工具;2. 让 LLM 调用 `mcp_<serverA>_fetch_xxx`(实际 fetch 在 serverB);3. 观察错误信息。 |
| 预期结果 | 错误信息包含 "Did you mean one of these?" + 正确的 `mcp_<serverB>_fetch_yyy` 候选名。 |

### 改动 10:Registry TTL 定时清理(防泄漏)

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/runtime/RunningConversationRegistry.java:155-211`

| 项 | 内容 |
|---|---|
| 改了什么 | 新增 `cleanupStale(Duration maxAge)` 方法 + `@Scheduled scheduledCleanup()`(每 5 分钟扫一次,清理 30 分钟未活跃的 handle)。用 `remove(key, value)` 保证不误删被并发 `register` 刷新的 handle。 |
| 为什么 | 兜底防御异常路径泄漏的 handle——即使 `safeUnregister` 因异常路径未执行(如 Reactor cancel 信号不触发 doFinally),后台线程也能回收。 |
| 验证步骤 | `RunningConversationRegistryTest.cleanupStaleRemovesOldHandles`:注册 → 反射 backdate lastActiveAt → 清理 → 验证被移除;`cleanupStaleDoesNotRemoveRefreshedHandle`:backdate 后 re-register → 清理 → 验证存活。 |
| 预期结果 | 30 分钟未活跃的 handle 被清理;被 `register` 刷新的 handle 不被误删。 |

### 改动 11:JSON 格式向后兼容迁移

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/progress/ProgressLedgerService.java:79-85,284-292`

| 项 | 内容 |
|---|---|
| 改了什么 | JSON 从 flat-map `{"step1":{...}}` 升级为 wrapper `{"entries":{...},"pinned":{...}}`;`parseWrapper` 通过 peek `"entries"` 键区分新旧格式,旧格式自动迁移为 wrapper(pinned 为空)。 |
| 为什么 | 老 conversation 的 ledger 列存的是 flat-map,新代码上线后必须能加载老数据。 |
| 验证步骤 | `ContextCompressionLedgerSurvivalTest.oldFlatMapLedgerMigratesToWrapperFormatWithEmptyPinned`:写入旧 JSON → load → 验证 pinned 为空 → upsert → 验证新 JSON 包含 `entries` 和 `pinned` 键。 |
| 预期结果 | 旧 conversation 无需迁移脚本,第一次 load 即兼容;写入时自动转为新格式。 |

## 新增测试验证

**文件**:
- `mateclaw-server/src/test/java/vip/mate/skill/manifest/SkillManifestConstraintsParsingTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/progress/ProgressLedgerPrefixGuardTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/runtime/RunningConversationRegistryTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/context/ContextCompressionLedgerSurvivalTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/graph/node/EnvironmentNotificationRenderingTest.java`

| 命令 | 预期 |
|------|------|
| `mvn -pl mateclaw-server -am test -Dtest='SkillManifestConstraintsParsingTest'` | Tests run: 5, Failures: 0 |
| `mvn -pl mateclaw-server -am test -Dtest='ProgressLedgerPrefixGuardTest'` | Tests run: 22, Failures: 0 |
| `mvn -pl mateclaw-server -am test -Dtest='RunningConversationRegistryTest'` | Tests run: 24, Failures: 0 |
| `mvn -pl mateclaw-server -am test -Dtest='ContextCompressionLedgerSurvivalTest'` | Tests run: 5, Failures: 0 |
| `mvn -pl mateclaw-server -am test -Dtest='EnvironmentNotificationRenderingTest'` | Tests run: 14, Failures: 0 |

## 回归检查清单

- [ ] 全量 `mvn -pl mateclaw-server -am test` 通过(已验证 0 失败 0 错误)
- [ ] 老 conversation(flat-map ledger JSON)首次 load 不报错,pinned 字段为空
- [ ] 多 MCP 服务器场景:LLM 工具列表中每个工具描述包含 `[MCP server: <name>]` 标签
- [ ] MCP 服务器中途断连:agent 下一轮推理看到 `📢 环境变更通知` 块,主动改路
- [ ] 长 conversation(>100 轮)经多次 PTL 压缩后,`ledger.renderSnapshot()` 仍包含 pinned 约束
- [ ] `progress_update(stepKey="auto_xxx")` 被拒绝,返回 `IllegalArgumentException` 错误信息
- [ ] auto-recorded 条目数始终 ≤ 5(10 次工具调用后仍为 5)
- [ ] 5 个并行工具调用只产生 1 次 DB roundtrip(批量 auto-record)
- [ ] Registry 中 30 分钟未活跃的 handle 被后台定时清理
- [ ] MCP/skill 绑定机制(`mate_agent_tool` / `mate_agent_skill` 表)不受影响
- [ ] Plan-Execute 路径(StepExecutionNode)目前**不**接收环境通知——只有 ReAct 路径生效(已知未覆盖项,不阻塞本 PR)

* feat(agent): 六招减法重构——修复压缩销毁 skill 约束与 MCP 按需暴露

## 背景

- 压缩三阶段(softTrim / hardClear / prePruneForSummary)只检查 `isSpillMarker`,不检查 `PRUNE_EXEMPT_TOOLS`,导致 `load_skill` 返回的 SKILL.md 约束、`delegateToAgent` 子智能体转录在压缩中被裁掉,模型在长对话中"忘记"任务规则,根因是"压缩导致注意力失效"。
- skillCatalog 表只列 Skill / Status / Description 三列,bound skill 的 constraints 与 allowedTools 没有任何可见入口,模型加载 skill 后约束仍可能被忽略。
- MCP 工具默认 CORE tier,20+ MCP 工具的 schema 涌入核心列表,挤占 builtin 工具的注意力,且 `DisclosureTier.fromToken(null)` 返回 CORE 导致 `getOrDefault` 的默认值永远不生效。
- 构建期工具过滤分 4 次 pass,重复遍历且无明确 deny/allow 边界。
- skillCatalog 在每次推理步都按当前 loadedSkills 动态渲染,破坏 Anthropic SYSTEM_AND_TOOLS cache 前缀稳定性。
- 进度账本(ProgressLedger)约束条目前缀无保护,跨 MCP server 键碰撞,环境事件无统一路由入口。

## 改动内容

### 文件改动

**主代码(21 个文件)**

- **`mateclaw-server/src/main/java/vip/mate/agent/context/ConversationWindowManager.java`** — Move 4:三阶段新增 `isExemptTool` 检查跳过 `load_skill`/`delegateToAgent`/`delegateParallel`;新增 Phase 2.7 无损 spill evict 在调用 LLM 摘要前把超大工具结果落盘
- **`mateclaw-server/src/main/java/vip/mate/skill/runtime/SkillRuntimeService.java`** — Move 2 & 3:catalog 表新增 Constraints 列(仅 bound skill 显示);新增 `### Bound skill allowed tools` 块
- **`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java`** — Move 1:skillCatalog 用 `render(Set.of())` 静态渲染进 nonHistoryPrefix;loadedThisRun hint 作为 volatile 后缀注入
- **`mateclaw-server/src/main/java/vip/mate/tool/disclosure/DefaultToolDisclosureService.java`** — Move 5:MCP 工具默认 tier 从 CORE 改 EXTENSION;`buildSnapshot` 跳过 null/blank tier 修复 `fromToken(null)→CORE` 陷阱
- **`mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java`** — Move 6:构建期权限过滤从 4 次 pass 合并为 2 次(deny 集 + allow 集)
- **`mateclaw-server/src/main/java/vip/mate/agent/AgentService.java`** — 接入 EnvironmentEventRouter 与 RunningConversationRegistry
- **`mateclaw-server/src/main/java/vip/mate/agent/graph/executor/ToolExecutionExecutor.java`** — 工具调用后自动回填 ProgressLedger
- **`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ActionNode.java`** — 渲染 ledger 三段式快照
- **`mateclaw-server/src/main/java/vip/mate/agent/progress/ProgressLedger.java`** — constraints 前缀保护,跨 MCP server 键命名空间隔离
- **`mateclaw-server/src/main/java/vip/mate/agent/progress/ProgressLedgerService.java`** — 写入 constraints 到 pinned 条目
- **`mateclaw-server/src/main/java/vip/mate/skill/manifest/SkillManifest.java`** — 新增 constraints 字段
- **`mateclaw-server/src/main/java/vip/mate/skill/manifest/SkillManifestParser.java`** — 解析 SKILL.md frontmatter 中的 constraints
- **`mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java`** — skill 更新事件发布
- **`mateclaw-server/src/main/java/vip/mate/tool/builtin/ProgressLedgerTool.java`** — 三段式渲染
- **`mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/McpClientManager.java`** — MCP 命名透明化
- **`mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/PrefixedNameToolCallback.java`** — 透明命名映射
- **`mateclaw-server/src/main/java/vip/mate/agent/runtime/EnvironmentEventRouter.java`** — 新增:5 个环境事件监听器
- **`mateclaw-server/src/main/java/vip/mate/agent/runtime/EnvironmentNotification.java`** — 新增:环境通知数据模型
- **`mateclaw-server/src/main/java/vip/mate/agent/runtime/RunningConversationRegistry.java`** — 新增:运行中会话注册表 + TTL 清理
- **`mateclaw-server/src/main/java/vip/mate/skill/event/SkillUpdatedEvent.java`** — 新增:skill 更新事件
- **`mateclaw-server/Dockerfile`** — 构建配置微调

**测试代码(11 个文件)**

- **`ConversationWindowManagerExemptAndSpillTest.java`** — 新增 15 个行为测试,证明 Move 4 生效
- **`SkillRuntimeServiceConstraintsAndToolsTest.java`** — 新增 8 个测试覆盖 Constraints 列与 allowedTools 块
- **`ReasoningNodeLoadedSkillsHintTest.java`** — 新增 8 个测试覆盖 loadedThisRun hint 渲染
- **`CompactionSurvivalComparisonTest.java`** — 新增 4 个场景的新旧代码对比测试(100 轮极限压缩)
- **`ContextCompressionLedgerSurvivalTest.java`** — 压缩后 ledger 存活测试
- **`EnvironmentNotificationRenderingTest.java`** — 环境通知渲染测试
- **`ProgressLedgerPrefixGuardTest.java`** — ledger 前缀保护测试
- **`RunningConversationRegistryTest.java`** — 会话注册表测试
- **`SkillManifestConstraintsParsingTest.java`** — constraints 解析测试
- **`ProgressLedgerStaleReminderTest.java`** — 修复回归
- **`ToolDisclosureServiceTest.java`** — 断言从 CORE 改为 EXTENSION

### 测试

**回归测试**

- `mvn test`(mateclaw-server 全量):**199 通过 / 1 跳过 / 0 失败**

**行为测试(证明改动生效,旧代码上失败)**

- `CompactionSurvivalComparisonTest`(4 个场景):在新代码上全部通过
- 用 `git stash` 还原旧代码后,16 个行为测试编译失败或断言失败 → 证明测试确实覆盖了新行为

**新旧代码对比测试(同一份测试源码,两套代码库运行)**

| 场景 | 旧代码 | 新代码 |
|---|---|---|
| A: 50 load_skill + 50 delegate + 50 read_file 单轮压缩 | load_skill 0/50, delegate 0/50 | **load_skill 50/50, delegate 50/50** |
| B: 100 轮极限压缩 + 头部 pinned load_skill | root_constraint_survived=**false**, 113ms | root_constraint_survived=**true**, 60ms |
| C: 20 个不同大小 load_skill 单轮压缩 | 0/20 存活, tokens 8694→754 | **20/20 存活**, tokens 8694→8694 |
| D: 30 轮稳态压缩 + pinned skill | pinned_survived=**false** | pinned_survived=**true** |

### 安全性

- `DisclosureTier.fromToken(null)` 陷阱修复:旧代码 `serverTierById.put(id, CORE)` 导致 `getOrDefault` 默认值永不生效;新代码跳过 null tier,未配置的 MCP server 才走 EXTENSION 默认值
- `PRUNE_EXEMPT_TOOLS` 保护范围从 2 处扩展到 5 处,避免 `load_skill` 约束被压缩销毁后模型在无约束下执行敏感操作

## 逐项验证

### 改动 1:nonHistoryPrefix 分层稳定化

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/graph/node/ReasoningNode.java:701-714, 776-788, 1246-1257`

| 项 | 内容 |
|---|---|
| 改了什么 | skillCatalog 用 `render(Set.of())` 静态渲染进 nonHistoryPrefix;loadedThisRun hint 作为 volatile 后缀注入 |
| 为什么 | 每次推理步都按 loadedSkills 动态渲染会破坏 Anthropic SYSTEM_AND_TOOLS cache 前缀,导致 cache 失效增加 token 成本 |
| 验证步骤 | 1. 打开 ReasoningNode.java:701;2. 确认 `skillCatalogRenderer.render(java.util.Set.of())` 调用;3. 跑 `ReasoningNodeLoadedSkillsHintTest` |
| 预期结果 | 8 个测试通过,loadedThisRun hint 作为后缀注入,不破坏前缀缓存 |

### 改动 2:skillCatalog 增加 Constraints 列

**文件**:`mateclaw-server/src/main/java/vip/mate/skill/runtime/SkillRuntimeService.java:502-561, 634-641`

| 项 | 内容 |
|---|---|
| 改了什么 | catalog 表从 3 列扩为 4 列,新增 Constraints 列(仅 bound skill 显示,长约束截断,pipe 转义);新增 `### Bound skill allowed tools` 块 |
| 为什么 | bound skill 的 constraints 没有任何可见入口,模型加载后仍可能忽略 |
| 验证步骤 | 1. 跑 `SkillRuntimeServiceConstraintsAndToolsTest`;2. 检查 catalog 渲染包含 Constraints 列 |
| 预期结果 | 8 个测试通过,bound skill 显示 constraints,非 bound skill 省略 |
- **边界验证**:长约束截断为单行;pipe 字符被转义不破坏表格

### 改动 3:修复 PRUNE_EXEMPT_TOOLS 在三阶段的绕过

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/context/ConversationWindowManager.java:990-994, 1030-1090, 1160-1210`

| 项 | 内容 |
|---|---|
| 改了什么 | `softTrimToolResults`、`hardClearToolResults`、`prePruneForSummary` 三处新增 `isExemptTool` 检查,跳过 `load_skill`/`delegateToAgent`/`delegateParallel` |
| 为什么 | 旧代码只在 `pruneOldToolResultsForModelInput` 和 `compactAgedToolResponses` 检查 exempt,三阶段不检查,导致 skill 约束在压缩中被裁掉 |
| 验证步骤 | 1. 跑 `ConversationWindowManagerExemptAndSpillTest`;2. 跑 `CompactionSurvivalComparisonTest` |
| 预期结果 | 15 个行为测试通过;100 轮压缩后 load_skill body 存活率 100% |
- **反例对照**:在新代码上跑对比测试,旧代码存活率 0%,新代码 100%

### 改动 4:Phase 2.7 无损 spill evict

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/context/ConversationWindowManager.java:440-468, 1263-1300`

| 项 | 内容 |
|---|---|
| 改了什么 | 在 Phase 2 hardClear 之后、LLM 摘要之前新增 Phase 2.7,把超大工具结果落盘替换为 spill marker |
| 为什么 | 旧代码超出预算直接走 LLM 摘要(有损+耗时+费 token),其实大部分场景落盘就够 |
| 验证步骤 | 1. 检查 `spillEvictToolResults` 方法;2. 跑 `ConversationWindowManagerExemptAndSpillTest.spillEvictReducesTokenCount` |
| 预期结果 | spill 后 token 数低于预算时跳过 LLM 摘要,strategy=lossless_spill_evict |

### 改动 5:MCP 工具默认 EXTENSION

**文件**:`mateclaw-server/src/main/java/vip/mate/tool/disclosure/DefaultToolDisclosureService.java:78-105, 276-336`

| 项 | 内容 |
|---|---|
| 改了什么 | `resolveTierByName` 默认返回 EXTENSION;`buildSnapshot` 跳过 null/blank tier 的 server 不放入 map |
| 为什么 | MCP schema 是 prompt 最重部分,默认 CORE 挤占 builtin 工具注意力;`fromToken(null)` 返回 CORE 导致默认值失效 |
| 验证步骤 | 1. 跑 `ToolDisclosureServiceTest.mcpDefaultsExtensionWhenServerTierUnset`;2. 检查未配置 tier 的 MCP server 工具不在 active 列表 |
| 预期结果 | 未配置 tier 的 MCP 工具进入 extensionCatalog,需 `enable_tool` 激活 |
- **边界验证**:显式 `disclosure_tier=core` 的 server 仍保持 CORE

### 改动 6:构建期权限过滤合并

**文件**:`mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java:258-310`

| 项 | 内容 |
|---|---|
| 改了什么 | 4 次 pass 合并为 2 次:先 `withDeniedToolsFiltered(deniedSet)`,再 `withAllowedToolsOnly(boundTools)` |
| 为什么 | 重复遍历浪费构建时间,且 deny/allow 边界不清晰 |
| 验证步骤 | 1. 检查 AgentGraphBuilder.java:258-310;2. 跑全量回归测试确认工具过滤行为不变 |
| 预期结果 | 工具列表与改动前一致,构建步骤减少 |

## 新增测试验证

**文件**:

- `mateclaw-server/src/test/java/vip/mate/agent/context/ConversationWindowManagerExemptAndSpillTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/context/CompactionSurvivalComparisonTest.java`
- `mateclaw-server/src/test/java/vip/mate/skill/runtime/SkillRuntimeServiceConstraintsAndToolsTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/graph/node/ReasoningNodeLoadedSkillsHintTest.java`
- `mateclaw-server/src/test/java/vip/mate/skill/manifest/SkillManifestConstraintsParsingTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/progress/ProgressLedgerPrefixGuardTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/runtime/RunningConversationRegistryTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/graph/node/EnvironmentNotificationRenderingTest.java`
- `mateclaw-server/src/test/java/vip/mate/agent/context/ContextCompressionLedgerSurvivalTest.java`

| 命令 | 预期 |
|------|------|
| `mvn -Dtest=ConversationWindowManagerExemptAndSpillTest test` | 15 个测试通过 |
| `mvn -Dtest=CompactionSurvivalComparisonTest test` | 4 个场景通过,新代码 load_skill 存活率 100% |
| `mvn -Dtest=SkillRuntimeServiceConstraintsAndToolsTest test` | 8 个测试通过 |
| `mvn -Dtest=ReasoningNodeLoadedSkillsHintTest test` | 8 个测试通过 |
| `mvn test`(全量) | 199 通过 / 1 跳过 / 0 失败 |

**新旧对比测试运行命令**:

```bash
# 新代码
cd /data/mateclaw/mateclaw-server && mvn -Dtest=CompactionSurvivalComparisonTest -Dsurefire.useFile=false test

# 旧代码(需把测试复制到 mateclaw-old)
cd /data/mateclaw/mateclaw-old/mateclaw-server && mvn -Dtest=CompactionSurvivalComparisonTest -Dsurefire.useFile=false test
```

## 回归检查清单

- [ ] `mvn test` 全量通过(199/1skip/0fail)
- [ ] 对比测试在新代码上 load_skill 存活率 100%
- [ ] 对比测试在旧代码上 load_skill 存活率 0%(证明测试有效)
- [ ] MCP 工具默认进入 extensionCatalog,`enable_tool` 可激活
- [ ] 显式 `disclosure_tier=core` 的 MCP server 仍保持 CORE
- [ ] 100 轮压缩后 root_constraint 仍存活
- [ ] Phase 2.7 spill evict 在预算内时跳过 LLM 摘要
- [ ] skillCatalog 静态渲染不依赖 loadedSkills,前缀缓存稳定
2026-07-06 11:50:41 +08:00
matevip
727adcd24b feat(agent): 压缩清理占位符信息化——保留工具名/原始大小/首行要点,便于模型判断是否需要重跑 2026-07-03 18:53:27 +08:00
matevip
36d1f1027d feat(agent): 小上下文降级档——紧凑/底线档收紧注入、压缩触发比自适应、prefix 分块统计与预超限快速失败 2026-07-03 18:53:05 +08:00
matevip
bf0d64e46a feat(tool): 工具 schema 预算阈值门——超出窗口预算时按使用频度自动降级到扩展目录,enable_tool 可找回 2026-07-03 18:52:45 +08:00
matevip
56737e197f feat(agent): prefix 注入块统一 token 预算——记忆/Wiki 注入随模型有效窗口缩放,身份 prompt 超大告警 2026-07-03 18:51:32 +08:00
倪程伟
a1221ac02d feat(mcp): type on-behalf-of identity by channel/trust (#459)
The identity forwarded to opt-in MCP servers was a one-dimensional string
(ChatOrigin.requesterId): a MateClaw username for web logins, but a webchat
visitorId for visitors and an IM sender id for IM — indistinguishable to the
REST backend. The signed-token mode (d204b702) made this worse: an RS256
signature over an unauthenticated visitorId reads as "MateClaw authenticated
this user" to any backend that trusts the signature.

Introduce an identity-typing dimension at McpIdentityForwardService:

- classify() branches on ChatOrigin: authenticated (web login, sub=immutable
  userId), anonymous (webchat visitor, trust=anonymous), external (IM sender,
  trust=external), or none (cron/system → nothing injected, fail-closed).
- mint() adds `trust` and `channel_type` claims; plaintext value is prefixed
  `trust:subject` so backends can tell the kinds apart without a JWT.

The immutable userId reaches resolve() without coupling it to the user store:
JwtAuthFilter stamps user.id into auth.setDetails() (both JWT and PAT paths),
and ChatController.memoryOrigin carries it on a new ChatOrigin.requesterUserId
field (only-add, per the record's evolution rule).

Resolves the webchat semantic mismatch raised in #459 and the "sub should be
an immutable user id" follow-up. 82 tests green (4 identity classes covered
with claim assertions + full ChatOrigin/MCP regression).

(cherry picked from commit b5d2cfbf98b39848d7139c743a0b81fea71e8ffe)
2026-07-01 19:02:49 +08:00
倪程伟
bd1ceace74 feat(agent): render runtime model identity line in RuntimeContextInjector
Add 5-arg buildContextMessage overload that emits [system-context] Model:
for every origin (web/cron/IM). Legacy 3/4-arg overloads delegate to the
new one with null model args, keeping their output byte-identical.

Also fix pre-existing FeishuMentionTest compile error caused by removed
mentionMatchesAnyAlias/collectMentionIdentifiers methods.
2026-06-08 20:53:39 +08:00
matevip
7894a50067 fix(tool): emit absolute download URLs for streaming-generated files (#164)
Streaming chat ran render tools on an async thread with no bound request,
so download links lost their host and arrived without a domain. Resolve the
host on the request thread and carry it through ChatOrigin/ToolContext;
falls back to a configurable public-base-url, then a relative path.
2026-06-07 15:03:48 +08:00
matevip
237f649e7e fix(tool-guard): trust shared skill root outside the workspace boundary 2026-05-29 09:59:19 +08:00
matevip
7f5652b2f0 fix(agent): replace per-loop head/tail trim with anchored token-budget budgeter 2026-05-26 07:34:25 +08:00
matevip
e953f8be5a feat(agent): age-based compaction of older tool-response bodies 2026-05-24 23:00:11 +08:00
matevip
03e68d3c74 fix(agent): structure-aware truncation to stop mid-JSON cuts inducing hallucination (#187) 2026-05-21 15:13:07 +08:00
matevip
3554da8dbc sync: inject sender context into agent prompt + Feishu DONE ack hook 2026-05-20 11:51:58 +08:00
matevip
db16ff02a5 chore: drop external project name references from code comments 2026-05-20 08:09:43 +08:00
matevip
a88edbdd07 refactor(llm): decouple model construction from the agent graph layer (#147) 2026-05-18 07:47:49 +08:00
matevip
e647f1d6cf fix(cron): isolate scheduled-job runs from shared conversation (#142) 2026-05-17 07:58:10 +08:00
matevip
419ee57cc8 feat(agent): structured compaction on prompt-too-long preserves prefix 2026-05-16 14:51:14 +08:00
matevip
af56763156 fix(context): keep the latest summary even when its row sits outside the recent window; include summaryId in compact_status 2026-05-13 08:50:42 +08:00
matevip
51b5eceb7d feat(context): preserve spill markers across compaction phases, persist enriched boundary, broadcast compact_status SSE (#110) 2026-05-13 08:50:28 +08:00
matevip
7015d1513c feat(context): anchor the first user message after compaction so long tasks remember the original goal 2026-05-13 08:50:15 +08:00
matevip
37894a6978 fix(context): never compact across a split tool_call ↔ tool_response pair 2026-05-13 08:50:08 +08:00
matevip
ae3d27922d fix(context): preserve raw older tool results instead of rewriting them to a lossy summary 2026-05-13 08:50:02 +08:00
matevip
2e0ff8f90f fix(agent): unblock multi-role parallel delegation + propagate force-stop 2026-05-04 20:54:38 +08:00
matevip
42d406ffc8 fix(agent): drop brittle output policing, add evidence-grounded long-task safeguards 2026-05-04 11:55:44 +08:00
matevip
eaacb3a78f fix(agent): include tools schema in context-window budget 2026-05-02 15:40:03 +08:00
matevip
4011050ceb feat(cron): channel delivery via ChatOrigin + Spring AI ToolContext
Replaces the prior ThreadLocal context plumbing with explicit Spring AI
ToolContext threading carried by an immutable ChatOrigin value object,
so a cron created from inside WeChat (or any IM channel) delivers its
results back to the originating channel.

Architecture
- ChatOrigin / ChannelTarget value objects + per-entry-point factories
  (ChannelChatOriginFactory in vip.mate.channel, CronChatOriginFactory
  in vip.mate.cron — symmetric, no cyclic deps).
- LocaleAwareToolCallback now forwards call(String, ToolContext) and
  getToolMetadata so the decorator chain cannot silently drop the origin.
- AgentService 6-method overhaul + ChatOriginHolder bridge into
  StateGraph buildInitialState which writes CHAT_ORIGIN; ActionNode +
  StepExecutionNode forward it to ToolExecutionExecutor.
- ToolExecutionExecutor builds ToolContext per call; 8/8 tools migrated
  (CronJobTool, WorkspacePathGuard, Video/Image/Browser/ReadFile/Music,
  DelegateAgentTool with parent-origin inheritance).
- CronJobRunner + CronJobLifecycleService 3-segment REQUIRES_NEW model
  (T1 startRun / no-tx runAgent / T2 finishRunAndPublish); ArchUnit
  pins CronJobRunner as @Transactional-free.
- CronResultDelivery Strategy + AbstractCronResultDelivery Template
  with SQL CAS idempotency on mate_cron_job_run.delivery_status —
  replaces the prior process-local Caffeine TTL, cluster-safe.
- CronJobCompletedEvent + @Async @TransactionalEventListener(AFTER_COMMIT);
  cronDeliveryExecutor (core=2, max=4, queue=1000, AbortPolicy + audit).
- CronRunStaleCleanup @Scheduled(5min) sweeps PENDING-15min and
  status='running'-30min in one query each.
- CronJobRunner.wrapWithDeliveryGuard prepends a system note for
  channel-bound crons to suppress hallucinated 'install CLI to send
  WeChat' suggestions.
- ApprovalWorkflowService Memento: persist ChatOrigin snapshot on
  create, restore on replay so cross-restart approvals keep channel
  binding; ChannelMessageRouter + ChatController web-replay both prefer
  the Memento and fall back to fresh-build.
- ChannelManager.sendToChannel 4-arg DeliveryOptions overload;
  ChannelAdapter#proactiveSend default 4-arg pass-through; Slack
  overrides for thread_ts and Telegram overrides for message_thread_id.
- CronJobs UI: read-only 'last delivery' badge driven by
  CronJobMapper.selectListWithDeliveryStatus subquery.

Schema migrations V57/V58/V59 (V56 was already taken by an unrelated
provider migration — Flyway processes versions in order regardless of
gaps):
- V57: mate_cron_job_run delivery_status / target / error + composite
       index (delivery_status, started_at) covering the cleanup sweep.
- V58: mate_cron_job channel_id (indexed) + delivery_config TEXT (JSON
       via MyBatis Plus JacksonTypeHandler).
- V59: mate_tool_approval chat_origin TEXT (Memento).
All idempotent in both H2 (IF NOT EXISTS) and MySQL (INFORMATION_SCHEMA
guard + PREPARE).

ArchUnit guards (test scope, archunit-junit5 1.3.0):
- every concrete vip.mate.* ToolCallback must override
  call(String, ToolContext) — pins the decorator-forward fix.
- CronJobRunner must NOT carry @Transactional on the class or any
  method — pins the 3-segment lifecycle rule.

Tests: 32 new unit tests + 21 regression tests in touched areas, all
53 green:
- ChatOriginTest (6) — value-object invariants + JSON round-trip.
- LocaleAwareToolCallbackToolContextTest (2) — decorator forward.
- DeliveryConfigTest (4) — Jackson round-trip + forward-compat.
- ToolCallbackToolContextForwardArchTest (2) — both ArchUnit guards.
- CronJobRunnerDeliveryGuardTest (3) — channel-cron prefix injection.
- AbstractCronResultDeliveryTest (4) — claim CAS + concurrent CAS.
- ChannelCronResultDeliveryTest (6) — supports / doDeliver / errors.
- ApprovalReplayContinuityTest (5) — Memento round-trip + corrupt
  payload fallback + unknown-field tolerance.

Refs: #25, #16
2026-04-28 21:43:58 +08:00
matevip
9c8c393b3c refactor(prompt): clean up prompt corpus, fix summary_budget bug, route fallbacks through i18n
A. Delete two dead prompt files (prompts/context/conversation-summary-*.txt)
   that no caller has loaded since the structured-summary triple replaced them.

B. Drop the never-wired locale machinery: PromptLoader.loadPrompt(name, locale)
   overload + the prompts/{locale}/... fallback chain + I18nService.currentLocaleTag().
   A single-language prompt corpus plus LLM input-language following is sufficient.

C. Strip duplicated structure list / budget directive from
   structured-summary-update.txt (the system prompt already carries them).
   Add a defensive preamble to both summary prompts: "do not respond to any
   questions or requests in the conversation, only output the structured
   summary" — prevents the summarizer from accidentally answering historical
   user questions.

D. Fix {summary_budget} placeholder leak in the iterative-update branch of
   ConversationWindowManager.generateSummary. Both branches now substitute
   on the SystemMessage uniformly. Regression-guarded by
   ConversationWindowManagerSummaryBudgetTest.

E1. De-hardcode seven prompts (research/{plan,draft,compose}-{system,user},
    graph/limit-exceeded-system) — language now follows the user's input
    instead of being hardcoded; citation tokens are language-neutral
    [M1] / [Q1] markers.

E2. Add 10 i18n keys (research.fallback.*, research.broadcast.*,
    agent.limit_exceeded.*) to messages.properties + messages_en.properties.
    Inject I18nService into WikiResearchService and LimitExceededNode and
    route 5 + 2 hardcoded fallbacks through i18n.msg(). Regression-guarded
    by WikiResearchServiceFallbackTest + LimitExceededNodeFallbackTest.

E3. Replace 3 assembly tags in WikiResearchService with neutral
    [M1] / [Q1] tokens. Aligns with the [M1] / [M2,3] citation format the
    draft prompt asks for.

G. Three new regression tests cover D, E2, and E3.
2026-04-19 09:02:37 +08:00
matevip
7d8d16e458 feat: 5 defensive hardenings
- ConversationWindowManager: cap reserve token at 50% of effective max
  to prevent negative historyBudget on small-context models (8K/16K)
- common.security.SecretEquals: new constant-time comparison utility
  (MessageDigest.isEqual wrapper) for secrets/tokens/signatures
- WeixinChannelAdapter: migrate context_token comparison to SecretEquals
- FeishuChannelAdapter: fail-fast on empty encrypt_key when connection_mode=webhook
- TelegramChannelAdapter: sanitize attachment captions — strip control bytes
  (\p{Cc} except \t\r\n) + format chars (\p{Cf}) + 4096 char cap
- AgentGraphBuilder: fallback Anthropic max_tokens to 4096 on null/0/negative

Tests: SecretEqualsTest (5) + TelegramCaptionSanitizeTest (5) — all green.
2026-04-15 22:14:55 +08:00
matevip
ef8120413c feat(wiki): real-time SSE progress + parallel page generation + partial-resume; fix concurrent slug collisions; fix skill overwrite + hub retry 2026-04-15 10:34:11 +08:00
matevip
aa09345403 feat(i18n): add backend internationalization — tool descriptions, error messages, guard rules, runtime context 2026-04-11 17:08:43 +08:00
matevip
cc28f665f1 feat(db): introduce Flyway migration framework and unify H2/MySQL schemas
- Add Flyway baseline migration (V1) for both H2 and MySQL
- Remove 5 legacy SchemaMigration ApplicationRunner classes
- Add WorkspacePathGuard for file tool sandbox enforcement
- Inject workspace basePath context into agent graph state and tool executor
- Add workspace basePath config UI in frontend
- Fix workspace slug preservation on update
2026-04-11 16:34:09 +08:00
matevip
dafdcb4182 feat(chat): segmented message display, progressive loading, and real-time segment persistence 2026-04-10 07:36:24 +08:00
matevip
ec7ea038e7 feat(agent): context compression upgrade, 429 retry, iteration limit & repetition fixes 2026-04-10 00:52:26 +08:00
matevip
d3f2a310e8 feat(agent): context pruning, thinking recovery, channel health monitor 2026-04-07 07:39:14 +08:00