mirror of https://gitee.com/dromara/liteFlow
docs: 添加 agentscope v2 迁移实施计划
按 spec 的 9 阶段拆为可执行任务(Phase 0 API spike → Phase 9 全量回归), 含文件结构图、逐任务 TDD 步骤、签名一致性自检。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6143229255
commit
8954412e93
|
|
@ -0,0 +1,937 @@
|
|||
# liteflow-react-agent AgentScope 2.0 迁移 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 把 `liteflow-react-agent` 模块底层从 agentscope 1.0.12 切到 2.0.x 的 `HarnessAgent`,会话/持久化/workspace/skill/工具全部改用 v2 原生,保住业务侧 `ReActAgentComponent` 核心用法与现有 `liteflow.agent.*` 配置键,并接通 compaction/plan-mode/subagent/sandbox/self-learning/interrupt 等增强能力。
|
||||
|
||||
**Architecture:** 每个 `ReActAgentComponent` 子类构建一个无状态 `HarnessAgent` 单例;`(conversationId, agentKey)` 映射为 `RuntimeContext(userId=conversationId, sessionId=agentKey)`;记忆/持久化走 `AgentStateStore`(NONE/JVM/LOCAL_FILE/REDIS/MYSQL/+OSS),workspace/文件/shell 走 HarnessAgent 文件系统 + 内置工具 + `PermissionEngine`,skill 走 `skillRepository`/workspace skills,Hook→Middleware,`stream()`→`streamEvents()`。删掉自建 `AgentSessionManager`/`AgentSessionFactory`/5 factory/`WorkspaceFileTools`/`ManagedShellCommandTool`/`SkillBoxFactory`。
|
||||
|
||||
**Tech Stack:** Java 21(运行时),Maven 多模块,agentscope 2.0.x(core + extensions-redis/mysql/oss/skill-git),Spring Boot,JUnit 5 + Mockito 4.11,Reactor。
|
||||
|
||||
**参考 spec:** `docs/superpowers/specs/2026-06-19-agentscope-v2-migration-design.md`
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 目标 agentscope 版本:`2.0.x`(在根 `pom.xml` 设 `<agentscope.version>2.0.x</agentscope.version>`,具体 patch 取已发布最新)。
|
||||
- 运行时 Java:21+(react-agent 已满足,不变)。
|
||||
- 测试**只放** `liteflow-testcase-el/liteflow-testcase-el-react-agent` 模块;react-agent-core **不放** `src/test`。**严禁**把任何 `skipTests=true` 改为 false。
|
||||
- 业务侧源码兼容:`model()/systemPrompt()/userPrompt()/tools()/skills()/enableSkills()/resolveConversationId()/agentKey()/maxIterations()/handleReply(Msg)/buildModel()/ctx()/getSlot()` 签名保持不变。
|
||||
- 老配置键保留并映射;被 v2 取代的键标 `@Deprecated` + 启动告警,不在本迁移删除。
|
||||
- 不重写 `liteflow-core` 编排引擎;agent 仍是 `NodeComponent`,`process()` 仍为 `final`。
|
||||
- 分支:`agentscope_v2`(已存在)。每个 Task 末尾 commit;不 push,除非用户要求。
|
||||
- 凡依赖 spec §16 中 R1/R4/R5/R6 的 API,**必须先完成 Task 0 的 API spike** 拿到确认签名后再写实现代码,不得臆造 API。
|
||||
|
||||
## File Structure
|
||||
|
||||
### 删除(react-agent-core)
|
||||
- `agent/session/AgentSessionManager.java`
|
||||
- `agent/session/AgentSession.java`
|
||||
- `agent/session/factory/AgentSessionFactory.java`
|
||||
- `agent/session/factory/AgentSessionFactoryRegistry.java`
|
||||
- `agent/session/factory/{InMemory,LocalFile,Redis,Mysql,None}AgentSessionFactory.java`
|
||||
- `agent/tool/WorkspaceFileTools.java`
|
||||
- `agent/tool/ManagedShellCommandTool.java`
|
||||
- `agent/skill/SkillBoxFactory.java`
|
||||
- `agent/skill/SkillToolResolver.java`
|
||||
- `agent/skill/SkillLoadResult.java`
|
||||
- `agent/skill/SkillTrackingHook.java`
|
||||
- `agent/hook/ReActLoggingHook.java`
|
||||
- `agent/hook/ChatUsageTrackingHook.java`
|
||||
|
||||
### 新增(react-agent-core,包 `com.yomahub.liteflow.agent`)
|
||||
- `state/AgentStateStoreResolver.java` —— `MemoryStorageMode`→`AgentStateStore`。
|
||||
- `state/HarnessAgentFactory.java` —— 按 `ReActAgentComponent` 子类构建并缓存无状态 `HarnessAgent` 单例。
|
||||
- `state/NoOpAgentStateStore.java` —— R1 兜底(若 v2 无原生 NoOp)。
|
||||
- `permission/PermissionConfigMapper.java` —— `ShellConfig`→`PermissionContextState`。
|
||||
- `middleware/LoggingMiddleware.java` —— 替代 `ReActLoggingHook`。
|
||||
- `middleware/ChatUsageMiddleware.java` —— 替代 `ChatUsageTrackingHook`,支撑 `ctx.getChatUsage()`。
|
||||
- `middleware/SkillTrackingMiddleware.java` —— 替代 `SkillTrackingHook`。
|
||||
- `middleware/HookMiddlewareAdapter.java` —— 把业务侧遗留 `Hook` 桥接为 middleware。
|
||||
- `event/AgentEventBridge.java` —— `AgentEvent`→`FlowEvent`。
|
||||
- `skill/SkillRepositoryResolver.java` —— `skills.*` 配置→`skillRepository`/`SkillFilter`。
|
||||
- `config/AgentStateStoreProperties.java`、`config/FilesystemProperties.java`、`config/CompactionProperties.java`、`config/PlanModeProperties.java`、`config/SubagentProperties.java`、`config/SandboxProperties.java` —— 新增能力配置(或并入既有 property 类,见 Task)。
|
||||
|
||||
### 改造
|
||||
- `agent/component/ReActAgentComponent.java` —— `process()`/`buildAgent()` 重写,新增 `middlewares()/permissionContext()/runtimeContext()`,`hooks()` 桥接 + `@Deprecated`。
|
||||
- `agent/component/ReActAgentContext.java` —— 增加 `getRuntimeContext()`;保留 `getChatUsage()`。
|
||||
- `property/agent/*`(liteflow-core)—— 语义重映射 + `@Deprecated` + 新增子配置。
|
||||
- `liteflow-react-agent-*/src/.../<vendor>/*Spec.java` —— 按 spike 结果微调 `XxxChatModel.builder()` 调用(R4)。
|
||||
- `docs/liteflow-react-agent-guide.md` —— 迁移说明。
|
||||
|
||||
### 测试(`liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/`)
|
||||
- `v2/AgentStateStoreResolverTest.java`、`v2/PermissionConfigMapperTest.java`、`v2/AgentEventBridgeTest.java`(纯单元,不用 Spring)
|
||||
- `v2/`(集成,Spring Boot):`StateIsolationTest`、`MemoryModeMappingTest`、`StreamingBridgeTest`、`CompactionTest`、`PlanModeTest`、`SubagentTest`、`InterruptTest`、`BackCompatSmokeTest`。
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — API Spike(解析 R1/R4/R5/R6/R7)
|
||||
|
||||
### Task 0: v2 API 探针与发现
|
||||
|
||||
**目的:** 在写任何实现前,用最小可编译探针对真实 v2 jar 验证不确定 API,产出一份发现笔记,后续 Task 消费。
|
||||
|
||||
**Files:**
|
||||
- Create(临时): `liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2spike/V2ApiProbe.java`
|
||||
- Create(交付物): `docs/superpowers/specs/v2-api-findings.md`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `docs/superpowers/specs/v2-api-findings.md`,记录以下 5 项的**确认签名**(类全名 + 方法签名 + 行为),后续 Task 须引用此处结论。
|
||||
|
||||
- [ ] **Step 1: 依赖先行(Task 1.1 提前到这里做最小版本)**
|
||||
|
||||
编辑 `/Users/bryan31/openSource/LiteFlow-Jdk17/pom.xml`,把 `<agentscope.version>1.0.12</agentscope.version>` 改为已发布最新 `2.0.x`(查 Maven Central 取最新 patch)。仅改版本号,先不删旧 session 包(编译会大面积失败,本 Task 只跑一个隔离探针)。
|
||||
|
||||
- [ ] **Step 2: 写探针测试,逐项验证**
|
||||
|
||||
```java
|
||||
package com.yomahub.liteflow.test.agent.v2spike;
|
||||
|
||||
import io.agentscope.core.HarnessAgent; // 验证类全名
|
||||
import io.agentscope.core.agent.RuntimeContext;
|
||||
import io.agentscope.core.state.AgentStateStore;
|
||||
import io.agentscope.core.state.InMemoryAgentStateStore;
|
||||
import io.agentscope.core.state.JsonFileAgentStateStore;
|
||||
import io.agentscope.core.middleware.MiddlewareBase; // 验证包路径
|
||||
import io.agentscope.core.event.AgentEvent; // 验证事件包
|
||||
import io.agentscope.core.message.UserMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class V2ApiProbe {
|
||||
@Test void r1_noneStore_onHarness() throws Exception {
|
||||
// R1: HarnessAgent 默认 stateStore 是什么?如何得到 NONE(不持久化)?
|
||||
// 探查 HarnessAgent.Builder 是否有 .disableStatePersistence() / 默认值;
|
||||
// 是否存在 NoOpAgentStateStore / NullAgentStateStore;
|
||||
// 不设 stateStore 时 build() 是否报错。
|
||||
}
|
||||
@Test void r4_vendorChatModelBuilders() throws Exception {
|
||||
// R4: OpenAIChatModel/DashScopeChatModel/AnthropicChatModel/GeminiChatModel
|
||||
// 的 builder 在 v2 的方法名(apiKey/modelName/baseUrl/stream/generateOptions/formatter)是否与 1.0 一致。
|
||||
}
|
||||
@Test void r5_chatUsage_retrieval() throws Exception {
|
||||
// R5: v2 message 上 ChatUsage 的 getter;MiddlewareBase 在模型调用后的回调签名
|
||||
// (onModelCall 入参类型,如何拿到本次 message 与 usage)。
|
||||
}
|
||||
@Test void r6_permissionRule_shape() throws Exception {
|
||||
// R6: PermissionContextState / PermissionRule / PermissionMode 的构造方式;
|
||||
// 能否表达"命令名 ∈ 白名单才允许 execute"这种细粒度规则。
|
||||
}
|
||||
@Test void r7_java_baseline() {
|
||||
// R7: 跑 mvn -pl liteflow-react-agent/liteflow-react-agent-core compile,确认 JDK 21 可编译 v2。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注:每个方法体用反射或直接调用探查目标 API(如 `HarnessAgent.builder().name("x").sysPrompt("y").model(stubModel).build()` 看默认 stateStore),用 `assertions` 或打印记录确认结果。
|
||||
|
||||
- [ ] **Step 3: 跑探针**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=V2ApiProbe -DfailIfNoTests=false test 2>&1 | tail -60`
|
||||
(编译会因旧 session 包失败——见 Step 4 临时处置。)
|
||||
|
||||
- [ ] **Step 4: 临时让 core 编译通过以运行探针**
|
||||
|
||||
为让探针能跑,临时把 `liteflow-react-agent-core` 里所有 import 了已删 `io.agentscope.core.session.*` 的类**整文件注释/临时移除**(仅本 Task 临时手段,Task 2 会正式删除)。最省事做法:把 `agent/session/**` 与 `agent/skill/SkillBoxFactory.java`、`agent/tool/**`、`agent/hook/**` 临时移到 core 外的 `_tmp_stash/`,并临时简化 `ReActAgentComponent`(注释掉 buildAgent 里用到它们的段落),使 core 编译通过。
|
||||
|
||||
- [ ] **Step 5: 把确认结果写入发现笔记**
|
||||
|
||||
`docs/superpowers/specs/v2-api-findings.md` 记录 R1/R4/R5/R6/R7 的确认签名(类全名 + 方法签名 + 默认值 + 结论)。**后续每个 Task 凡用到这些 API,须与笔记核对;若笔记与计划代码冲突,以笔记为准并回填修正计划。**
|
||||
|
||||
- [ ] **Step 6: 还原临时改动,仅保留 findings + 版本号 + 探针**
|
||||
|
||||
把 `_tmp_stash/` 移回(恢复 1.0 session 包,使仓库回到"仅版本号改了、探针+findings 新增"的可编译?不——版本号已升 v2,session 包会编译失败)。处理:本 Task 结束时**保留版本号升级与探针/findings,接受 core 暂不编译**,下一个 Task(Task 1/2)正是修复编译。commit 信息注明"WIP: v2 探针,core 暂不可编译"。
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/specs/v2-api-findings.md \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2spike/V2ApiProbe.java \
|
||||
pom.xml
|
||||
git commit -m "wip(agent): v2 API 探针与发现笔记 (R1/R4/R5/R6/R7)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — 依赖切换
|
||||
|
||||
### Task 1: 升级 agentscope 版本与扩展依赖
|
||||
|
||||
**Files:**
|
||||
- Modify: `pom.xml`(根,`agentscope.version` + `dependencyManagement`)
|
||||
- Modify: `liteflow-react-agent/liteflow-react-agent-core/pom.xml`(按需加 extensions)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: 构建期可用 `io.agentscope.core.*`(2.0.x)与按需扩展。
|
||||
|
||||
- [ ] **Step 1: 根 pom 设定版本**
|
||||
|
||||
`pom.xml` 中 `<agentscope.version>` 改为 Task 0 确认的最新 `2.0.x`。
|
||||
|
||||
- [ ] **Step 2: core pom 按需声明扩展(provided/optional 优先,避免强耦合)**
|
||||
|
||||
`liteflow-react-agent-core/pom.xml` 新增(全部 `<optional>true</optional>`,反射/类路径探测式加载,与现有 Redis/MySQL factory 风格一致):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-redis</artifactId>
|
||||
<version>${agentscope.version}</version><optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-mysql</artifactId>
|
||||
<version>${agentscope.version}</version><optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-oss</artifactId>
|
||||
<version>${agentscope.version}</version><optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-skill-git-repository</artifactId>
|
||||
<version>${agentscope.version}</version><optional>true</optional>
|
||||
</dependency>
|
||||
```
|
||||
(具体 artifactId 以 Task 0 发现笔记为准;OSS/扩展模块名若有出入,按笔记修正。)
|
||||
|
||||
- [ ] **Step 3: 校验依赖可解析**
|
||||
|
||||
Run: `mvn -pl liteflow-react-agent/liteflow-react-agent-core dependency:tree -Dincludes=io.agentscope 2>&1 | tail -40`
|
||||
Expected: 列出 agentscope core + 所声明的扩展,版本均为 2.0.x。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add pom.xml liteflow-react-agent/liteflow-react-agent-core/pom.xml
|
||||
git commit -m "build(agent): 升级 agentscope 至 2.0.x 并声明扩展依赖"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — 会话与状态层
|
||||
|
||||
### Task 2.1: AgentStateStoreResolver(mode→store)
|
||||
|
||||
**Files:**
|
||||
- Create: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/AgentStateStoreResolver.java`
|
||||
- Create(test): `liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/AgentStateStoreResolverTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `MemoryStorageMode`、`AgentConfig`(`session.memory.*`、`workspace.root`)、v2 `AgentStateStore` 及其实现(按 Task 0 findings)、`ContextAwareHolder`(取 Redis/DataSource bean,沿用现有反射风格)。
|
||||
- Produces: `AgentStateStoreResolver.resolve(AgentConfig) -> AgentStateStore`(NONE 返回 NoOp/不落盘 store,见 R1 结论)。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.agent.state.AgentStateStoreResolver;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.state.AgentStateStore;
|
||||
import io.agentscope.core.state.InMemoryAgentStateStore;
|
||||
import io.agentscope.core.state.JsonFileAgentStateStore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import java.nio.file.Path;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class AgentStateStoreResolverTest {
|
||||
@TempDir Path tmp;
|
||||
private AgentConfig cfg(MemoryStorageMode mode) {
|
||||
AgentConfig c = new AgentConfig();
|
||||
c.getWorkspace().setRoot(tmp.toString()); // 必填
|
||||
c.getSession().getMemory().setMode(mode);
|
||||
return c;
|
||||
}
|
||||
@Test void jvm_maps_to_inMemory() {
|
||||
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.JVM));
|
||||
assertTrue(s instanceof InMemoryAgentStateStore, s.getClass().getName());
|
||||
}
|
||||
@Test void localFile_maps_to_jsonFile() {
|
||||
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.LOCAL_FILE));
|
||||
assertTrue(s instanceof JsonFileAgentStateStore, s.getClass().getName());
|
||||
}
|
||||
@Test void none_maps_to_noOp() {
|
||||
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.NONE));
|
||||
// 不落盘:save 是 no-op,load 返回空 AgentState。具体断言依 R1 结论。
|
||||
assertNotNull(s);
|
||||
// e.g. assertTrue(s instanceof NoOpAgentStateStore);
|
||||
}
|
||||
}
|
||||
```
|
||||
(REDIS/MYSQL 用例见 Task 2.1 Step 5,需 mock bean,放后。)
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=AgentStateStoreResolverTest test`
|
||||
Expected: FAIL(`AgentStateStoreResolver` 不存在)。
|
||||
|
||||
- [ ] **Step 3: 实现 resolver(NONE/JVM/LOCAL_FILE 三档先通)**
|
||||
|
||||
```java
|
||||
package com.yomahub.liteflow.agent.state;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.state.AgentStateStore;
|
||||
import io.agentscope.core.state.InMemoryAgentStateStore;
|
||||
import io.agentscope.core.state.JsonFileAgentStateStore;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public final class AgentStateStoreResolver {
|
||||
private AgentStateStoreResolver() {}
|
||||
|
||||
public static AgentStateStore resolve(AgentConfig cfg) {
|
||||
MemoryStorageMode mode = cfg.getSession().getMemory().getMode();
|
||||
switch (mode) {
|
||||
case NONE: return NoOpAgentStateStore.INSTANCE; // R1 结论兜底
|
||||
case JVM: return new InMemoryAgentStateStore();
|
||||
case LOCAL_FILE: return new JsonFileAgentStateStore(localRoot(cfg));
|
||||
case REDIS: return RedisStoreBuilder.build(cfg); // Task 2.1 Step 5
|
||||
case MYSQL: return MysqlStoreBuilder.build(cfg); // Task 2.1 Step 5
|
||||
case OSS: return OssStoreBuilder.build(cfg); // Task 2.1 Step 5
|
||||
default: throw new AgentConfigException("Unsupported memory mode: " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path localRoot(AgentConfig cfg) {
|
||||
Path root = Paths.get(cfg.getWorkspace().getRoot()).toAbsolutePath().normalize()
|
||||
.resolve(cfg.getSession().getMemory().getLocalFile().getSubDir());
|
||||
return root;
|
||||
}
|
||||
}
|
||||
```
|
||||
(`NoOpAgentStateStore`、`RedisStoreBuilder`、`MysqlStoreBuilder`、`OssStoreBuilder` 在后续 Step 创建;NONE/JVM/LOCAL_FILE 本步即可让前三个测试通过。)
|
||||
|
||||
- [ ] **Step 4: 跑测试确认通过(前三例)**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=AgentStateStoreResolverTest test`
|
||||
Expected: 前三例 PASS。
|
||||
|
||||
- [ ] **Step 5: NoOp + Redis + MySQL + OSS builder**
|
||||
|
||||
`NoOpAgentStateStore.java`:依 R1 结论——若 v2 提供原生 NoOp/Null store,则 `NoOpAgentStateStore` 直接 `= 原生类`(删除自写);否则实现一个最小 `AgentStateStore`:`save(...)` 空实现,`load(...)` 返回新建空 `AgentState`,`delete(...)`/`list(...)` 空实现。
|
||||
|
||||
`RedisStoreBuilder.build(cfg)`:复刻旧 `RedisAgentSessionFactory` 的反射逻辑——按 `redis.beanName/clientType(REDISSON|JEDIS|LETTUCE)` 从 `ContextAwareHolder` 取 bean,构造 v2 `RedisAgentStateStore`(具体构造方法/Builder 形态依 R1/Task 0 笔记)。
|
||||
|
||||
`MysqlStoreBuilder.build(cfg)`:按 `mysql.dataSourceBeanName/databaseName/tableName` 取 `DataSource`,构造 v2 `MysqlAgentStateStore`。
|
||||
|
||||
`OssStoreBuilder.build(cfg)`:新增;按 `oss.*` 子配置(Task 8 新增 property)构造 `OssAgentStateStore`。
|
||||
|
||||
为 Redis/MySQL 各补一个 mock 测试(mock bean 返回 mock 客户端,断言返回的 store 类型),加入 `AgentStateStoreResolverTest`。
|
||||
|
||||
- [ ] **Step 6: 跑全部 resolver 测试**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=AgentStateStoreResolverTest test`
|
||||
Expected: 全部 PASS。
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/ \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/AgentStateStoreResolverTest.java
|
||||
git commit -m "feat(agent): AgentStateStoreResolver 把 MemoryStorageMode 映射到 v2 AgentStateStore"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.2: HarnessAgentFactory(单例无状态 agent 构建)
|
||||
|
||||
**Files:**
|
||||
- Create: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/HarnessAgentFactory.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `ReActAgentComponent`(读 `model()/systemPrompt()/tools()/middlewares()/...`)、`AgentConfig`、`AgentStateStoreResolver`、`PermissionConfigMapper`(Task 3.1)、`SkillRepositoryResolver`(Task 4.1)、`MiddlewareBase` 列表。
|
||||
- Produces: `HarnessAgentFactory.getOrCreate(ReActAgentComponent, AgentConfig) -> HarnessAgent`(按组件子类 Class 缓存单例)。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.agent.state.HarnessAgentFactory;
|
||||
import com.yomahub.liteflow.agent.component.ReActAgentComponent;
|
||||
import com.yomahub.liteflow.agent.model.ModelSpec;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import io.agentscope.core.HarnessAgent;
|
||||
import io.agentscope.core.model.Model;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class HarnessAgentFactoryTest {
|
||||
static class StubCmp extends ReActAgentComponent {
|
||||
@Override protected ModelSpec<?> model() { return cfg -> stubModel(); }
|
||||
@Override protected String systemPrompt() { return "x"; }
|
||||
@Override protected String userPrompt() { return "y"; }
|
||||
}
|
||||
static Model stubModel() { /* 返回一个 mock Model,避免真实网络;用 mockito */ return null; }
|
||||
|
||||
@Test void sameComponentClass_returnsSameSingleton() {
|
||||
AgentConfig cfg = HarnessFixture.minimalConfig();
|
||||
HarnessAgent a1 = HarnessAgentFactory.getOrCreate(new StubCmp(), cfg);
|
||||
HarnessAgent a2 = HarnessAgentFactory.getOrCreate(new StubCmp(), cfg);
|
||||
assertSame(a1, a2, "按组件子类缓存单例");
|
||||
}
|
||||
}
|
||||
```
|
||||
(`HarnessFixture` 提供最小可构建 `AgentConfig`,放进 `v2/HarnessFixture.java`。)
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=HarnessAgentFactoryTest test`
|
||||
Expected: FAIL(类不存在)。
|
||||
|
||||
- [ ] **Step 3: 实现 HarnessAgentFactory**
|
||||
|
||||
```java
|
||||
package com.yomahub.liteflow.agent.state;
|
||||
|
||||
import com.yomahub.liteflow.agent.component.ReActAgentComponent;
|
||||
import com.yomahub.liteflow.agent.middleware.HookMiddlewareAdapter;
|
||||
import com.yomahub.liteflow.agent.model.ModelSpec;
|
||||
import com.yomahub.liteflow.agent.permission.PermissionConfigMapper;
|
||||
import com.yomahub.liteflow.agent.skill.SkillRepositoryResolver;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import io.agentscope.core.HarnessAgent;
|
||||
import io.agentscope.core.middleware.MiddlewareBase;
|
||||
import io.agentscope.core.model.Model;
|
||||
import io.agentscope.core.state.AgentStateStore;
|
||||
import io.agentscope.core.tool.Toolkit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class HarnessAgentFactory {
|
||||
|
||||
private static final ConcurrentHashMap<Class<?>, HarnessAgent> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static HarnessAgent getOrCreate(ReActAgentComponent cmp, AgentConfig cfg) {
|
||||
return CACHE.computeIfAbsent(cmp.getClass(), k -> build(cmp, cfg));
|
||||
}
|
||||
|
||||
private static HarnessAgent build(ReActAgentComponent cmp, AgentConfig cfg) {
|
||||
Toolkit toolkit = new Toolkit();
|
||||
cmp.tools().forEach(toolkit::registerTool);
|
||||
|
||||
List<MiddlewareBase> mws = new ArrayList<>(cmp.middlewares());
|
||||
if (cmp.enableReActLogging()) mws.add(new com.yomahub.liteflow.agent.middleware.LoggingMiddleware());
|
||||
mws.add(new com.yomahub.liteflow.agent.middleware.ChatUsageMiddleware());
|
||||
// 业务侧遗留 Hook 桥接
|
||||
cmp.hooks().forEach(h -> mws.add(HookMiddlewareAdapter.wrap(h)));
|
||||
|
||||
Model model = cmp.buildModel(); // ModelSpec.resolve(agentConfig())
|
||||
|
||||
HarnessAgent.Builder b = HarnessAgent.builder()
|
||||
.name(cmp.getNodeId() == null ? "liteflow-agent" : cmp.getNodeId())
|
||||
.sysPrompt(cmp.effectiveSystemPrompt())
|
||||
.model(model)
|
||||
.toolkit(toolkit)
|
||||
.middlewares(mws)
|
||||
.maxIters(cmp.maxIterations() > 0 ? cmp.maxIterations() : cfg.getDefaults().getMaxIterations())
|
||||
.stateStore(AgentStateStoreResolver.resolve(cfg))
|
||||
.permissionContext(PermissionConfigMapper.map(cfg));
|
||||
|
||||
// 文件系统 / skill / 增强能力(Task 3/4/7 各自接入)
|
||||
HarnessFilesystemConfigurer.configure(b, cmp, cfg); // Task 3.2
|
||||
SkillRepositoryResolver.configure(b, cmp, cfg); // Task 4.1
|
||||
HarnessCapabilityConfigurer.configure(b, cmp, cfg); // Task 7
|
||||
|
||||
return b.build();
|
||||
}
|
||||
|
||||
/** 仅供测试重置缓存。 */
|
||||
static void resetForTesting() { CACHE.clear(); }
|
||||
}
|
||||
```
|
||||
(`HarnessFilesystemConfigurer`/`SkillRepositoryResolver`/`HarnessCapabilityConfigurer` 在后续 Task 创建;本 Task 先各建一个空 `static configure(...)` 占位,使本 Task 编译通过,后续 Task 填充。)
|
||||
|
||||
- [ ] **Step 4: 跑测试确认通过**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=HarnessAgentFactoryTest test`
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/HarnessAgentFactory.java \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/HarnessAgentFactoryTest.java \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/HarnessFixture.java
|
||||
git commit -m "feat(agent): HarnessAgentFactory 构建并按组件子类缓存无状态 HarnessAgent 单例"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.3: 重写 ReActAgentComponent.process()
|
||||
|
||||
**Files:**
|
||||
- Modify: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentComponent.java`
|
||||
- Modify: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentContext.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `HarnessAgentFactory`、`AgentEventBridge`(Task 6.1,本 Task 先用占位静态方法)、`RuntimeContext`、`UserMessage`。
|
||||
- Produces: `process()` 走 v2 单例 agent + RuntimeContext;新增 `middlewares()`、`runtimeContext()`、`permissionContext()`;`hooks()` 桥接 + `@Deprecated`;`ctx.getRuntimeContext()`。
|
||||
|
||||
- [ ] **Step 1: 写失败测试(隔离 + 共享语义)**
|
||||
|
||||
`v2/StateIsolationTest.java`(Spring Boot 集成,用 mock 模型):
|
||||
- 两个不同 `agentKey`(两个组件)同 `conversationId`:各自 `responseData` 独立;但两者 workspace 内能读写到同一文件(共享 workspace)。
|
||||
- 同 `(conversationId, agentKey)` 两次调用:第二次能"记住"第一次(AgentState 恢复)——用 JVM store + mock 模型回放验证。
|
||||
|
||||
(具体断言形式参考既有 `feature/multiturn/MultiTurnMemoryTest.java` 的 mock 模型回放范式。)
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=StateIsolationTest test`
|
||||
Expected: FAIL(旧 session 包已删 / process 仍旧逻辑)。
|
||||
|
||||
- [ ] **Step 3: 重写 process()(替换 §spec 4.3 骨架为真实代码)**
|
||||
|
||||
把 `ReActAgentComponent.process()`、`callAgent()`、`streamAgent()`、`buildAgent()`、`AgentSessionManagerHolder` 整体替换:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public final void process() throws Exception {
|
||||
AgentConfig cfg = agentConfig();
|
||||
HarnessAgent agent = HarnessAgentFactory.getOrCreate(this, cfg);
|
||||
|
||||
String cid = resolveConversationId();
|
||||
Slot slot = getSlot();
|
||||
slot.setConversationId(cid);
|
||||
String akey = agentKey();
|
||||
RuntimeContext rc = runtimeContext(cid, akey);
|
||||
|
||||
ReActAgentContext ctx = new ReActAgentContext(slot, cid, akey, workspaceRoot(cfg, cid));
|
||||
ctx.setRuntimeContext(rc);
|
||||
slot.setAttachment(ctxKey(), ctx);
|
||||
try {
|
||||
ChatUsageMiddleware.bind(ctx); // 让 ctx.getChatUsage() 生效
|
||||
Msg reply = callAgent(agent, new UserMessage(userPrompt()), rc, slot);
|
||||
handleReply(reply);
|
||||
} finally {
|
||||
ChatUsageMiddleware.unbind();
|
||||
slot.removeAttachment(ctxKey());
|
||||
}
|
||||
}
|
||||
|
||||
protected RuntimeContext runtimeContext(String conversationId, String agentKey) {
|
||||
return RuntimeContext.builder().userId(conversationId).sessionId(agentKey).build();
|
||||
}
|
||||
|
||||
private Msg callAgent(HarnessAgent agent, Msg userMsg, RuntimeContext rc, Slot slot) {
|
||||
if (!FlowEventPublisher.hasListener(slot)) {
|
||||
return agent.call(List.of(userMsg), rc).block();
|
||||
}
|
||||
return AgentEventBridge.streamAndPublish(agent, userMsg, rc, slot).block();
|
||||
}
|
||||
|
||||
/** 业务侧可用 middleware 取代 hooks()。默认空。 */
|
||||
protected List<MiddlewareBase> middlewares() { return List.of(); }
|
||||
|
||||
/** 旧 hooks() 保留兼容,内部经 HookMiddlewareAdapter 桥接。 */
|
||||
@Deprecated(forRemoval = true, since = "2.16.0")
|
||||
protected List<Hook> hooks() { return List.of(); }
|
||||
```
|
||||
|
||||
移除:`buildAgent()`、`streamAgent()`、`AgentSessionManagerHolder`、对 `AgentSessionManager`/`AgentSession` 的全部引用、`skillHookKey()` 相关 skill-tracking-by-hook 逻辑(改由 middleware,Task 5)。
|
||||
|
||||
`workspaceRoot(cfg, cid)`:返回 HarnessAgent workspace 根(不再每会话一目录),具体在 Task 3.2 文件系统配置里定义;本 Task 先返回 `Paths.get(cfg.getWorkspace().getRoot())`。
|
||||
|
||||
`ReActAgentContext`:新增 `RuntimeContext runtimeContext` 字段 + getter/setter;`getChatUsage()` 改为从 `ChatUsageMiddleware` 当前绑定 ctx 取累计值。
|
||||
|
||||
- [ ] **Step 4: 跑测试确认通过**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=StateIsolationTest test`
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 5: 删除旧 session 包**
|
||||
|
||||
```bash
|
||||
git rm liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSessionManager.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSession.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/AgentSessionFactory.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/AgentSessionFactoryRegistry.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/InMemoryAgentSessionFactory.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/LocalFileAgentSessionFactory.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/RedisAgentSessionFactory.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/MysqlAgentSessionFactory.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/NoneAgentSessionFactory.java
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 编译 react-agent 全模块**
|
||||
|
||||
Run: `mvn -pl liteflow-react-agent/liteflow-react-agent-core -am compile 2>&1 | tail -30`
|
||||
Expected: 编译通过(无 `io.agentscope.core.session.*` 引用残留)。
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add -A liteflow-react-agent/liteflow-react-agent-core \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/StateIsolationTest.java
|
||||
git commit -m "refactor(agent): process() 改走 HarnessAgent 单例+RuntimeContext,删除旧 session 包"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — 工具与权限
|
||||
|
||||
### Task 3.1: PermissionConfigMapper(ShellConfig→Permission)
|
||||
|
||||
**Files:**
|
||||
- Create: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/permission/PermissionConfigMapper.java`
|
||||
- Create(test): `v2/PermissionConfigMapperTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `ShellConfig`(mode/whitelist/blacklist)、R6 结论(`PermissionContextState`/`PermissionRule`/`PermissionMode` 构造)。
|
||||
- Produces: `PermissionConfigMapper.map(AgentConfig) -> PermissionContextState`。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
@Test void disabled_mode_deniesExecute() { /* shell.mode=DISABLED → execute 被拒 */ }
|
||||
@Test void whitelist_allowsListedCommand() { /* WHITELIST → 仅白名单命令允许 */ }
|
||||
@Test void blacklist_deniesListedCommand() { /* BLACKLIST → 黑名单命令被拒 */ }
|
||||
```
|
||||
(具体 `PermissionContextState` 断言依 R6 笔记。)
|
||||
|
||||
- [ ] **Step 2: 跑确认失败** — `mvn ... -Dtest=PermissionConfigMapperTest test` → FAIL。
|
||||
|
||||
- [ ] **Step 3: 实现**(依 R6 笔记构造 `PermissionContextState`;DISABLED→对 `execute` 工具整体拒绝;WHITELIST/BLACKLIST→命令级 `PermissionRule`,若 v2 原生不支持命令级,则在此 mapper 内做命令级裁决的轻量包装并记入 findings)。
|
||||
|
||||
- [ ] **Step 4: 跑通过** — 同上 → PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/permission/PermissionConfigMapper.java \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/PermissionConfigMapperTest.java
|
||||
git commit -m "feat(agent): PermissionConfigMapper 把 ShellConfig 映射到 v2 PermissionContextState"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3.2: 删除自建工具,接 HarnessAgent 文件系统 + execute
|
||||
|
||||
**Files:**
|
||||
- Delete: `agent/tool/WorkspaceFileTools.java`、`agent/tool/ManagedShellCommandTool.java`
|
||||
- Create: `agent/state/HarnessFilesystemConfigurer.java`
|
||||
- Modify: property `WorkspaceConfig` / `ShellConfig`(新增 `filesystemMode` 字段,默认 LOCAL;保留老键)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `FilesystemSpec`(`LocalFilesystemSpec`/`DockerFilesystemSpec`/`RemoteFilesystemSpec`)、`filesystem.mode`。
|
||||
- Produces: `HarnessFilesystemConfigurer.configure(HarnessAgent.Builder, cmp, cfg)`——按 mode 设 `.filesystem(...)`;`enableShellTool()`/`enableWorkspaceFileTools()` 转为对应工具组/`disable*` 开关。
|
||||
|
||||
- [ ] **Step 1: 写失败测试** — `v2/FilesystemModeTest`:`filesystem.mode=LOCAL` 时 build 出的 agent 含 `execute`+文件工具;`DISABLED` shell 时不注册 `execute`。
|
||||
|
||||
- [ ] **Step 2: 跑确认失败。**
|
||||
|
||||
- [ ] **Step 3: 实现 `HarnessFilesystemConfigurer`**
|
||||
|
||||
```java
|
||||
static void configure(HarnessAgent.Builder b, ReActAgentComponent cmp, AgentConfig cfg) {
|
||||
FilesystemMode mode = cfg.getWorkspace().getFilesystemMode(); // 新增字段,默认 LOCAL
|
||||
switch (mode) {
|
||||
case LOCAL -> { /* 默认:HarnessAgent 自带 LocalFilesystemSpec + host shell;按 enableShellTool() 决定是否禁 execute */ }
|
||||
case SANDBOX_DOCKER -> b.filesystem(new DockerFilesystemSpec()); // 依 findings 调参
|
||||
case SANDBOX_K8S, SANDBOX_E2B, SANDBOX_AGENTRUN -> { /* 依 findings */ }
|
||||
case REMOTE -> b.filesystem(new RemoteFilesystemSpec(distributedStore(cfg)));
|
||||
}
|
||||
if (!cmp.enableShellTool()) b.disableShell(); // 方法名依 findings
|
||||
if (!cmp.enableWorkspaceFileTools()) b.disableFileTools(); // 方法名依 findings
|
||||
}
|
||||
```
|
||||
(具体 builder 方法名/`disable*` 形态依 Task 0 笔记修正。)
|
||||
|
||||
- [ ] **Step 4: property 新增** `WorkspaceConfig.filesystemMode`(枚举 `FilesystemMode`,默认 `LOCAL`),`@ConfigurationProperties` 自动绑定 `filesystem-mode`。
|
||||
|
||||
- [ ] **Step 5: 删除自建工具类**
|
||||
|
||||
```bash
|
||||
git rm liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/tool/WorkspaceFileTools.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/tool/ManagedShellCommandTool.java
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 跑测试 + 编译** — `mvn ... -Dtest=FilesystemModeTest test` → PASS;`mvn -pl liteflow-react-agent/liteflow-react-agent-core -am compile` → 通过。
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor(agent): 删除自建文件/shell 工具,采用 HarnessAgent 文件系统+execute+Permission"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Skill
|
||||
|
||||
### Task 4.1: SkillRepositoryResolver + 删除 SkillBoxFactory
|
||||
|
||||
**Files:**
|
||||
- Delete: `agent/skill/SkillBoxFactory.java`、`SkillToolResolver.java`、`SkillLoadResult.java`、`SkillTrackingHook.java`
|
||||
- Create: `agent/skill/SkillRepositoryResolver.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `SkillsConfig`(enabled/path/strict + 新增 repository 类型)、`cmp.skills()`(allow-list)、`cmp.enableSkills()`、v2 `skillRepository`/`SkillFilter`/`dynamicSkillsEnabled`。
|
||||
- Produces: `SkillRepositoryResolver.configure(b, cmp, cfg)`——注册 `Classpath/Git/Nacos/MySQL` 仓库或走 workspace `skills/`,设 `skillFilter`。
|
||||
|
||||
- [ ] **Step 1: 写失败测试** — `v2/SkillRepositoryTest`:`skills.enabled=true` + `skills.path=<dir with SKILL.md>` 时 build 出的 agent 能解析到 skill;`skills()` allow-list 生效过滤。
|
||||
|
||||
- [ ] **Step 2: 跑确认失败。**
|
||||
|
||||
- [ ] **Step 3: 实现 `SkillRepositoryResolver`**——默认把 `skills.path` 作为 workspace skills 目录(HarnessAgent 原生扫描),可选 `.skillRepository(new ClasspathSkillRepository(...))` 等(依 findings)。`enableSkills()==false` 时 `.dynamicSkillsEnabled(false)`。`skills()` 非空 → `.skillFilter(...)`。
|
||||
|
||||
- [ ] **Step 4: 删除旧 skill 类**
|
||||
|
||||
```bash
|
||||
git rm liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillBoxFactory.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillToolResolver.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillLoadResult.java \
|
||||
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillTrackingHook.java
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 跑测试 + 编译** → PASS / 通过。
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor(agent): SkillBoxFactory→skillRepository/workspace skills,删除旧 skill 类"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Hook → Middleware
|
||||
|
||||
### Task 5.1: LoggingMiddleware + ChatUsageMiddleware + SkillTrackingMiddleware + HookMiddlewareAdapter
|
||||
|
||||
**Files:**
|
||||
- Create: `agent/middleware/LoggingMiddleware.java`、`ChatUsageMiddleware.java`、`SkillTrackingMiddleware.java`、`HookMiddlewareAdapter.java`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: R5 笔记(`MiddlewareBase` 各 stage 回调签名、`ChatUsage` 取值点)、`MiddlewareBase`、`Hook`(桥接源)。
|
||||
- Produces: `ctx.getChatUsage()` 由 `ChatUsageMiddleware` 在 `onModelCall` 累加;`bind(ctx)/unbind()` 线程本地绑定。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
// v2/ChatUsageMiddlewareTest
|
||||
@Test void accumulates_across_steps() {
|
||||
// 回放两次 model call,各自带 ChatUsage;断言 ctx.getChatUsage() 为两者之和
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑确认失败。**
|
||||
|
||||
- [ ] **Step 3: 实现**——`ChatUsageMiddleware`:`onModelCall` 后从 message 取 `ChatUsage`(R5),累加到与当前 `ReActAgentContext` 绑定的累加器(`bind/unbind` 用 ThreadLocal 或 slot attachment)。`LoggingMiddleware`:`onReasoning`/`onActing` 打 reason/act/error(替代 `ReActLoggingHook`)。`SkillTrackingMiddleware`:记录已用 skill(替代 `SkillTrackingHook`)。`HookMiddlewareAdapter.wrap(Hook)`:把旧 `Hook.onEvent` 转成对应 stage 调用(依 findings 的 `LegacyHookDispatcher` 是否可直接复用)。
|
||||
|
||||
- [ ] **Step 4: 跑测试** → PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/middleware/ \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/ChatUsageMiddlewareTest.java
|
||||
git commit -m "feat(agent): Hook→Middleware(Logging/ChatUsage/SkillTracking)+ Hook 桥接适配器"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — 流式事件
|
||||
|
||||
### Task 6.1: AgentEventBridge(AgentEvent→FlowEvent)
|
||||
|
||||
**Files:**
|
||||
- Create: `agent/event/AgentEventBridge.java`
|
||||
- Create(test): `v2/AgentEventBridgeTest.java`(纯单元)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: v2 `AgentEvent`/`AgentEventType`(依 findings 的具体事件类型常量)、`FlowEvent`/`FlowEventPublisher`。
|
||||
- Produces: `AgentEventBridge.streamAndPublish(HarnessAgent, Msg, RuntimeContext, Slot) -> Mono<Msg>`;映射 4 类对外 type + 可选 `agent.hitl.*`。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**
|
||||
|
||||
```java
|
||||
@Test void reasoningDelta_mapsTo_agentReasoning() { /* TextBlockDeltaEvent → "agent.reasoning", last=false */ }
|
||||
@Test void agentResult_mapsTo_agentResult_lastTrue() { /* AgentResultEvent → "agent.result", last=true, 返回该 Msg */ }
|
||||
@Test void hitlEvent_mapsTo_agentHitl() { /* RequireUserConfirmEvent → "agent.hitl.confirm" */ }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑确认失败。**
|
||||
|
||||
- [ ] **Step 3: 实现**
|
||||
|
||||
```java
|
||||
public static Mono<Msg> streamAndPublish(HarnessAgent agent, Msg userMsg, RuntimeContext rc, Slot slot) {
|
||||
AtomicReference<Msg> result = new AtomicReference<>();
|
||||
return agent.streamEvents(List.of(userMsg), rc)
|
||||
.doOnNext(ev -> {
|
||||
FlowEventMapping m = map(ev);
|
||||
if (m == null) return;
|
||||
if (m.isResult()) result.set(m.msg());
|
||||
FlowEventPublisher.publish(slot, FlowEvent.builder()
|
||||
.type(m.type()).chainId(slot.getChainId()).nodeId(/* cmp nodeId */)
|
||||
.requestId(slot.getRequestId()).conversationId(slot.getConversationId())
|
||||
.text(m.text()).last(m.last()).data(ev).build());
|
||||
})
|
||||
.then(Mono.defer(() -> result.get() == null ? Mono.empty() : Mono.just(result.get())));
|
||||
}
|
||||
```
|
||||
`map(AgentEvent)` 依 findings 把 `AgentEventType` 常量映射到 4 类 type(+HITL)。
|
||||
|
||||
- [ ] **Step 4: 跑测试** → PASS。
|
||||
|
||||
- [ ] **Step 5: 集成测试** — `v2/StreamingBridgeTest`(Spring Boot + mock 模型流式回放):订阅 FlowEvent,断言收到 `agent.reasoning` 增量与末尾 `agent.result(last=true)`。
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/event/AgentEventBridge.java \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/AgentEventBridgeTest.java \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/StreamingBridgeTest.java
|
||||
git commit -m "feat(agent): AgentEventBridge 把 v2 AgentEvent 映射到 FlowEvent (streamEvents)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — 增强能力接入
|
||||
|
||||
### Task 7.1: HarnessCapabilityConfigurer(compaction / memory / toolResultEviction / planMode / taskList / subagent / sandbox / interrupt)
|
||||
|
||||
**Files:**
|
||||
- Create: `agent/state/HarnessCapabilityConfigurer.java`
|
||||
- Create: property `CompactionProperties`/`PlanModeProperties`/`TaskListProperties`/`SubagentProperties`(或并入 `AgentConfig`)
|
||||
- Create: `ReActAgentComponent.interrupt(String conversationId, String agentKey)` 方法
|
||||
- Create(test): `v2/CompactionTest`、`v2/PlanModeTest`、`v2/SubagentTest`、`v2/InterruptTest`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 新增配置开关、`HarnessAgent.Builder` 的 `.compaction()/.memory()/.toolResultEviction()/.enablePlanMode()/.enableTaskList()/.subagent()`、`agent.interrupt(userId,sessionId)`。
|
||||
- Produces: 按配置在 `HarnessAgentFactory.build()` 阶段挂载能力;`interrupt()` 透传到 agent。
|
||||
|
||||
- [ ] **Step 1: 写失败测试**(每个能力一个最小集成用例)
|
||||
- `CompactionTest`:长对话超过阈值 → 触发压缩(断言 AgentState.summary 非空)。
|
||||
- `PlanModeTest`:`planMode.enabled=true` → agent 先进只读计划阶段,`plan_exit` 后才获写权限。
|
||||
- `SubagentTest`:声明一个子 agent → 主 agent 可委派(断言子 agent reply 出现)。
|
||||
- `InterruptTest`:in-flight 调用 `interrupt(conv,key)` → 该 session call 以 `INTERRUPTED` 结束、AgentState 已存。
|
||||
|
||||
- [ ] **Step 2: 跑确认失败。**
|
||||
|
||||
- [ ] **Step 3: 实现 `HarnessCapabilityConfigurer.configure(b, cmp, cfg)`**
|
||||
|
||||
```java
|
||||
if (cfg.getCompaction().isEnabled())
|
||||
b.compaction(CompactionConfig.builder().model(cfg.getCompaction().getModel()).build());
|
||||
// .memory(MemoryConfig) 默认开,可按 cfg 覆盖 prompts/model
|
||||
if (cfg.getPlanMode().isEnabled()) b.enablePlanMode();
|
||||
if (cfg.getTaskList().isEnabled()) b.enableTaskList(true);
|
||||
cfg.getSubagents().forEach(sd -> b.subagent(sd));
|
||||
```
|
||||
(具体 `CompactionConfig`/`MemoryConfig`/subagent 声明构造依 findings。)
|
||||
|
||||
- [ ] **Step 4: 实现 interrupt**
|
||||
|
||||
```java
|
||||
// ReActAgentComponent
|
||||
public final GenerateResult interrupt(String conversationId, String agentKey) {
|
||||
HarnessAgent agent = HarnessAgentFactory.getOrCreate(this, agentConfig());
|
||||
return agent.interrupt(conversationId, agentKey); // 返回类型依 findings
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: property 类** — `AgentConfig` 新增 `compaction/planMode/taskList/subagents` 子配置(`@ConfigurationProperties` 自动绑定 `compaction.*` 等)。
|
||||
|
||||
- [ ] **Step 6: 跑测试** → 全 PASS。
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "feat(agent): 接入 HarnessAgent compaction/memory/planMode/taskList/subagent/interrupt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — 配置映射收口 + 废弃告警 + 文档
|
||||
|
||||
### Task 8.1: @Deprecated 标注 + 启动告警
|
||||
|
||||
**Files:**
|
||||
- Modify: `property/agent/SessionConfig.java`(`maxSessions/idleTimeout/cleanupInterval` `@Deprecated`)
|
||||
- Modify: `property/agent/MemoryStorageConfig.java`(`loadOnFirstUse/saveAfterCall/saveOnError` `@Deprecated`)
|
||||
- Create: `agent/config/AgentConfigDeprecationLogger.java`(启动时对使用了已废弃键的配置打 WARN)
|
||||
|
||||
- [ ] **Step 1: 写失败测试** — `v2/DeprecationWarningTest`:配置含 `session.maxSessions` 时启动日志含 WARN 指引。
|
||||
|
||||
- [ ] **Step 2: 跑确认失败。**
|
||||
|
||||
- [ ] **Step 3: 实现** — 给字段加 `@Deprecated(since="2.16.0", forRemoval=true)` + JavaDoc 指引替代;`AgentConfigDeprecationLogger` 在 agent 模块初始化时扫描配置并打 WARN。
|
||||
|
||||
- [ ] **Step 4: 跑测试** → PASS。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(agent): 标注被 v2 取代的配置键 @Deprecated 并启动告警"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8.2: 更新使用指南 + 源码兼容冒烟
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/liteflow-react-agent-guide.md`
|
||||
- Create(test): `v2/BackCompatSmokeTest.java`
|
||||
|
||||
- [ ] **Step 1: 写源码兼容冒烟测试** — 取 guide §2.3 的 `DeepSeekAgentCmp`(继承 `ReActAgentComponent`,覆写 `model()/systemPrompt()/userPrompt()`),**原样不动**放进 `v2/BackCompatSmokeTest`,用 mock 模型跑通一次 `execute2Resp`,断言成功。这把"业务子类零改动"做成回归测试。
|
||||
|
||||
- [ ] **Step 2: 跑确认通过**(若失败,说明对外签名被破坏,回查 Task 2.3/5)。
|
||||
|
||||
- [ ] **Step 3: 更新 guide** — 增加"从 1.0 升级"章节:配置键映射表(spec §7)、workspace 布局/清理语义变化、hooks()→middlewares() 迁移、被删自建工具类的影响、R1–R7 的行为说明。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/liteflow-react-agent-guide.md \
|
||||
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/BackCompatSmokeTest.java
|
||||
git commit -m "docs(agent): 更新 react-agent 指南并新增 1.0→2.0 升级说明 + 兼容冒烟测试"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 9 — 全量回归
|
||||
|
||||
### Task 9: 全量构建 + 既有测试回归
|
||||
|
||||
- [ ] **Step 1: 单模块测试**
|
||||
|
||||
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent test 2>&1 | tail -40`
|
||||
Expected: 全 PASS(含既有平台/特性测试,可能需按 v2 调整 mock 模型回放——若既有测试因 v2 API 变化失败,按 findings 修正测试夹具,**不改 skipTests**)。
|
||||
|
||||
- [ ] **Step 2: react-agent 全模块编译 + 打包**
|
||||
|
||||
Run: `mvn -pl liteflow-react-agent/liteflow-react-agent-core,liteflow-react-agent/liteflow-react-agent-openai,liteflow-react-agent/liteflow-react-agent-anthropic,liteflow-react-agent/liteflow-react-agent-gemini,liteflow-react-agent/liteflow-react-agent-dashscope -am clean package -DskipTests 2>&1 | tail -30`
|
||||
Expected: BUILD SUCCESS。
|
||||
|
||||
- [ ] **Step 3: vendor Spec 微调核对(R4)** — 逐个 vendor `XxxSpec.resolve()` 与 findings 笔记核对 `XxxChatModel.builder()` 调用,必要时修正。
|
||||
|
||||
- [ ] **Step 4: 全量回归(JDK 21)**
|
||||
|
||||
Run: `mvn -P compile-17+ clean test 2>&1 | tail -40`
|
||||
Expected: BUILD SUCCESS(react-agent 相关全绿;其余模块不受影响)。
|
||||
|
||||
- [ ] **Step 5: Commit(若有测试夹具修正)**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "test(agent): v2 迁移全量回归(含既有测试夹具按 v2 调整)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review(计划自检)
|
||||
|
||||
**1. Spec 覆盖**:spec §3 架构地图→Task 2.x/3.x/4.x/5/6;§4 会话/状态→Task 2.1–2.3;§5 workspace/工具→Task 3.x;§6 skill→Task 4.1;§7 配置映射→Task 2.1/3.2/7.1/8.1;§8 Hook→Middleware→Task 5.1;§9 模型层→Task 9 Step 3(R4);§10 流式→Task 6.1;§11 新增能力→Task 7.1;§12 API 兼容→Task 2.3/8.2;§13 破坏性变更→各删除步骤;§14 九阶段→Phase 0–9;§15 测试→各 Task 测试 + Phase 9;§16 风险 R1–R7→Task 0 spike 逐项消化。无遗漏。
|
||||
|
||||
**2. 占位符扫描**:凡涉及 R1/R4/R5/R6 不确定 API 处,均以"依 Task 0 findings 修正/核对"显式标注,并配 spike 任务先行——非空洞占位。`HarnessFilesystemConfigurer`/`SkillRepositoryResolver`/`HarnessCapabilityConfigurer` 在 Task 2.2 先建空 `configure(...)` 占位以保编译,所属 Task 内填充,已在各自步骤说明。
|
||||
|
||||
**3. 类型一致性**:`AgentStateStoreResolver.resolve(AgentConfig)->AgentStateStore`、`HarnessAgentFactory.getOrCreate(ReActAgentComponent,AgentConfig)->HarnessAgent`、`PermissionConfigMapper.map(AgentConfig)->PermissionContextState`、`AgentEventBridge.streamAndPublish(HarnessAgent,Msg,RuntimeContext,Slot)->Mono<Msg>`、`runtimeContext(String,String)->RuntimeContext`、`middlewares()->List<MiddlewareBase>`、`hooks()->List<Hook>` 在各 Task 间签名一致。`ctx.getRuntimeContext()`/`getChatUsage()` 跨 Task 2.3 与 5.1 一致。
|
||||
|
||||
**已知前置依赖**:Task 0(spike/findings)必须最先;Task 1(依赖)紧随;Task 2.1→2.2→2.3 顺序依赖;Task 3.1/4.1/5.1/6.1 可在 2.3 后并行;Task 7.1 依赖 3.1/4.1;Task 8/9 收尾。
|
||||
Loading…
Reference in New Issue