Commit Graph

33 Commits

Author SHA1 Message Date
mateaix
a204e8eec8 fix(self-evolution): harden lifecycle and plan loops 2026-08-09 18:13:24 +08:00
matevip
c72c8b48e4 fix(skill,agent): curation observation anchor, per-skill adoption, and empty plan-summary fallback 2026-08-07 05:12:33 -04:00
matevip
e22173a476 feat(skill): close the self-evolution loop — auto-bind, routine mining, curation provenance and restore points 2026-08-06 01:51:22 -04:00
matevip
f715e8ffc0 feat(skill): add workspace-scoped runtime skill resolution overloads 2026-07-24 11:06:37 +08:00
matevip
c18ff31ae8 feat(skill): scope skill workspace filesystem paths by workspace to isolate same-named skills 2026-07-24 10:26:57 +08:00
matevip
4ac72a90a0 feat(skill): bundle file management — view and edit scripts, references, and templates from the console
The skill detail drawer could only show and edit SKILL.md; the bundle
files under scripts/ and references/ had no console surface, and
templates/ was readable by agents but absent from the canonical
store's bucket set.

- admin endpoints on /api/v1/skills/{id}/files: list (self-heals an
  empty canonical store from on-disk files), read, upsert, delete.
  Writes update the canonical row, materialize the workspace cache,
  and re-resolve the skill so agents pick changes up immediately.
  Path envelope enforces the three buckets and blocks traversal;
  builtin skill files stay read-only; virtual skills own no files.
- templates/ becomes a first-class DB-persisted bucket shared across
  the syncer, the workspace write/delete envelope, and prune guards.
- the agent-facing write_file action now mirrors into the canonical
  store and re-resolves instead of writing only the local filesystem.
- SkillMarket detail drawer gains a Files tab: grouped list, viewer,
  inline editor, create and delete, refetched on every entry.
2026-07-22 15:39:59 +08:00
matevip
aa0e249ce4 feat(skill): endpoint reachability checks in the skill requirement gate
Skills backed by a network service could show ready while the service
was unreachable from the current deployment (intranet-only address,
wrong network segment) — the failure only surfaced mid-task.

- endpoint requirement type: TCP-connect probe (1.5s timeout) of the
  declared service address; accepts http(s)://host[:port][/path],
  host:port, and bare-host forms
- URL-shaped check targets infer the endpoint type without an explicit
  declaration; unparseable targets report UNKNOWN instead of missing
- probe results cached 60s per host:port so refresh passes stay cheap
  and a VPN connect is picked up within a minute
- unreachable endpoints surface as setup-needed on the skill card,
  pre-flight requirement rows, and the agent-facing catalog
2026-07-22 14:38:37 +08:00
matevip
5e188cd77b feat(skill): single-source SKILL.md — DB canonical, workspace file as tracked cache
The runtime resolved SKILL.md from the workspace directory while the
admin console read the skill_content column, so out-of-band file edits
(agent shell tools in a chat session) changed runtime behavior but never
showed up in the console, and a failed workspace export left agents
executing stale content the console claimed was current.

- SkillContentReconciler: three-way sync between the canonical DB column
  and the convention-workspace file, anchored on a sidecar hash marker.
  File-side edits ingest into the DB, DB-side edits materialize to the
  file, two-sided conflicts resolve DB-wins with a backup.
- Skill detail GET performs a read-time reconcile and triggers a
  single-skill rescan when the file side changed, so a console query is
  always current without waiting for the runtime cache TTL.
- SkillMarket detail drawer refetches the row and runtime status on open
  instead of rendering the page-load list snapshot.
