diff --git a/docs/superpowers/plans/2026-06-19-agentscope-v2-migration.md b/docs/superpowers/plans/2026-06-19-agentscope-v2-migration.md new file mode 100644 index 000000000..0096448e3 --- /dev/null +++ b/docs/superpowers/plans/2026-06-19-agentscope-v2-migration.md @@ -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` 设 `2.0.x`,具体 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/...//*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`,把 `1.0.12` 改为已发布最新 `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` 中 `` 改为 Task 0 确认的最新 `2.0.x`。 + +- [ ] **Step 2: core pom 按需声明扩展(provided/optional 优先,避免强耦合)** + +`liteflow-react-agent-core/pom.xml` 新增(全部 `true`,反射/类路径探测式加载,与现有 Redis/MySQL factory 风格一致): + +```xml + + io.agentscopeagentscope-extensions-redis + ${agentscope.version}true + + + io.agentscopeagentscope-extensions-mysql + ${agentscope.version}true + + + io.agentscopeagentscope-extensions-oss + ${agentscope.version}true + + + io.agentscopeagentscope-extensions-skill-git-repository + ${agentscope.version}true + +``` +(具体 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, 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 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 middlewares() { return List.of(); } + +/** 旧 hooks() 保留兼容,内部经 HookMiddlewareAdapter 桥接。 */ +@Deprecated(forRemoval = true, since = "2.16.0") +protected List 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=` 时 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`;映射 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 streamAndPublish(HarnessAgent agent, Msg userMsg, RuntimeContext rc, Slot slot) { + AtomicReference 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`、`runtimeContext(String,String)->RuntimeContext`、`middlewares()->List`、`hooks()->List` 在各 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 收尾。