2026-07-22 14:20:27 +08:00
matevip
0ad1371abd refactor(skill): harden bundled-skill DB sync and extend self-heal 2026-07-21 18:12:29 +08:00
rootdeng
02772d58b8
fix(skill): sync builtin skill scripts to DB and self-heal missing workspace scripts
Bundled skill scripts/ and references/ are now persisted to mate_skill_file during startup sync; a workspace missing its scripts directory is force-restored from the classpath bundle even when the SKILL.md version is unchanged; and builtin skills with neither DB rows nor on-disk files backfill from the classpath. Fixes installs performed from builds whose jar shipped without bundle scripts.
2026-07-21 17:56:56 +08:00
matevip
7a1d237de4 fix(skill): decode zip entry names and content independently to fix mojibake on mixed-encoding archives
Windows-authored zips often store entry names in the local codepage (GBK)
without setting the ZIP UTF-8 flag, while file content stays UTF-8. The
previous fallback decoded the whole archive with one charset, so a single
GBK-named entry forced already-correct UTF-8 content to be re-decoded as
GBK, corrupting valid Chinese text into mojibake. Names and content now
each try UTF-8 first and fall back to GBK independently, per entry.
2026-07-16 14:13:27 +08:00
MIST
11fa2b0a03
feat(skill): configurable pip index for skill Python scripts (mirror & private LAN sources)
Let skill Python scripts install packages from a configurable pip index instead of the default PyPI. docker-compose passes PIP_INDEX_URL / PIP_TRUSTED_HOST into the container; for the desktop app (host JVM, no Docker env) SkillScriptExecutionService falls back to mateclaw.pip.index-url / trusted-host Spring config and injects them into the subprocess, auto-deriving the trusted host for plain-HTTP LAN mirrors. The runtime image gains pip and a build toolchain (with the PEP 668 marker removed so on-the-fly installs work), and the script timeout ceiling is raised to accommodate large installs.
2026-07-10 14:48:56 +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
b6cca3edd0 fix(skill): make skill ZIP size caps configurable (#467) 2026-07-03 14:58:48 +08:00
matevip
f08abad076 feat(skill): self-evolving skills — out-of-band reflection, curator consolidation, agent-authored skill files 2026-06-23 13:51:04 +08:00
matevip
94cf812207 feat(tool): add execute_code for running agent-authored code (#257)
Add an execute_code built-in tool that runs python/bash/node code the agent
writes on the fly, so a documentation-only skill (a SKILL.md with no bundled
scripts) can be acted on. Scoped runs inject the skill's secrets and run in
the skill directory; otherwise a private scratch directory is used. Host
secret env vars are scrubbed from the subprocess. execute_code is an
agent-wide capability, registered in the tool catalog (V143), and screened
by the tool guard with a dedicated set of destructive-pattern rules.

Tests cover python/bash/node execution, scratch-dir fallback, env scrubbing,
argument decoding, and guard gating.
2026-06-09 08:05:50 +08:00
matevip
be7bbd9644 fix(skill): persist skill workspace on the existing data volume; skip binary entries in ZIP packages (#273) 2026-06-07 23:09:42 +08:00
matevip
86cb449bd5 fix(skill): keep skill workspace paths stable while fixing non-ASCII collision
The #254 fix changed resolveConventionPath to {name}-{hashCode} and folded
hyphens to underscores, which re-pathed every existing skill (browser-cdp ->
browser_cdp-<hash>) with no migration, orphaning already-created workspaces and
breaking SkillWorkspaceManagerApplyBundleTest. Drop the hash suffix and keep
hyphens: the bare Unicode-preserving sanitized name already prevents the
non-ASCII collision (distinct CJK names map to distinct dirs) and leaves ASCII
kebab-case paths identical to the legacy scheme. Add path regression tests.
2026-06-07 20:06:19 +08:00
matevip
5cd6e841a4 feat(skill): broaden script security scan + GBK zip-import fallback 2026-05-23 22:55:10 +08:00
matevip
123d912f84 fix(skill): allow template file access 2026-05-23 09:08:01 +08:00
matevip
cef1730e6e feat(tool,skill,ui): progressive tool/skill disclosure (load_skill + enable_tool + tier UI) 2026-05-23 09:07:45 +08:00
matevip
3d643f909d feat(skill): typed wrapper tools for declared script entrypoints 2026-05-19 09:56:39 +08:00
matevip
520429ac6f test(skill): end-to-end coverage for the lifecycle curator 2026-05-19 09:56:27 +08:00
matevip
4cd21056a0 feat(skill): automatic lifecycle archival for idle skills 2026-05-19 09:55:59 +08:00
matevip
c320aab1ba fix(skill): synthesize SKILL.md body for ACP-derived virtual skills (#136) 2026-05-16 14:50:51 +08:00
matevip
130dfd5278 fix(skill): synthesize SKILL.md for MCP-derived virtual skills (#136) 2026-05-16 14:50:46 +08:00
matevip
3b9b4d79d5 feat(skill): scope skill catalog and runtime by workspace (#135) 2026-05-15 20:01:25 +08:00
matevip
65dd4c1f49 fix(skill): cascade-delete agent-skill bindings on skill removal (#127) 2026-05-15 10:17:41 +08:00
matevip
1bba59fc7e fix(skill): encode bridge virtual ids in the top two bits 2026-05-14 18:18:21 +08:00
matevip
139a478bcd fix(skill): give CJK-only MCP/ACP names a stable id-based slug 2026-05-14 18:18:15 +08:00
matevip
0ffc224623 chore(test): backfill mateclaw-server/src/test/ that earlier PRIVATE_ITEMS pattern accidentally excluded
The PRIVATE_ITEMS list contained the bare 'test' entry, which rsync
interprets as 'any directory named test at any depth' — so it caught
the root-level /test/ scratch directory (intended) AND every src/test/
under each module (not intended).

Pattern is already anchored to /test (root-only). This commit rsyncs
the accumulated src/test/ tree forward so opensource has the unit tests
that have been written / updated against existing src/main/ code since
the pattern regression. Going forward each per-commit sync will carry
src/test/ files along with the main change.
2026-05-12 17:38:08 +08:00
matevip
7f88a6ddd4 release: v1.1.0 2026-04-17 19:41:03 +08:00
matevip
579d60125b Initial commit: MateClaw — Java + Vue 3 AI Assistant System
Full-stack AI assistant built on Spring AI Alibaba.
Features: ReAct Agent, Plan-and-Execute, MCP Protocol, Multi-Model, Multi-Channel.

Apache-2.0 License
2026-04-04 19:03:49 +08:00