diff --git a/AGENTS.md b/AGENTS.md index 9a9e20320ed89a045c537b80bf31ac4ba65e844e..abe5f5da08692d6727c7c9367d76fb2fb9adfbd6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,13 @@ ### 1. 统一消息模型 -位于 [src/main/java/com/jimuqu/claw/agent/model](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model): +位于 [src/main/java/com/jimuqu/claw/agent/model](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model) 及其子包: + +- [src/main/java/com/jimuqu/claw/agent/model/envelope](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model/envelope) +- [src/main/java/com/jimuqu/claw/agent/model/event](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model/event) +- [src/main/java/com/jimuqu/claw/agent/model/route](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model/route) +- [src/main/java/com/jimuqu/claw/agent/model/run](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model/run) +- [src/main/java/com/jimuqu/claw/agent/model/enums](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/model/enums) - `InboundEnvelope`:标准化后的入站消息 - `OutboundEnvelope`:标准化后的出站消息 @@ -303,8 +309,8 @@ 本地调试页相关文件: -- [src/main/java/com/jimuqu/claw/web/DebugChatController.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/web/DebugChatController.java) -- [src/main/java/com/jimuqu/claw/web/RootController.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/web/RootController.java) +- [src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java) +- [src/main/java/com/jimuqu/claw/web/controller/RootController.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/web/controller/RootController.java) - [src/main/resources/static/index.html](D:/IdeaProjects/SolonClaw/src/main/resources/static/index.html) 当前调试接口: @@ -477,7 +483,15 @@ java -jar target/solonclaw.jar --env=dev 7. 钉钉相关改动要同时考虑私聊、群聊、白名单、markdown 发送和回复路由 8. 工具、子任务、通知、定时任务都应复用统一运行时,不要平行造轮子 9. 工作区相关能力优先改 `AgentWorkspaceService / WorkspacePromptService / WorkspaceAgentTools` -10. Git 提交信息使用中英双语描述,推荐格式:`增加了xx功能 (Add xx feature)` +10. Git 提交信息默认使用 Conventional Commits 风格:`(): `;注意冒号 `:` 后必须有一个空格 +11. `scope` 选填,表示 commit 作用范围;可以写模块名、目录名,或数据层 / 视图层 / runtime / workspace 这类职责范围 +12. `subject` 必填,用于对 commit 做简短描述;默认继续使用中英双语描述,例如:`feat(agent): 增加了子任务聚合能力 (Add child-run aggregation)` +13. `type` 必填,可选值固定为:`feat` 新功能、`fix` 修复 bug、`docs` 文档注释、`style` 代码格式、`refactor` 重构优化、`perf` 性能优化、`test` 增加测试、`chore` 构建过程或辅助工具变动、`revert` 回退、`build` 打包 +14. 提交代码时,默认按职责拆分 commit;优先拆成“提示词与上下文 / 运行时治理 / 配置默认值与注释 / 测试”这类最小修改单元,尽量做到一个 commit 只解决一类问题,避免把无关改动混在一起 +15. 实体类、DTO、事件载荷、结果对象、配置承载对象这类数据类,优先使用 Lombok 管理字段访问器;明确适合的类优先使用 `@Data` +16. 无参构造优先交给 Lombok 管理;这类数据类默认优先使用 `@NoArgsConstructor`,不要继续手写大量空构造 +17. 需要持久化、序列化传输、缓存或作为稳定数据载体的类,应按需实现 `Serializable` +18. 不允许或尽量减少内部类的使用;尤其是配置承载对象,应优先拆成独立类,例如不要在 `SolonClawProperties` 中持续堆叠大量静态内部类 ## PR 规范 @@ -561,6 +575,6 @@ java -jar target/solonclaw.jar --env=dev - [src/main/java/com/jimuqu/claw/agent/job/WorkspaceJobService.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/agent/job/WorkspaceJobService.java) - [src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapter.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapter.java) - [src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSender.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSender.java) -- [src/main/java/com/jimuqu/claw/web/DebugChatController.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/web/DebugChatController.java) +- [src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java](D:/IdeaProjects/SolonClaw/src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java) - [src/main/resources/app.yml](D:/IdeaProjects/SolonClaw/src/main/resources/app.yml) - [scripts/config.example.yml](D:/IdeaProjects/SolonClaw/scripts/config.example.yml) diff --git a/README.en.md b/README.en.md index f692309f3f99aa34bedd294603d2cfdc949c4a65..3f589bcd03bb9aae5e482fdcc882423839278e75 100644 --- a/README.en.md +++ b/README.en.md @@ -381,6 +381,10 @@ The current test suite covers: - Prefer Solon lifecycle hooks for long-running resources - Add new project config under `SolonClawProperties` - Reuse the existing Debug Web entrypoint for local debugging +- Prefer Lombok-managed accessors for entity classes, DTOs, event payloads, result objects, and config carrier objects; use `@Data` where it is clearly appropriate +- Prefer Lombok-managed no-arg constructors for these data carriers, typically via `@NoArgsConstructor` +- Classes that need persistence, serialized transport, caching, or stable data-carrier semantics should implement `Serializable` when appropriate +- Avoid adding inner classes when possible; config carrier objects should preferably be extracted into standalone classes ## PR Guidelines @@ -419,6 +423,46 @@ Additional expectations: - PR titles and commit messages are encouraged to use bilingual Chinese/English wording in this repository. - If behavior or configuration changes, update the related docs in the same PR. +## Commit Guidelines + +Commit messages should follow the Conventional Commits style: + +```text +(): +``` + +Notes: + +- There must be one space after the colon `:` +- `scope` is optional and describes the affected area, such as a module name, directory name, or a responsibility like `runtime`, `workspace`, or `web` +- `subject` is required and should briefly describe the change; bilingual Chinese/English wording is recommended in this repository + +`type` is required and may be one of: + +- `feat`: new feature +- `fix`: bug fix +- `docs`: documentation changes +- `style`: formatting-only changes that do not affect runtime behavior +- `refactor`: refactoring or optimization that is neither a feature nor a bug fix +- `perf`: performance optimization +- `test`: test additions or adjustments +- `chore`: build process or auxiliary tooling changes +- `revert`: revert +- `build`: packaging/build changes + +Recommended examples: + +```text +feat(agent): 增加了子任务聚合能力 (Add child-run aggregation) +docs(readme): 补充了提交信息格式说明 (Add commit message format notes) +fix(runtime): 修复了 continuation 丢失问题 (Fix continuation dispatch issue) +``` + +Additional conventions: + +- Split commits by responsibility whenever possible, preferably into small units such as prompt/context, runtime governance, config defaults/comments, and tests +- Try to keep each commit focused on one kind of change instead of mixing unrelated work + ## AI-Assisted Development AI-assisted code and documentation authoring is allowed in this project. diff --git a/README.md b/README.md index 528c0cf797f3ae3f7929344eedc237b6894d2c5e..74250d56c1fbe7089d84f9ee643ad5086ac30f23 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,10 @@ environment: - 新增配置优先并入 `SolonClawProperties` - 调试能力优先复用现有 Debug Web - 不要把系统退回成全局串行队列 +- 实体类、DTO、事件载荷、结果对象、配置承载对象优先使用 Lombok 管理字段访问器;明确适合的类优先使用 `@Data` +- 无参构造优先交给 Lombok 管理,这类数据类默认优先使用 `@NoArgsConstructor` +- 需要持久化、序列化传输、缓存或作为稳定数据载体的类,应按需实现 `Serializable` +- 尽量避免新增内部类;配置承载对象优先拆成独立类维护 ## PR 规范 @@ -490,6 +494,46 @@ environment: - PR 标题与提交信息建议使用中英双语 - 改动涉及配置或行为变化时,要同步更新文档 +## Commit 规范 + +提交信息默认采用 Conventional Commits 风格: + +```text +(): +``` + +说明: + +- 冒号 `:` 后必须有一个空格 +- `scope` 选填,表示 commit 的作用范围,可以写模块名、目录名,或 `runtime`、`workspace`、`web` 这类职责范围 +- `subject` 必填,用于简短描述本次提交内容,建议使用中英双语 + +`type` 必填,允许值如下: + +- `feat`:新功能 feature +- `fix`:修复 bug +- `docs`:文档注释 +- `style`:代码格式(不影响代码运行的变动) +- `refactor`:重构、优化(既不增加新功能,也不是修复 bug) +- `perf`:性能优化 +- `test`:增加测试 +- `chore`:构建过程或辅助工具的变动 +- `revert`:回退 +- `build`:打包 + +推荐示例: + +```text +feat(agent): 增加了子任务聚合能力 (Add child-run aggregation) +docs(readme): 补充了提交信息格式说明 (Add commit message format notes) +fix(runtime): 修复了 continuation 丢失问题 (Fix continuation dispatch issue) +``` + +额外约定: + +- 默认按职责拆分 commit,优先拆成“提示词与上下文 / 运行时治理 / 配置默认值与注释 / 测试”这类最小修改单元 +- 尽量做到一个 commit 只解决一类问题,避免把无关改动混在一起 + ## AI 辅助开发说明 本项目允许使用 AI 辅助编写代码和文档。 diff --git a/scripts/config.example.yml b/scripts/config.example.yml index 57ca808fe3506c462d0da0fa74d7244a635fd0e9..377d71fe435648983caf35c767e88437931b8de8 100644 --- a/scripts/config.example.yml +++ b/scripts/config.example.yml @@ -153,6 +153,12 @@ solonclaw: # 说明:@skills 这类逻辑路径始终是只读挂载池,开关不会放开写入 sandboxMode: true + subtasks: + # 是否允许子任务继续派生新的子任务 + # true: 默认允许子任务继续拆分 + # false: 可用于收紧编排,避免出现多层失控扇出 + allowNestedSpawn: true + heartbeat: enabled: true intervalSeconds: 1800 diff --git a/src/main/java/com/jimuqu/claw/agent/channel/ChannelAdapter.java b/src/main/java/com/jimuqu/claw/agent/channel/ChannelAdapter.java index b11fb2d9bfae79cf8dd00b81b20c91d6e1276f72..17ec49089bc77622158446e9db7adeca3e033dc0 100644 --- a/src/main/java/com/jimuqu/claw/agent/channel/ChannelAdapter.java +++ b/src/main/java/com/jimuqu/claw/agent/channel/ChannelAdapter.java @@ -1,7 +1,7 @@ package com.jimuqu.claw.agent.channel; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.OutboundEnvelope; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; /** * 抽象统一的消息渠道适配器接口。 @@ -30,3 +30,4 @@ public interface ChannelAdapter { */ void send(OutboundEnvelope outboundEnvelope); } + diff --git a/src/main/java/com/jimuqu/claw/agent/channel/ChannelRegistry.java b/src/main/java/com/jimuqu/claw/agent/channel/ChannelRegistry.java index 9e299d90fbf69c7768b4f4b8ef2128e4b1fe0bff..343c9a91d9d5b705ea7f26a415ca7b4c29e3aa0d 100644 --- a/src/main/java/com/jimuqu/claw/agent/channel/ChannelRegistry.java +++ b/src/main/java/com/jimuqu/claw/agent/channel/ChannelRegistry.java @@ -1,7 +1,7 @@ package com.jimuqu.claw.agent.channel; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.OutboundEnvelope; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -48,3 +48,4 @@ public class ChannelRegistry { } } } + diff --git a/src/main/java/com/jimuqu/claw/agent/job/JobDefinition.java b/src/main/java/com/jimuqu/claw/agent/job/JobDefinition.java index 248c443a2d0d920286d6396dea40f7c14d4fd9b8..60fbdaaf32caebe74ea24b9c4efc13c700d04f97 100644 --- a/src/main/java/com/jimuqu/claw/agent/job/JobDefinition.java +++ b/src/main/java/com/jimuqu/claw/agent/job/JobDefinition.java @@ -1,11 +1,19 @@ package com.jimuqu.claw.agent.job; -import com.jimuqu.claw.agent.model.ReplyTarget; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; /** * 定义一个可持久化的定时任务。 */ -public class JobDefinition { +@Data +@NoArgsConstructor +public class JobDefinition implements Serializable { + private static final long serialVersionUID = 1L; + private String name; private String mode; private String scheduleValue; @@ -17,92 +25,4 @@ public class JobDefinition { private ReplyTarget replyTarget; private long createdAt; private long updatedAt; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getMode() { - return mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - public String getScheduleValue() { - return scheduleValue; - } - - public void setScheduleValue(String scheduleValue) { - this.scheduleValue = scheduleValue; - } - - public long getInitialDelay() { - return initialDelay; - } - - public void setInitialDelay(long initialDelay) { - this.initialDelay = initialDelay; - } - - public String getZone() { - return zone; - } - - public void setZone(String zone) { - this.zone = zone; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getPrompt() { - return prompt; - } - - public void setPrompt(String prompt) { - this.prompt = prompt; - } - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public ReplyTarget getReplyTarget() { - return replyTarget; - } - - public void setReplyTarget(ReplyTarget replyTarget) { - this.replyTarget = replyTarget; - } - - public long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(long createdAt) { - this.createdAt = createdAt; - } - - public long getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(long updatedAt) { - this.updatedAt = updatedAt; - } } diff --git a/src/main/java/com/jimuqu/claw/agent/job/WorkspaceJobService.java b/src/main/java/com/jimuqu/claw/agent/job/WorkspaceJobService.java index b057c8c32d172f3777bb63cc0eb77af3d4e3da0d..cba325d0073290f65b9c299f8c8051b5275f58d6 100644 --- a/src/main/java/com/jimuqu/claw/agent/job/WorkspaceJobService.java +++ b/src/main/java/com/jimuqu/claw/agent/job/WorkspaceJobService.java @@ -1,8 +1,8 @@ package com.jimuqu.claw.agent.job; import cn.hutool.core.util.StrUtil; -import com.jimuqu.claw.agent.model.LatestReplyRoute; -import com.jimuqu.claw.agent.model.ReplyTarget; +import com.jimuqu.claw.agent.model.route.LatestReplyRoute; +import com.jimuqu.claw.agent.model.route.ReplyTarget; import com.jimuqu.claw.agent.store.RuntimeStoreService; import org.noear.solon.scheduling.ScheduledAnno; import org.noear.solon.scheduling.ScheduledException; @@ -219,3 +219,4 @@ public class WorkspaceJobService { return "once_delay".equals(definition.getMode()); } } + diff --git a/src/main/java/com/jimuqu/claw/agent/model/AgentRun.java b/src/main/java/com/jimuqu/claw/agent/model/AgentRun.java deleted file mode 100644 index 25bfa9e2f1fe430a1ff6f388fdb5511fa01453bb..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/AgentRun.java +++ /dev/null @@ -1,327 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 描述单条入站消息触发的一次 Agent 执行任务。 - */ -public class AgentRun { - /** 运行任务唯一标识。 */ - private String runId; - /** 所属会话键。 */ - private String sessionKey; - /** 来源消息标识。 */ - private String sourceMessageId; - /** 来源用户消息版本号。 */ - private long sourceUserVersion; - /** 父运行任务标识;为空表示根运行。 */ - private String parentRunId; - /** 父运行所属会话键。 */ - private String parentSessionKey; - /** 父运行原路回复目标。 */ - private ReplyTarget parentReplyTarget; - /** 当前运行承载的任务描述。 */ - private String taskDescription; - /** 当前运行所属的子任务批次键。 */ - private String batchKey; - /** 原路回复目标。 */ - private ReplyTarget replyTarget; - /** 当前运行状态。 */ - private RunStatus status; - /** 创建时间戳。 */ - private long createdAt; - /** 开始执行时间戳。 */ - private long startedAt; - /** 完成时间戳。 */ - private long finishedAt; - /** 最终回复文本。 */ - private String finalResponse; - /** 错误信息。 */ - private String errorMessage; - - /** - * 返回运行任务标识。 - * - * @return 运行任务标识 - */ - public String getRunId() { - return runId; - } - - /** - * 设置运行任务标识。 - * - * @param runId 运行任务标识 - */ - public void setRunId(String runId) { - this.runId = runId; - } - - /** - * 返回会话键。 - * - * @return 会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置会话键。 - * - * @param sessionKey 会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回来源消息标识。 - * - * @return 来源消息标识 - */ - public String getSourceMessageId() { - return sourceMessageId; - } - - /** - * 设置来源消息标识。 - * - * @param sourceMessageId 来源消息标识 - */ - public void setSourceMessageId(String sourceMessageId) { - this.sourceMessageId = sourceMessageId; - } - - /** - * 返回来源用户消息版本号。 - * - * @return 来源用户消息版本号 - */ - public long getSourceUserVersion() { - return sourceUserVersion; - } - - /** - * 设置来源用户消息版本号。 - * - * @param sourceUserVersion 来源用户消息版本号 - */ - public void setSourceUserVersion(long sourceUserVersion) { - this.sourceUserVersion = sourceUserVersion; - } - - /** - * 返回父运行任务标识。 - * - * @return 父运行任务标识 - */ - public String getParentRunId() { - return parentRunId; - } - - /** - * 设置父运行任务标识。 - * - * @param parentRunId 父运行任务标识 - */ - public void setParentRunId(String parentRunId) { - this.parentRunId = parentRunId; - } - - /** - * 返回父运行所属会话键。 - * - * @return 父运行所属会话键 - */ - public String getParentSessionKey() { - return parentSessionKey; - } - - /** - * 设置父运行所属会话键。 - * - * @param parentSessionKey 父运行所属会话键 - */ - public void setParentSessionKey(String parentSessionKey) { - this.parentSessionKey = parentSessionKey; - } - - /** - * 返回父运行原路回复目标。 - * - * @return 父运行原路回复目标 - */ - public ReplyTarget getParentReplyTarget() { - return parentReplyTarget; - } - - /** - * 设置父运行原路回复目标。 - * - * @param parentReplyTarget 父运行原路回复目标 - */ - public void setParentReplyTarget(ReplyTarget parentReplyTarget) { - this.parentReplyTarget = parentReplyTarget; - } - - /** - * 返回当前运行承载的任务描述。 - * - * @return 当前运行任务描述 - */ - public String getTaskDescription() { - return taskDescription; - } - - /** - * 设置当前运行承载的任务描述。 - * - * @param taskDescription 当前运行任务描述 - */ - public void setTaskDescription(String taskDescription) { - this.taskDescription = taskDescription; - } - - /** - * 返回当前运行所属的子任务批次键。 - * - * @return 子任务批次键 - */ - public String getBatchKey() { - return batchKey; - } - - /** - * 设置当前运行所属的子任务批次键。 - * - * @param batchKey 子任务批次键 - */ - public void setBatchKey(String batchKey) { - this.batchKey = batchKey; - } - - /** - * 返回回复目标。 - * - * @return 回复目标 - */ - public ReplyTarget getReplyTarget() { - return replyTarget; - } - - /** - * 设置回复目标。 - * - * @param replyTarget 回复目标 - */ - public void setReplyTarget(ReplyTarget replyTarget) { - this.replyTarget = replyTarget; - } - - /** - * 返回当前状态。 - * - * @return 当前状态 - */ - public RunStatus getStatus() { - return status; - } - - /** - * 设置当前状态。 - * - * @param status 当前状态 - */ - public void setStatus(RunStatus status) { - this.status = status; - } - - /** - * 返回创建时间。 - * - * @return 创建时间 - */ - public long getCreatedAt() { - return createdAt; - } - - /** - * 设置创建时间。 - * - * @param createdAt 创建时间 - */ - public void setCreatedAt(long createdAt) { - this.createdAt = createdAt; - } - - /** - * 返回开始时间。 - * - * @return 开始时间 - */ - public long getStartedAt() { - return startedAt; - } - - /** - * 设置开始时间。 - * - * @param startedAt 开始时间 - */ - public void setStartedAt(long startedAt) { - this.startedAt = startedAt; - } - - /** - * 返回完成时间。 - * - * @return 完成时间 - */ - public long getFinishedAt() { - return finishedAt; - } - - /** - * 设置完成时间。 - * - * @param finishedAt 完成时间 - */ - public void setFinishedAt(long finishedAt) { - this.finishedAt = finishedAt; - } - - /** - * 返回最终回复文本。 - * - * @return 最终回复文本 - */ - public String getFinalResponse() { - return finalResponse; - } - - /** - * 设置最终回复文本。 - * - * @param finalResponse 最终回复文本 - */ - public void setFinalResponse(String finalResponse) { - this.finalResponse = finalResponse; - } - - /** - * 返回错误信息。 - * - * @return 错误信息 - */ - public String getErrorMessage() { - return errorMessage; - } - - /** - * 设置错误信息。 - * - * @param errorMessage 错误信息 - */ - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/AttachmentRef.java b/src/main/java/com/jimuqu/claw/agent/model/AttachmentRef.java deleted file mode 100644 index 8bb3a344f824537d74f20b76a8f8697afc049ad2..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/AttachmentRef.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 描述一条消息中保存到本地的附件引用信息。 - */ -public class AttachmentRef { - /** 附件类别,例如图片、音频或文件。 */ - private String type; - /** 附件展示名称。 */ - private String name; - /** 附件或附件元数据在本地的保存路径。 */ - private String path; - - /** - * 创建一个空的附件引用对象。 - */ - public AttachmentRef() { - } - - /** - * 按完整字段创建附件引用对象。 - * - * @param type 附件类别 - * @param name 附件名称 - * @param path 本地路径 - */ - public AttachmentRef(String type, String name, String path) { - this.type = type; - this.name = name; - this.path = path; - } - - /** - * 返回附件类别。 - * - * @return 附件类别 - */ - public String getType() { - return type; - } - - /** - * 设置附件类别。 - * - * @param type 附件类别 - */ - public void setType(String type) { - this.type = type; - } - - /** - * 返回附件名称。 - * - * @return 附件名称 - */ - public String getName() { - return name; - } - - /** - * 设置附件名称。 - * - * @param name 附件名称 - */ - public void setName(String name) { - this.name = name; - } - - /** - * 返回附件本地路径。 - * - * @return 本地路径 - */ - public String getPath() { - return path; - } - - /** - * 设置附件本地路径。 - * - * @param path 本地路径 - */ - public void setPath(String path) { - this.path = path; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/ChildRunCompletedData.java b/src/main/java/com/jimuqu/claw/agent/model/ChildRunCompletedData.java deleted file mode 100644 index 165ceb57d3ea5c2f85c783e13f05c72c47a311ad..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/ChildRunCompletedData.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 描述父会话中的子任务完成事件数据。 - */ -public class ChildRunCompletedData { - /** 父运行标识。 */ - private String parentRunId; - /** 子运行标识。 */ - private String childRunId; - /** 子会话键。 */ - private String childSessionKey; - /** 子任务状态。 */ - private String status; - /** 任务描述。 */ - private String taskDescription; - /** 子任务批次键。 */ - private String batchKey; - /** 子任务结果。 */ - private String result; - /** 子任务错误。 */ - private String errorMessage; - - public String getParentRunId() { - return parentRunId; - } - - public void setParentRunId(String parentRunId) { - this.parentRunId = parentRunId; - } - - public String getChildRunId() { - return childRunId; - } - - public void setChildRunId(String childRunId) { - this.childRunId = childRunId; - } - - public String getChildSessionKey() { - return childSessionKey; - } - - public void setChildSessionKey(String childSessionKey) { - this.childSessionKey = childSessionKey; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public String getTaskDescription() { - return taskDescription; - } - - public void setTaskDescription(String taskDescription) { - this.taskDescription = taskDescription; - } - - public String getBatchKey() { - return batchKey; - } - - public void setBatchKey(String batchKey) { - this.batchKey = batchKey; - } - - public String getResult() { - return result; - } - - public void setResult(String result) { - this.result = result; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/ChildRunSpawnedData.java b/src/main/java/com/jimuqu/claw/agent/model/ChildRunSpawnedData.java deleted file mode 100644 index e0342801803ce3cb59be0dddcf16049db2497600..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/ChildRunSpawnedData.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 描述父会话中的子任务创建事件数据。 - */ -public class ChildRunSpawnedData { - /** 父运行标识。 */ - private String parentRunId; - /** 子运行标识。 */ - private String childRunId; - /** 子会话键。 */ - private String childSessionKey; - /** 任务描述。 */ - private String taskDescription; - /** 子任务批次键。 */ - private String batchKey; - - public String getParentRunId() { - return parentRunId; - } - - public void setParentRunId(String parentRunId) { - this.parentRunId = parentRunId; - } - - public String getChildRunId() { - return childRunId; - } - - public void setChildRunId(String childRunId) { - this.childRunId = childRunId; - } - - public String getChildSessionKey() { - return childSessionKey; - } - - public void setChildSessionKey(String childSessionKey) { - this.childSessionKey = childSessionKey; - } - - public String getTaskDescription() { - return taskDescription; - } - - public void setTaskDescription(String taskDescription) { - this.taskDescription = taskDescription; - } - - public String getBatchKey() { - return batchKey; - } - - public void setBatchKey(String batchKey) { - this.batchKey = batchKey; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/ConversationEvent.java b/src/main/java/com/jimuqu/claw/agent/model/ConversationEvent.java deleted file mode 100644 index abfdc3852d169eb4b6afa35aaf19ef2fc9aeb773..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/ConversationEvent.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 表示会话历史中的单条事件记录。 - */ -public class ConversationEvent { - /** 事件版本号。 */ - private long version; - /** 所属会话键。 */ - private String sessionKey; - /** 事件类型。 */ - private String eventType; - /** 关联运行任务标识。 */ - private String runId; - /** 来源消息标识。 */ - private String sourceMessageId; - /** 来源用户消息对应的版本号。 */ - private long sourceUserVersion; - /** 事件角色,例如 user、assistant、system。 */ - private String role; - /** 事件文本内容。 */ - private String content; - /** 事件结构化数据的 JSON 表示。 */ - private String eventDataJson; - /** 事件创建时间戳。 */ - private long createdAt; - - /** - * 返回事件版本号。 - * - * @return 事件版本号 - */ - public long getVersion() { - return version; - } - - /** - * 设置事件版本号。 - * - * @param version 事件版本号 - */ - public void setVersion(long version) { - this.version = version; - } - - /** - * 返回会话键。 - * - * @return 会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置会话键。 - * - * @param sessionKey 会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回事件类型。 - * - * @return 事件类型 - */ - public String getEventType() { - return eventType; - } - - /** - * 设置事件类型。 - * - * @param eventType 事件类型 - */ - public void setEventType(String eventType) { - this.eventType = eventType; - } - - /** - * 返回运行任务标识。 - * - * @return 运行任务标识 - */ - public String getRunId() { - return runId; - } - - /** - * 设置运行任务标识。 - * - * @param runId 运行任务标识 - */ - public void setRunId(String runId) { - this.runId = runId; - } - - /** - * 返回来源消息标识。 - * - * @return 来源消息标识 - */ - public String getSourceMessageId() { - return sourceMessageId; - } - - /** - * 设置来源消息标识。 - * - * @param sourceMessageId 来源消息标识 - */ - public void setSourceMessageId(String sourceMessageId) { - this.sourceMessageId = sourceMessageId; - } - - /** - * 返回来源用户消息版本号。 - * - * @return 来源用户消息版本号 - */ - public long getSourceUserVersion() { - return sourceUserVersion; - } - - /** - * 设置来源用户消息版本号。 - * - * @param sourceUserVersion 来源用户消息版本号 - */ - public void setSourceUserVersion(long sourceUserVersion) { - this.sourceUserVersion = sourceUserVersion; - } - - /** - * 返回事件角色。 - * - * @return 事件角色 - */ - public String getRole() { - return role; - } - - /** - * 设置事件角色。 - * - * @param role 事件角色 - */ - public void setRole(String role) { - this.role = role; - } - - /** - * 返回事件内容。 - * - * @return 事件内容 - */ - public String getContent() { - return content; - } - - /** - * 设置事件内容。 - * - * @param content 事件内容 - */ - public void setContent(String content) { - this.content = content; - } - - /** - * 返回事件结构化数据 JSON。 - * - * @return 事件结构化数据 JSON - */ - public String getEventDataJson() { - return eventDataJson; - } - - /** - * 设置事件结构化数据 JSON。 - * - * @param eventDataJson 事件结构化数据 JSON - */ - public void setEventDataJson(String eventDataJson) { - this.eventDataJson = eventDataJson; - } - - /** - * 返回事件创建时间。 - * - * @return 事件创建时间 - */ - public long getCreatedAt() { - return createdAt; - } - - /** - * 设置事件创建时间。 - * - * @param createdAt 事件创建时间 - */ - public void setCreatedAt(long createdAt) { - this.createdAt = createdAt; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/InboundEnvelope.java b/src/main/java/com/jimuqu/claw/agent/model/InboundEnvelope.java deleted file mode 100644 index 035b7f349a45c1ae0c4792ec7ef13967edc2710e..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/InboundEnvelope.java +++ /dev/null @@ -1,350 +0,0 @@ -package com.jimuqu.claw.agent.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * 统一描述外部或内部入站消息的标准信封对象。 - */ -public class InboundEnvelope { - /** 上游消息唯一标识。 */ - private String messageId; - /** 入站消息来源渠道。 */ - private ChannelType channelType; - /** 渠道实例标识,用于多实例扩展。 */ - private String channelInstanceId; - /** 发送者标识。 */ - private String senderId; - /** 会话标识。 */ - private String conversationId; - /** 会话类型。 */ - private ConversationType conversationType; - /** 文本内容。 */ - private String content; - /** 附件引用列表。 */ - private List attachments = new ArrayList<>(); - /** 原路回复目标。 */ - private ReplyTarget replyTarget; - /** 接收时间戳。 */ - private long receivedAt; - /** 运行时内部会话键。 */ - private String sessionKey; - /** 会话事件版本号。 */ - private long sessionVersion; - /** 当前入站触发类型。 */ - private InboundTriggerType triggerType = InboundTriggerType.USER; - /** 当前运行关联到的历史锚点版本。 */ - private long historyAnchorVersion; - /** 是否允许将最终回复回发到外部渠道。 */ - private boolean externalReplyEnabled = true; - /** 是否将当前入站消息写入会话历史。 */ - private boolean persistInboundConversationEvent = true; - /** 是否将本次运行产生的助手回复写入会话历史。 */ - private boolean persistAssistantConversationEvent = true; - - /** - * 返回消息唯一标识。 - * - * @return 消息唯一标识 - */ - public String getMessageId() { - return messageId; - } - - /** - * 设置消息唯一标识。 - * - * @param messageId 消息唯一标识 - */ - public void setMessageId(String messageId) { - this.messageId = messageId; - } - - /** - * 返回渠道类型。 - * - * @return 渠道类型 - */ - public ChannelType getChannelType() { - return channelType; - } - - /** - * 设置渠道类型。 - * - * @param channelType 渠道类型 - */ - public void setChannelType(ChannelType channelType) { - this.channelType = channelType; - } - - /** - * 返回渠道实例标识。 - * - * @return 渠道实例标识 - */ - public String getChannelInstanceId() { - return channelInstanceId; - } - - /** - * 设置渠道实例标识。 - * - * @param channelInstanceId 渠道实例标识 - */ - public void setChannelInstanceId(String channelInstanceId) { - this.channelInstanceId = channelInstanceId; - } - - /** - * 返回发送者标识。 - * - * @return 发送者标识 - */ - public String getSenderId() { - return senderId; - } - - /** - * 设置发送者标识。 - * - * @param senderId 发送者标识 - */ - public void setSenderId(String senderId) { - this.senderId = senderId; - } - - /** - * 返回会话标识。 - * - * @return 会话标识 - */ - public String getConversationId() { - return conversationId; - } - - /** - * 设置会话标识。 - * - * @param conversationId 会话标识 - */ - public void setConversationId(String conversationId) { - this.conversationId = conversationId; - } - - /** - * 返回会话类型。 - * - * @return 会话类型 - */ - public ConversationType getConversationType() { - return conversationType; - } - - /** - * 设置会话类型。 - * - * @param conversationType 会话类型 - */ - public void setConversationType(ConversationType conversationType) { - this.conversationType = conversationType; - } - - /** - * 返回消息文本内容。 - * - * @return 文本内容 - */ - public String getContent() { - return content; - } - - /** - * 设置消息文本内容。 - * - * @param content 文本内容 - */ - public void setContent(String content) { - this.content = content; - } - - /** - * 返回附件引用列表。 - * - * @return 附件引用列表 - */ - public List getAttachments() { - return attachments; - } - - /** - * 设置附件引用列表。 - * - * @param attachments 附件引用列表 - */ - public void setAttachments(List attachments) { - this.attachments = attachments; - } - - /** - * 返回原路回复目标。 - * - * @return 原路回复目标 - */ - public ReplyTarget getReplyTarget() { - return replyTarget; - } - - /** - * 设置原路回复目标。 - * - * @param replyTarget 原路回复目标 - */ - public void setReplyTarget(ReplyTarget replyTarget) { - this.replyTarget = replyTarget; - } - - /** - * 返回接收时间戳。 - * - * @return 接收时间戳 - */ - public long getReceivedAt() { - return receivedAt; - } - - /** - * 设置接收时间戳。 - * - * @param receivedAt 接收时间戳 - */ - public void setReceivedAt(long receivedAt) { - this.receivedAt = receivedAt; - } - - /** - * 返回内部会话键。 - * - * @return 内部会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置内部会话键。 - * - * @param sessionKey 内部会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回会话版本号。 - * - * @return 会话版本号 - */ - public long getSessionVersion() { - return sessionVersion; - } - - /** - * 设置会话版本号。 - * - * @param sessionVersion 会话版本号 - */ - public void setSessionVersion(long sessionVersion) { - this.sessionVersion = sessionVersion; - } - - /** - * 返回当前入站触发类型。 - * - * @return 入站触发类型 - */ - public InboundTriggerType getTriggerType() { - return triggerType; - } - - /** - * 设置当前入站触发类型。 - * - * @param triggerType 入站触发类型 - */ - public void setTriggerType(InboundTriggerType triggerType) { - this.triggerType = triggerType; - } - - /** - * 返回历史锚点版本。 - * - * @return 历史锚点版本 - */ - public long getHistoryAnchorVersion() { - return historyAnchorVersion; - } - - /** - * 设置历史锚点版本。 - * - * @param historyAnchorVersion 历史锚点版本 - */ - public void setHistoryAnchorVersion(long historyAnchorVersion) { - this.historyAnchorVersion = historyAnchorVersion; - } - - /** - * 返回是否允许外部回发。 - * - * @return 若允许外部回发则返回 true - */ - public boolean isExternalReplyEnabled() { - return externalReplyEnabled; - } - - /** - * 设置是否允许外部回发。 - * - * @param externalReplyEnabled 外部回发标记 - */ - public void setExternalReplyEnabled(boolean externalReplyEnabled) { - this.externalReplyEnabled = externalReplyEnabled; - } - - /** - * 返回是否持久化当前入站事件。 - * - * @return 若持久化则返回 true - */ - public boolean isPersistInboundConversationEvent() { - return persistInboundConversationEvent; - } - - /** - * 设置是否持久化当前入站事件。 - * - * @param persistInboundConversationEvent 入站事件持久化标记 - */ - public void setPersistInboundConversationEvent(boolean persistInboundConversationEvent) { - this.persistInboundConversationEvent = persistInboundConversationEvent; - } - - /** - * 返回是否持久化助手回复事件。 - * - * @return 若持久化则返回 true - */ - public boolean isPersistAssistantConversationEvent() { - return persistAssistantConversationEvent; - } - - /** - * 设置是否持久化助手回复事件。 - * - * @param persistAssistantConversationEvent 助手回复事件持久化标记 - */ - public void setPersistAssistantConversationEvent(boolean persistAssistantConversationEvent) { - this.persistAssistantConversationEvent = persistAssistantConversationEvent; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/LatestReplyRoute.java b/src/main/java/com/jimuqu/claw/agent/model/LatestReplyRoute.java deleted file mode 100644 index aa81041bdb60063363e666b85b0ddd91d0e93060..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/LatestReplyRoute.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 保存最近一次可用于外发的会话路由信息。 - */ -public class LatestReplyRoute { - /** 最近一次路由命中的内部会话键。 */ - private String sessionKey; - /** 最近一次可外发的回复目标。 */ - private ReplyTarget replyTarget; - - /** - * 返回内部会话键。 - * - * @return 内部会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置内部会话键。 - * - * @param sessionKey 内部会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回回复目标。 - * - * @return 回复目标 - */ - public ReplyTarget getReplyTarget() { - return replyTarget; - } - - /** - * 设置回复目标。 - * - * @param replyTarget 回复目标 - */ - public void setReplyTarget(ReplyTarget replyTarget) { - this.replyTarget = replyTarget; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/OutboundEnvelope.java b/src/main/java/com/jimuqu/claw/agent/model/OutboundEnvelope.java deleted file mode 100644 index a4abc23c73baf3fa6f7c6734cb65d15c0332c66d..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/OutboundEnvelope.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.jimuqu.claw.agent.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * 统一描述要发送到外部渠道的出站消息。 - */ -public class OutboundEnvelope { - /** 所属运行任务标识。 */ - private String runId; - /** 实际回复目标。 */ - private ReplyTarget replyTarget; - /** 文本内容。 */ - private String content; - /** 附件或媒体路径列表。 */ - private List media = new ArrayList<>(); - /** 是否为进度消息。 */ - private boolean progress; - - /** - * 返回运行任务标识。 - * - * @return 运行任务标识 - */ - public String getRunId() { - return runId; - } - - /** - * 设置运行任务标识。 - * - * @param runId 运行任务标识 - */ - public void setRunId(String runId) { - this.runId = runId; - } - - /** - * 返回回复目标。 - * - * @return 回复目标 - */ - public ReplyTarget getReplyTarget() { - return replyTarget; - } - - /** - * 设置回复目标。 - * - * @param replyTarget 回复目标 - */ - public void setReplyTarget(ReplyTarget replyTarget) { - this.replyTarget = replyTarget; - } - - /** - * 返回文本内容。 - * - * @return 文本内容 - */ - public String getContent() { - return content; - } - - /** - * 设置文本内容。 - * - * @param content 文本内容 - */ - public void setContent(String content) { - this.content = content; - } - - /** - * 返回媒体列表。 - * - * @return 媒体列表 - */ - public List getMedia() { - return media; - } - - /** - * 设置媒体列表。 - * - * @param media 媒体列表 - */ - public void setMedia(List media) { - this.media = media; - } - - /** - * 判断当前消息是否为进度消息。 - * - * @return 若为进度消息则返回 true - */ - public boolean isProgress() { - return progress; - } - - /** - * 设置是否为进度消息。 - * - * @param progress 进度标记 - */ - public void setProgress(boolean progress) { - this.progress = progress; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/RunEvent.java b/src/main/java/com/jimuqu/claw/agent/model/RunEvent.java deleted file mode 100644 index cfdd36b259abef5e773b88f04539ee7856280fb1..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/model/RunEvent.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.jimuqu.claw.agent.model; - -/** - * 表示一次运行任务在执行过程中的事件。 - */ -public class RunEvent { - /** 事件序号。 */ - private long seq; - /** 所属运行任务标识。 */ - private String runId; - /** 事件类型。 */ - private String eventType; - /** 事件消息文本。 */ - private String message; - /** 事件创建时间戳。 */ - private long createdAt; - - /** - * 返回事件序号。 - * - * @return 事件序号 - */ - public long getSeq() { - return seq; - } - - /** - * 设置事件序号。 - * - * @param seq 事件序号 - */ - public void setSeq(long seq) { - this.seq = seq; - } - - /** - * 返回运行任务标识。 - * - * @return 运行任务标识 - */ - public String getRunId() { - return runId; - } - - /** - * 设置运行任务标识。 - * - * @param runId 运行任务标识 - */ - public void setRunId(String runId) { - this.runId = runId; - } - - /** - * 返回事件类型。 - * - * @return 事件类型 - */ - public String getEventType() { - return eventType; - } - - /** - * 设置事件类型。 - * - * @param eventType 事件类型 - */ - public void setEventType(String eventType) { - this.eventType = eventType; - } - - /** - * 返回事件消息文本。 - * - * @return 事件消息文本 - */ - public String getMessage() { - return message; - } - - /** - * 设置事件消息文本。 - * - * @param message 事件消息文本 - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * 返回事件创建时间。 - * - * @return 事件创建时间 - */ - public long getCreatedAt() { - return createdAt; - } - - /** - * 设置事件创建时间。 - * - * @param createdAt 事件创建时间 - */ - public void setCreatedAt(long createdAt) { - this.createdAt = createdAt; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/model/ChannelType.java b/src/main/java/com/jimuqu/claw/agent/model/enums/ChannelType.java similarity index 86% rename from src/main/java/com/jimuqu/claw/agent/model/ChannelType.java rename to src/main/java/com/jimuqu/claw/agent/model/enums/ChannelType.java index f3185c0a11de8f56844bb6dfe8cc1a12a35ad594..8aad3fe326ad4b45285ae66018580816d3ce8a27 100644 --- a/src/main/java/com/jimuqu/claw/agent/model/ChannelType.java +++ b/src/main/java/com/jimuqu/claw/agent/model/enums/ChannelType.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.agent.model; +package com.jimuqu.claw.agent.model.enums; /** * 定义运行时支持的消息来源渠道类型。 @@ -13,3 +13,4 @@ public enum ChannelType { /** 系统内部触发渠道。 */ SYSTEM } + diff --git a/src/main/java/com/jimuqu/claw/agent/model/ConversationType.java b/src/main/java/com/jimuqu/claw/agent/model/enums/ConversationType.java similarity index 83% rename from src/main/java/com/jimuqu/claw/agent/model/ConversationType.java rename to src/main/java/com/jimuqu/claw/agent/model/enums/ConversationType.java index aa7e2bceee98372f1fad446247bdc437f722aa84..92577b57368353724103d7cc238504869da477a6 100644 --- a/src/main/java/com/jimuqu/claw/agent/model/ConversationType.java +++ b/src/main/java/com/jimuqu/claw/agent/model/enums/ConversationType.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.agent.model; +package com.jimuqu.claw.agent.model.enums; /** * 定义会话所在的上下文类型。 @@ -11,3 +11,4 @@ public enum ConversationType { /** 系统内部会话。 */ SYSTEM } + diff --git a/src/main/java/com/jimuqu/claw/agent/model/InboundTriggerType.java b/src/main/java/com/jimuqu/claw/agent/model/enums/InboundTriggerType.java similarity index 88% rename from src/main/java/com/jimuqu/claw/agent/model/InboundTriggerType.java rename to src/main/java/com/jimuqu/claw/agent/model/enums/InboundTriggerType.java index 4544c9c8806768bfffdcf79356cc3c5221543217..b06a42028642efc19bf7ec09105c790c7dc4ff5d 100644 --- a/src/main/java/com/jimuqu/claw/agent/model/InboundTriggerType.java +++ b/src/main/java/com/jimuqu/claw/agent/model/enums/InboundTriggerType.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.agent.model; +package com.jimuqu.claw.agent.model.enums; /** * 描述一次入站触发在运行时中的语义类型。 @@ -11,3 +11,4 @@ public enum InboundTriggerType { /** 仅用于内部检查的静默系统触发。 */ SYSTEM_SILENT } + diff --git a/src/main/java/com/jimuqu/claw/agent/model/RunStatus.java b/src/main/java/com/jimuqu/claw/agent/model/enums/RunStatus.java similarity index 92% rename from src/main/java/com/jimuqu/claw/agent/model/RunStatus.java rename to src/main/java/com/jimuqu/claw/agent/model/enums/RunStatus.java index 90b903489775a7bf6a6c00c50b4831eda06945f2..f316e3ca98588a97bfd7cbe6e1fb851018c80360 100644 --- a/src/main/java/com/jimuqu/claw/agent/model/RunStatus.java +++ b/src/main/java/com/jimuqu/claw/agent/model/enums/RunStatus.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.agent.model; +package com.jimuqu.claw.agent.model.enums; /** * 定义单次 Agent 运行任务的状态枚举。 @@ -19,3 +19,4 @@ public enum RunStatus { /** 任务因进程中断等原因被异常终止。 */ ABORTED } + diff --git a/src/main/java/com/jimuqu/claw/agent/model/envelope/AttachmentRef.java b/src/main/java/com/jimuqu/claw/agent/model/envelope/AttachmentRef.java new file mode 100644 index 0000000000000000000000000000000000000000..0f89a818a02889cb9817e3f18aca36adb45c2195 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/envelope/AttachmentRef.java @@ -0,0 +1,24 @@ +package com.jimuqu.claw.agent.model.envelope; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述一条消息中保存到本地的附件引用信息。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AttachmentRef implements Serializable { + private static final long serialVersionUID = 1L; + + /** 附件类别,例如图片、音频或文件。 */ + private String type; + /** 附件展示名称。 */ + private String name; + /** 附件或附件元数据在本地的保存路径。 */ + private String path; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/envelope/InboundEnvelope.java b/src/main/java/com/jimuqu/claw/agent/model/envelope/InboundEnvelope.java new file mode 100644 index 0000000000000000000000000000000000000000..000bf24ce7dd19f57e97375f98fe28f0501192a0 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/envelope/InboundEnvelope.java @@ -0,0 +1,56 @@ +package com.jimuqu.claw.agent.model.envelope; + +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 统一描述外部或内部入站消息的标准信封对象。 + */ +@Data +@NoArgsConstructor +public class InboundEnvelope implements Serializable { + private static final long serialVersionUID = 1L; + + /** 上游消息唯一标识。 */ + private String messageId; + /** 入站消息来源渠道。 */ + private ChannelType channelType; + /** 渠道实例标识,用于多实例扩展。 */ + private String channelInstanceId; + /** 发送者标识。 */ + private String senderId; + /** 会话标识。 */ + private String conversationId; + /** 会话类型。 */ + private ConversationType conversationType; + /** 文本内容。 */ + private String content; + /** 附件引用列表。 */ + private List attachments = new ArrayList(); + /** 原路回复目标。 */ + private ReplyTarget replyTarget; + /** 接收时间戳。 */ + private long receivedAt; + /** 运行时内部会话键。 */ + private String sessionKey; + /** 会话事件版本号。 */ + private long sessionVersion; + /** 当前入站触发类型。 */ + private InboundTriggerType triggerType = InboundTriggerType.USER; + /** 当前运行关联到的历史锚点版本。 */ + private long historyAnchorVersion; + /** 是否允许将最终回复回发到外部渠道。 */ + private boolean externalReplyEnabled = true; + /** 是否将当前入站消息写入会话历史。 */ + private boolean persistInboundConversationEvent = true; + /** 是否将本次运行产生的助手回复写入会话历史。 */ + private boolean persistAssistantConversationEvent = true; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/envelope/OutboundEnvelope.java b/src/main/java/com/jimuqu/claw/agent/model/envelope/OutboundEnvelope.java new file mode 100644 index 0000000000000000000000000000000000000000..874d0a2c5d936332e94c13f8eab5820e11f3ed85 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/envelope/OutboundEnvelope.java @@ -0,0 +1,29 @@ +package com.jimuqu.claw.agent.model.envelope; + +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 统一描述要发送到外部渠道的出站消息。 + */ +@Data +@NoArgsConstructor +public class OutboundEnvelope implements Serializable { + private static final long serialVersionUID = 1L; + + /** 所属运行任务标识。 */ + private String runId; + /** 实际回复目标。 */ + private ReplyTarget replyTarget; + /** 文本内容。 */ + private String content; + /** 附件或媒体路径列表。 */ + private List media = new ArrayList(); + /** 是否为进度消息。 */ + private boolean progress; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/event/ChildRunCompletedData.java b/src/main/java/com/jimuqu/claw/agent/model/event/ChildRunCompletedData.java new file mode 100644 index 0000000000000000000000000000000000000000..211eba784ccceac936fe8680ff5cc0da944da47a --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/event/ChildRunCompletedData.java @@ -0,0 +1,32 @@ +package com.jimuqu.claw.agent.model.event; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述父会话中的子任务完成事件数据。 + */ +@Data +@NoArgsConstructor +public class ChildRunCompletedData implements Serializable { + private static final long serialVersionUID = 1L; + + /** 父运行标识。 */ + private String parentRunId; + /** 子运行标识。 */ + private String childRunId; + /** 子会话键。 */ + private String childSessionKey; + /** 子任务状态。 */ + private String status; + /** 任务描述。 */ + private String taskDescription; + /** 子任务批次键。 */ + private String batchKey; + /** 子任务结果。 */ + private String result; + /** 子任务错误。 */ + private String errorMessage; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/event/ChildRunSpawnedData.java b/src/main/java/com/jimuqu/claw/agent/model/event/ChildRunSpawnedData.java new file mode 100644 index 0000000000000000000000000000000000000000..8c4895d103663f1168620dab87dc619044a001b3 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/event/ChildRunSpawnedData.java @@ -0,0 +1,26 @@ +package com.jimuqu.claw.agent.model.event; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述父会话中的子任务创建事件数据。 + */ +@Data +@NoArgsConstructor +public class ChildRunSpawnedData implements Serializable { + private static final long serialVersionUID = 1L; + + /** 父运行标识。 */ + private String parentRunId; + /** 子运行标识。 */ + private String childRunId; + /** 子会话键。 */ + private String childSessionKey; + /** 任务描述。 */ + private String taskDescription; + /** 子任务批次键。 */ + private String batchKey; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/event/ConversationEvent.java b/src/main/java/com/jimuqu/claw/agent/model/event/ConversationEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..b3dfd1e5314b4340b2b719c3558c749b5bfb766d --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/event/ConversationEvent.java @@ -0,0 +1,36 @@ +package com.jimuqu.claw.agent.model.event; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 表示会话历史中的单条事件记录。 + */ +@Data +@NoArgsConstructor +public class ConversationEvent implements Serializable { + private static final long serialVersionUID = 1L; + + /** 事件版本号。 */ + private long version; + /** 所属会话键。 */ + private String sessionKey; + /** 事件类型。 */ + private String eventType; + /** 关联运行任务标识。 */ + private String runId; + /** 来源消息标识。 */ + private String sourceMessageId; + /** 来源用户消息对应的版本号。 */ + private long sourceUserVersion; + /** 事件角色,例如 user、assistant、system。 */ + private String role; + /** 事件文本内容。 */ + private String content; + /** 事件结构化数据的 JSON 表示。 */ + private String eventDataJson; + /** 事件创建时间戳。 */ + private long createdAt; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/event/RunEvent.java b/src/main/java/com/jimuqu/claw/agent/model/event/RunEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..e9da7009f33354e397bf059803d1235bdc5c33be --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/event/RunEvent.java @@ -0,0 +1,26 @@ +package com.jimuqu.claw.agent.model.event; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 表示一次运行任务在执行过程中的事件。 + */ +@Data +@NoArgsConstructor +public class RunEvent implements Serializable { + private static final long serialVersionUID = 1L; + + /** 事件序号。 */ + private long seq; + /** 所属运行任务标识。 */ + private String runId; + /** 事件类型。 */ + private String eventType; + /** 事件消息文本。 */ + private String message; + /** 事件创建时间戳。 */ + private long createdAt; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/route/LatestReplyRoute.java b/src/main/java/com/jimuqu/claw/agent/model/route/LatestReplyRoute.java new file mode 100644 index 0000000000000000000000000000000000000000..236a4193ad608e9d84b162e03282f2ef8385b86b --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/route/LatestReplyRoute.java @@ -0,0 +1,20 @@ +package com.jimuqu.claw.agent.model.route; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 保存最近一次可用于外发的会话路由信息。 + */ +@Data +@NoArgsConstructor +public class LatestReplyRoute implements Serializable { + private static final long serialVersionUID = 1L; + + /** 最近一次路由命中的内部会话键。 */ + private String sessionKey; + /** 最近一次可外发的回复目标。 */ + private ReplyTarget replyTarget; +} diff --git a/src/main/java/com/jimuqu/claw/agent/model/ReplyTarget.java b/src/main/java/com/jimuqu/claw/agent/model/route/ReplyTarget.java similarity index 35% rename from src/main/java/com/jimuqu/claw/agent/model/ReplyTarget.java rename to src/main/java/com/jimuqu/claw/agent/model/route/ReplyTarget.java index ace23e2ee832f422dcfda58ca2d600ac8e5804ae..ecbba4171fa3d203dc46f6572a4cb9ade6daabfe 100644 --- a/src/main/java/com/jimuqu/claw/agent/model/ReplyTarget.java +++ b/src/main/java/com/jimuqu/claw/agent/model/route/ReplyTarget.java @@ -1,9 +1,20 @@ -package com.jimuqu.claw.agent.model; +package com.jimuqu.claw.agent.model.route; + +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; /** * 描述一条回复应投递到的目标位置。 */ -public class ReplyTarget { +@Data +@NoArgsConstructor +public class ReplyTarget implements Serializable { + private static final long serialVersionUID = 1L; + /** 目标所属渠道。 */ private ChannelType channelType; /** 目标所属会话类型。 */ @@ -17,12 +28,6 @@ public class ReplyTarget { /** 历史兼容保留的 sessionWebhook 过期时间。 */ private Long sessionWebhookExpiredAt; - /** - * 创建一个空的回复目标。 - */ - public ReplyTarget() { - } - /** * 按关键字段创建回复目标。 * @@ -38,114 +43,6 @@ public class ReplyTarget { this.userId = userId; } - /** - * 返回渠道类型。 - * - * @return 渠道类型 - */ - public ChannelType getChannelType() { - return channelType; - } - - /** - * 设置渠道类型。 - * - * @param channelType 渠道类型 - */ - public void setChannelType(ChannelType channelType) { - this.channelType = channelType; - } - - /** - * 返回会话类型。 - * - * @return 会话类型 - */ - public ConversationType getConversationType() { - return conversationType; - } - - /** - * 设置会话类型。 - * - * @param conversationType 会话类型 - */ - public void setConversationType(ConversationType conversationType) { - this.conversationType = conversationType; - } - - /** - * 返回会话标识。 - * - * @return 会话标识 - */ - public String getConversationId() { - return conversationId; - } - - /** - * 设置会话标识。 - * - * @param conversationId 会话标识 - */ - public void setConversationId(String conversationId) { - this.conversationId = conversationId; - } - - /** - * 返回用户标识。 - * - * @return 用户标识 - */ - public String getUserId() { - return userId; - } - - /** - * 设置用户标识。 - * - * @param userId 用户标识 - */ - public void setUserId(String userId) { - this.userId = userId; - } - - /** - * 返回兼容字段 sessionWebhook。 - * - * @return sessionWebhook - */ - public String getSessionWebhook() { - return sessionWebhook; - } - - /** - * 设置兼容字段 sessionWebhook。 - * - * @param sessionWebhook sessionWebhook - */ - public void setSessionWebhook(String sessionWebhook) { - this.sessionWebhook = sessionWebhook; - } - - /** - * 返回 sessionWebhook 过期时间。 - * - * @return 过期时间戳 - */ - public Long getSessionWebhookExpiredAt() { - return sessionWebhookExpiredAt; - } - - /** - * 设置 sessionWebhook 过期时间。 - * - * @param sessionWebhookExpiredAt 过期时间戳 - */ - public void setSessionWebhookExpiredAt(Long sessionWebhookExpiredAt) { - this.sessionWebhookExpiredAt = sessionWebhookExpiredAt; - } - /** * 判断当前目标是否属于调试页渠道。 * diff --git a/src/main/java/com/jimuqu/claw/agent/model/run/AgentRun.java b/src/main/java/com/jimuqu/claw/agent/model/run/AgentRun.java new file mode 100644 index 0000000000000000000000000000000000000000..f91c945042b2a24bfa8ae8ae8127f5d3972cec91 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/model/run/AgentRun.java @@ -0,0 +1,50 @@ +package com.jimuqu.claw.agent.model.run; + +import com.jimuqu.claw.agent.model.enums.RunStatus; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述单条入站消息触发的一次 Agent 执行任务。 + */ +@Data +@NoArgsConstructor +public class AgentRun implements Serializable { + private static final long serialVersionUID = 1L; + + /** 运行任务唯一标识。 */ + private String runId; + /** 所属会话键。 */ + private String sessionKey; + /** 来源消息标识。 */ + private String sourceMessageId; + /** 来源用户消息版本号。 */ + private long sourceUserVersion; + /** 父运行任务标识;为空表示根运行。 */ + private String parentRunId; + /** 父运行所属会话键。 */ + private String parentSessionKey; + /** 父运行原路回复目标。 */ + private ReplyTarget parentReplyTarget; + /** 当前运行承载的任务描述。 */ + private String taskDescription; + /** 当前运行所属的子任务批次键。 */ + private String batchKey; + /** 原路回复目标。 */ + private ReplyTarget replyTarget; + /** 当前运行状态。 */ + private RunStatus status; + /** 创建时间戳。 */ + private long createdAt; + /** 开始执行时间戳。 */ + private long startedAt; + /** 完成时间戳。 */ + private long finishedAt; + /** 最终回复文本。 */ + private String finalResponse; + /** 错误信息。 */ + private String errorMessage; +} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/ConversationExecutionRequest.java b/src/main/java/com/jimuqu/claw/agent/runtime/ConversationExecutionRequest.java deleted file mode 100644 index df90b329a34ea8d73aa45515f3d1c155b964bf30..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/runtime/ConversationExecutionRequest.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.jimuqu.claw.agent.runtime; - -import com.jimuqu.claw.agent.model.InboundTriggerType; -import org.noear.solon.ai.chat.message.ChatMessage; - -import java.util.ArrayList; -import java.util.List; - -/** - * 描述一次会话执行所需的上下文输入。 - */ -public class ConversationExecutionRequest { - /** 当前会话对应的内部键。 */ - private String sessionKey; - /** 当前待处理的用户消息。 */ - private String currentMessage; - /** 当前消息的触发类型。 */ - private InboundTriggerType currentMessageTriggerType = InboundTriggerType.USER; - /** 历史消息列表。 */ - private List history = new ArrayList<>(); - /** 当前运行可用的子任务派生能力。 */ - private SpawnTaskSupport spawnTaskSupport; - /** 当前运行可用的任务状态查询能力。 */ - private RunQuerySupport runQuerySupport; - /** 当前运行可用的主动通知能力。 */ - private NotificationSupport notificationSupport; - - /** - * 返回会话键。 - * - * @return 会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置会话键。 - * - * @param sessionKey 会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回当前消息。 - * - * @return 当前消息 - */ - public String getCurrentMessage() { - return currentMessage; - } - - /** - * 设置当前消息。 - * - * @param currentMessage 当前消息 - */ - public void setCurrentMessage(String currentMessage) { - this.currentMessage = currentMessage; - } - - /** - * 返回当前消息的触发类型。 - * - * @return 触发类型 - */ - public InboundTriggerType getCurrentMessageTriggerType() { - return currentMessageTriggerType; - } - - /** - * 设置当前消息的触发类型。 - * - * @param currentMessageTriggerType 触发类型 - */ - public void setCurrentMessageTriggerType(InboundTriggerType currentMessageTriggerType) { - this.currentMessageTriggerType = currentMessageTriggerType; - } - - /** - * 返回历史消息列表。 - * - * @return 历史消息列表 - */ - public List getHistory() { - return history; - } - - /** - * 设置历史消息列表。 - * - * @param history 历史消息列表 - */ - public void setHistory(List history) { - this.history = history; - } - - /** - * 返回当前运行可用的子任务派生能力。 - * - * @return 子任务派生能力 - */ - public SpawnTaskSupport getSpawnTaskSupport() { - return spawnTaskSupport; - } - - /** - * 设置当前运行可用的子任务派生能力。 - * - * @param spawnTaskSupport 子任务派生能力 - */ - public void setSpawnTaskSupport(SpawnTaskSupport spawnTaskSupport) { - this.spawnTaskSupport = spawnTaskSupport; - } - - /** - * 返回当前运行可用的任务状态查询能力。 - * - * @return 任务状态查询能力 - */ - public RunQuerySupport getRunQuerySupport() { - return runQuerySupport; - } - - /** - * 设置当前运行可用的任务状态查询能力。 - * - * @param runQuerySupport 任务状态查询能力 - */ - public void setRunQuerySupport(RunQuerySupport runQuerySupport) { - this.runQuerySupport = runQuerySupport; - } - - /** - * 返回当前运行可用的主动通知能力。 - * - * @return 主动通知能力 - */ - public NotificationSupport getNotificationSupport() { - return notificationSupport; - } - - /** - * 设置当前运行可用的主动通知能力。 - * - * @param notificationSupport 主动通知能力 - */ - public void setNotificationSupport(NotificationSupport notificationSupport) { - this.notificationSupport = notificationSupport; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/ParentRunChildrenSummary.java b/src/main/java/com/jimuqu/claw/agent/runtime/ParentRunChildrenSummary.java deleted file mode 100644 index 10f12f145851b598ceb3bc3bf0fd99a1356f86b6..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/runtime/ParentRunChildrenSummary.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.jimuqu.claw.agent.runtime; - -import com.jimuqu.claw.agent.model.AgentRun; - -import java.util.ArrayList; -import java.util.List; - -/** - * 聚合某个父运行下的所有子任务状态。 - */ -public class ParentRunChildrenSummary { - /** 父运行标识。 */ - private String parentRunId; - /** 聚合使用的批次键。 */ - private String batchKey; - /** 子任务总数。 */ - private int totalChildren; - /** 已成功子任务数。 */ - private int succeededChildren; - /** 已失败子任务数。 */ - private int failedChildren; - /** 仍未结束子任务数。 */ - private int pendingChildren; - /** 是否全部结束。 */ - private boolean allCompleted; - /** 聚合的子任务列表。 */ - private List children = new ArrayList<>(); - - public String getParentRunId() { - return parentRunId; - } - - public void setParentRunId(String parentRunId) { - this.parentRunId = parentRunId; - } - - public String getBatchKey() { - return batchKey; - } - - public void setBatchKey(String batchKey) { - this.batchKey = batchKey; - } - - public int getTotalChildren() { - return totalChildren; - } - - public void setTotalChildren(int totalChildren) { - this.totalChildren = totalChildren; - } - - public int getSucceededChildren() { - return succeededChildren; - } - - public void setSucceededChildren(int succeededChildren) { - this.succeededChildren = succeededChildren; - } - - public int getFailedChildren() { - return failedChildren; - } - - public void setFailedChildren(int failedChildren) { - this.failedChildren = failedChildren; - } - - public int getPendingChildren() { - return pendingChildren; - } - - public void setPendingChildren(int pendingChildren) { - this.pendingChildren = pendingChildren; - } - - public boolean isAllCompleted() { - return allCompleted; - } - - public void setAllCompleted(boolean allCompleted) { - this.allCompleted = allCompleted; - } - - public List getChildren() { - return children; - } - - public void setChildren(List children) { - this.children = children; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/SpawnTaskResult.java b/src/main/java/com/jimuqu/claw/agent/runtime/SpawnTaskResult.java deleted file mode 100644 index f022a8b6c097c618b44807bb4166564ddb7009e6..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/agent/runtime/SpawnTaskResult.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.jimuqu.claw.agent.runtime; - -/** - * 描述一次子任务派生的结果。 - */ -public class SpawnTaskResult { - /** 新建子运行标识。 */ - private String runId; - /** 子会话键。 */ - private String sessionKey; - /** 任务描述。 */ - private String taskDescription; - /** 子任务批次键。 */ - private String batchKey; - - /** - * 返回子运行标识。 - * - * @return 子运行标识 - */ - public String getRunId() { - return runId; - } - - /** - * 设置子运行标识。 - * - * @param runId 子运行标识 - */ - public void setRunId(String runId) { - this.runId = runId; - } - - /** - * 返回子会话键。 - * - * @return 子会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置子会话键。 - * - * @param sessionKey 子会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回任务描述。 - * - * @return 任务描述 - */ - public String getTaskDescription() { - return taskDescription; - } - - /** - * 设置任务描述。 - * - * @param taskDescription 任务描述 - */ - public void setTaskDescription(String taskDescription) { - this.taskDescription = taskDescription; - } - - /** - * 返回子任务批次键。 - * - * @return 子任务批次键 - */ - public String getBatchKey() { - return batchKey; - } - - /** - * 设置子任务批次键。 - * - * @param batchKey 子任务批次键 - */ - public void setBatchKey(String batchKey) { - this.batchKey = batchKey; - } -} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/ConversationAgent.java b/src/main/java/com/jimuqu/claw/agent/runtime/api/ConversationAgent.java similarity index 81% rename from src/main/java/com/jimuqu/claw/agent/runtime/ConversationAgent.java rename to src/main/java/com/jimuqu/claw/agent/runtime/api/ConversationAgent.java index ec4d6c415bf3af6289f1c383bdf2a8f4c8792f14..364d37003a899f2eea93f2d23bb1400a0e5b91e6 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/ConversationAgent.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/api/ConversationAgent.java @@ -1,4 +1,6 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.api; + +import com.jimuqu.claw.agent.runtime.support.ConversationExecutionRequest; import java.util.function.Consumer; @@ -16,3 +18,4 @@ public interface ConversationAgent { */ String execute(ConversationExecutionRequest request, Consumer progressConsumer) throws Throwable; } + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/NotificationSupport.java b/src/main/java/com/jimuqu/claw/agent/runtime/api/NotificationSupport.java similarity index 77% rename from src/main/java/com/jimuqu/claw/agent/runtime/NotificationSupport.java rename to src/main/java/com/jimuqu/claw/agent/runtime/api/NotificationSupport.java index 7359d4cc66d328da74671f74132e7df6a26802d6..3b499628e2b93b2f2846ff53e35aabe2e665ae6a 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/NotificationSupport.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/api/NotificationSupport.java @@ -1,4 +1,6 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.api; + +import com.jimuqu.claw.agent.runtime.support.NotificationResult; /** * 为当前运行提供主动通知用户的能力。 @@ -13,3 +15,4 @@ public interface NotificationSupport { */ NotificationResult notifyUser(String message, boolean progress); } + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/RunQuerySupport.java b/src/main/java/com/jimuqu/claw/agent/runtime/api/RunQuerySupport.java similarity index 86% rename from src/main/java/com/jimuqu/claw/agent/runtime/RunQuerySupport.java rename to src/main/java/com/jimuqu/claw/agent/runtime/api/RunQuerySupport.java index 81e8e016e54a454fbe32881fc467f248687ff17f..d4af3fdc637b26005f8a3cf1102a91ca270d2d1f 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/RunQuerySupport.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/api/RunQuerySupport.java @@ -1,6 +1,7 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.api; -import com.jimuqu.claw.agent.model.AgentRun; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; import java.util.List; @@ -40,3 +41,5 @@ public interface RunQuerySupport { */ ParentRunChildrenSummary getChildSummary(String parentRunId, String batchKey); } + + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/SpawnTaskSupport.java b/src/main/java/com/jimuqu/claw/agent/runtime/api/SpawnTaskSupport.java similarity index 85% rename from src/main/java/com/jimuqu/claw/agent/runtime/SpawnTaskSupport.java rename to src/main/java/com/jimuqu/claw/agent/runtime/api/SpawnTaskSupport.java index 6d3ecc7b31d848917cff247903e4bd3cef2526c6..aa2a1275e9f3a8c260e7cd131fcfff7965f11ad5 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/SpawnTaskSupport.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/api/SpawnTaskSupport.java @@ -1,4 +1,6 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.api; + +import com.jimuqu.claw.agent.runtime.support.SpawnTaskResult; /** * 为当前运行提供派生子任务的能力。 @@ -23,3 +25,4 @@ public interface SpawnTaskSupport { */ SpawnTaskResult spawnTask(String taskDescription, String batchKey); } + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/AgentRuntimeService.java b/src/main/java/com/jimuqu/claw/agent/runtime/impl/AgentRuntimeService.java similarity index 81% rename from src/main/java/com/jimuqu/claw/agent/runtime/AgentRuntimeService.java rename to src/main/java/com/jimuqu/claw/agent/runtime/impl/AgentRuntimeService.java index 534e28b3d18adc227558ac1e5587878f5077ce93..fb9f9d6a8ffcb00479a9a9b50f58c0eed72dab80 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/AgentRuntimeService.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/impl/AgentRuntimeService.java @@ -1,18 +1,26 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.impl; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.jimuqu.claw.agent.channel.ChannelAdapter; import com.jimuqu.claw.agent.channel.ChannelRegistry; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.agent.model.InboundTriggerType; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.agent.model.RunEvent; -import com.jimuqu.claw.agent.model.RunStatus; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.model.event.RunEvent; +import com.jimuqu.claw.agent.model.enums.RunStatus; +import com.jimuqu.claw.agent.runtime.api.ConversationAgent; +import com.jimuqu.claw.agent.runtime.api.NotificationSupport; +import com.jimuqu.claw.agent.runtime.api.RunQuerySupport; +import com.jimuqu.claw.agent.runtime.api.SpawnTaskSupport; +import com.jimuqu.claw.agent.runtime.support.ConversationExecutionRequest; +import com.jimuqu.claw.agent.runtime.support.NotificationResult; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.runtime.support.SpawnTaskResult; import com.jimuqu.claw.agent.store.RuntimeStoreService; import com.jimuqu.claw.config.SolonClawProperties; import org.slf4j.Logger; @@ -357,8 +365,10 @@ public class AgentRuntimeService { request.setSessionKey(inboundEnvelope.getSessionKey()); request.setCurrentMessage(inboundEnvelope.getContent()); request.setCurrentMessageTriggerType(inboundEnvelope.getTriggerType()); + request.setChildRun(StrUtil.isNotBlank(run.getParentRunId())); + request.setParentRunId(run.getParentRunId()); request.setHistory(runtimeStoreService.loadConversationHistoryBefore(inboundEnvelope.getSessionKey(), inboundEnvelope.getSessionVersion())); - request.setSpawnTaskSupport((taskDescription, batchKey) -> spawnTask(runId, inboundEnvelope, taskDescription, batchKey)); + request.setSpawnTaskSupport(buildSpawnTaskSupport(runId, run, inboundEnvelope)); request.setRunQuerySupport(buildRunQuerySupport(inboundEnvelope.getSessionKey())); request.setNotificationSupport(buildNotificationSupport(inboundEnvelope.getSessionKey(), runId)); @@ -553,16 +563,29 @@ public class AgentRuntimeService { return; } - String internalMessage = buildChildCompletionMessage(run); AgentRun parentRun = runtimeStoreService.getRun(run.getParentRunId()); long sourceUserVersion = parentRun == null ? 0L : parentRun.getSourceUserVersion(); + ParentRunChildrenSummary overallSummary = runtimeStoreService.summarizeChildRuns(run.getParentRunId(), null); + ParentRunChildrenSummary batchSummary = StrUtil.isBlank(run.getBatchKey()) + ? null + : runtimeStoreService.summarizeChildRuns(run.getParentRunId(), run.getBatchKey()); + appendParentChildCompletionEvents(run, overallSummary, batchSummary); + + String internalMessage = buildChildCompletionMessage(run, overallSummary, batchSummary); runtimeStoreService.appendChildRunCompletedEvent(run.getParentSessionKey(), run.getParentRunId(), sourceUserVersion, run); - submitSystemMessage( + String continuationRunId = submitSystemMessage( run.getParentSessionKey(), run.getParentReplyTarget(), internalMessage, "child-complete:" + run.getParentRunId() ); + runtimeStoreService.appendRunEvent( + run.getParentRunId(), + "child_continuation_submitted", + "childRunId=" + run.getRunId() + + ", continuationRunId=" + continuationRunId + + ", pendingChildren=" + (overallSummary == null ? 0 : overallSummary.getPendingChildren()) + ); } /** @@ -601,7 +624,11 @@ public class AgentRuntimeService { * @param run 子运行 * @return 内部消息文本 */ - private String buildChildCompletionMessage(AgentRun run) { + private String buildChildCompletionMessage( + AgentRun run, + ParentRunChildrenSummary overallSummary, + ParentRunChildrenSummary batchSummary + ) { StringBuilder builder = new StringBuilder(); builder.append("[内部事件] 子任务已完成").append('\n'); builder.append("父运行ID: ").append(run.getParentRunId()).append('\n'); @@ -611,15 +638,118 @@ public class AgentRuntimeService { if (StrUtil.isNotBlank(run.getTaskDescription())) { builder.append("任务: ").append(run.getTaskDescription()).append('\n'); } + if (overallSummary != null && overallSummary.getTotalChildren() > 0) { + builder.append("全部子任务汇总: total=").append(overallSummary.getTotalChildren()) + .append(", succeeded=").append(overallSummary.getSucceededChildren()) + .append(", failed=").append(overallSummary.getFailedChildren()) + .append(", pending=").append(overallSummary.getPendingChildren()) + .append(", allCompleted=").append(overallSummary.isAllCompleted()) + .append('\n'); + } + if (batchSummary != null && batchSummary.getTotalChildren() > 0) { + builder.append("当前批次汇总: batchKey=").append(StrUtil.blankToDefault(batchSummary.getBatchKey(), "(空)")) + .append(", total=").append(batchSummary.getTotalChildren()) + .append(", succeeded=").append(batchSummary.getSucceededChildren()) + .append(", failed=").append(batchSummary.getFailedChildren()) + .append(", pending=").append(batchSummary.getPendingChildren()) + .append(", allCompleted=").append(batchSummary.isAllCompleted()) + .append('\n'); + } if (run.getStatus() == RunStatus.SUCCEEDED) { builder.append("结果:\n").append(StrUtil.blankToDefault(run.getFinalResponse(), "(空结果)")); } else { builder.append("错误:\n").append(StrUtil.blankToDefault(run.getErrorMessage(), "(未知错误)")); } - builder.append("\n\n请基于已有上下文继续处理,必要时再派生新的子任务。"); + builder.append("\n\n请基于已有上下文继续处理。"); + if (overallSummary != null && overallSummary.getPendingChildren() > 0) { + builder.append("\n- 仍有子任务未完成时,优先返回 NO_REPLY,避免过早对外回复。"); + builder.append("\n- 若需要了解进度,可用 get_child_summary 或 list_child_runs 查看当前状态。"); + } else { + builder.append("\n- 如果现在需要统一对外回复,优先使用 FINAL_REPLY_ONCE: 前缀给出最终聚合结果。"); + builder.append("\n- 如果只需要结束内部编排、不需要外发,请返回 NO_REPLY。"); + } return builder.toString(); } + /** + * 为当前运行构造子任务派生能力,并在子任务场景下应用默认的防扇出限制。 + * + * @param runId 当前运行标识 + * @param run 当前运行对象 + * @param inboundEnvelope 当前入站消息 + * @return 子任务派生能力 + */ + private SpawnTaskSupport buildSpawnTaskSupport(String runId, AgentRun run, InboundEnvelope inboundEnvelope) { + if (run == null) { + return null; + } + if (StrUtil.isBlank(run.getParentRunId()) || properties.getAgent().getSubtasks().isAllowNestedSpawn()) { + return (taskDescription, batchKey) -> spawnTask(runId, inboundEnvelope, taskDescription, batchKey); + } + + return (taskDescription, batchKey) -> { + String reason = "当前子任务默认禁止继续派生子任务;请先返回结果给父任务,由父任务决定是否继续拆分"; + runtimeStoreService.appendRunEvent( + runId, + "spawn_task_blocked", + reason + (StrUtil.isBlank(taskDescription) ? "" : ",task=" + taskDescription.trim()) + ); + throw new IllegalStateException(reason); + }; + } + + /** + * 在父运行上追加与子任务完成相关的结构化调试事件。 + * + * @param childRun 已完成的子任务 + * @param overallSummary 父运行下的全部子任务汇总 + * @param batchSummary 当前批次汇总 + */ + private void appendParentChildCompletionEvents( + AgentRun childRun, + ParentRunChildrenSummary overallSummary, + ParentRunChildrenSummary batchSummary + ) { + if (childRun == null || StrUtil.isBlank(childRun.getParentRunId())) { + return; + } + + StringBuilder received = new StringBuilder(); + received.append("childRunId=").append(childRun.getRunId()) + .append(", status=").append(childRun.getStatus()); + if (StrUtil.isNotBlank(childRun.getBatchKey())) { + received.append(", batchKey=").append(childRun.getBatchKey()); + } + if (overallSummary != null) { + received.append(", totalChildren=").append(overallSummary.getTotalChildren()) + .append(", pendingChildren=").append(overallSummary.getPendingChildren()); + } + runtimeStoreService.appendRunEvent(childRun.getParentRunId(), "child_completion_received", received.toString()); + + if (batchSummary != null && batchSummary.getTotalChildren() > 0) { + runtimeStoreService.appendRunEvent( + childRun.getParentRunId(), + "child_batch_progress", + "batchKey=" + StrUtil.blankToDefault(batchSummary.getBatchKey(), "(空)") + + ", total=" + batchSummary.getTotalChildren() + + ", succeeded=" + batchSummary.getSucceededChildren() + + ", failed=" + batchSummary.getFailedChildren() + + ", pending=" + batchSummary.getPendingChildren() + ); + } + + if (overallSummary != null) { + runtimeStoreService.appendRunEvent( + childRun.getParentRunId(), + overallSummary.isAllCompleted() ? "children_all_completed" : "children_pending", + "total=" + overallSummary.getTotalChildren() + + ", succeeded=" + overallSummary.getSucceededChildren() + + ", failed=" + overallSummary.getFailedChildren() + + ", pending=" + overallSummary.getPendingChildren() + ); + } + } + /** * 为当前会话构造任务状态查询能力。 * @@ -786,4 +916,7 @@ public class AgentRuntimeService { String prefix = "child-complete:"; return senderId.startsWith(prefix) ? senderId.substring(prefix.length()) : null; } + } + + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/ConversationScheduler.java b/src/main/java/com/jimuqu/claw/agent/runtime/impl/ConversationScheduler.java similarity index 99% rename from src/main/java/com/jimuqu/claw/agent/runtime/ConversationScheduler.java rename to src/main/java/com/jimuqu/claw/agent/runtime/impl/ConversationScheduler.java index 49802aad54235519f5649468d06682b89c2ed025..1f886cb6f68332aad1649f6bf03b7f84f65503ee 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/ConversationScheduler.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/impl/ConversationScheduler.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.impl; import java.util.ArrayDeque; import java.util.Deque; @@ -158,3 +158,4 @@ public class ConversationScheduler { } } } + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/HeartbeatService.java b/src/main/java/com/jimuqu/claw/agent/runtime/impl/HeartbeatService.java similarity index 93% rename from src/main/java/com/jimuqu/claw/agent/runtime/HeartbeatService.java rename to src/main/java/com/jimuqu/claw/agent/runtime/impl/HeartbeatService.java index 1f8198f876a96f96d1d6c0044fffc7555c88f711..3e6ddc091a9ff0473eee7506f8df81e6a7c5d37d 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/HeartbeatService.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/impl/HeartbeatService.java @@ -1,10 +1,11 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.impl; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; -import com.jimuqu.claw.agent.model.LatestReplyRoute; +import com.jimuqu.claw.agent.model.route.LatestReplyRoute; import com.jimuqu.claw.agent.store.RuntimeStoreService; import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.config.props.HeartbeatProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +51,7 @@ public class HeartbeatService { * 启动心跳定时任务。 */ public void start() { - SolonClawProperties.Heartbeat heartbeat = properties.getAgent().getHeartbeat(); + HeartbeatProperties heartbeat = properties.getAgent().getHeartbeat(); if (!heartbeat.isEnabled()) { log.info("Heartbeat service disabled."); return; @@ -96,7 +97,7 @@ public class HeartbeatService { /** * 执行一次心跳检查和投递。 */ - void tick() { + public void tick() { File heartbeatFile = new File(properties.getWorkspace(), "HEARTBEAT.md"); if (!heartbeatFile.exists()) { return; @@ -115,3 +116,5 @@ public class HeartbeatService { agentRuntimeService.submitSilentSystemMessage(route.getSessionKey(), route.getReplyTarget(), content, "heartbeat"); } } + + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/SolonAiConversationAgent.java b/src/main/java/com/jimuqu/claw/agent/runtime/impl/SolonAiConversationAgent.java similarity index 93% rename from src/main/java/com/jimuqu/claw/agent/runtime/SolonAiConversationAgent.java rename to src/main/java/com/jimuqu/claw/agent/runtime/impl/SolonAiConversationAgent.java index 87ca12273515ae5e140694f0c19d10f008ada255..c4e1e08f2ad2e15bb876fbb7fafd2bc7863e9c04 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/SolonAiConversationAgent.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/impl/SolonAiConversationAgent.java @@ -1,6 +1,10 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.impl; -import com.jimuqu.claw.agent.model.InboundTriggerType; +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.runtime.api.ConversationAgent; +import com.jimuqu.claw.agent.runtime.support.ConversationExecutionRequest; +import com.jimuqu.claw.agent.runtime.support.SystemAwareAgentSession; +import com.jimuqu.claw.agent.runtime.support.VisibleProgressAccumulator; import com.jimuqu.claw.agent.tool.ConversationRuntimeTools; import com.jimuqu.claw.agent.tool.JobTools; import com.jimuqu.claw.agent.tool.WorkspaceAgentTools; @@ -112,7 +116,7 @@ public class SolonAiConversationAgent implements ConversationAgent { ); return ReActAgent.of(chatModel) .name(workspacePromptService.resolveAgentName()) - .instruction(workspacePromptService.buildSystemPrompt()) + .instruction(workspacePromptService.buildSystemPrompt(request)) .defaultToolAdd(runtimeTools) .defaultToolAdd(jobTools) .defaultSkillAdd(cliSkillProvider) @@ -148,3 +152,5 @@ public class SolonAiConversationAgent implements ConversationAgent { return "这是一次内部系统触发。请优先依据最新的 system 消息和既有上下文继续处理,不要把它当作用户新消息。"; } } + + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/support/ConversationExecutionRequest.java b/src/main/java/com/jimuqu/claw/agent/runtime/support/ConversationExecutionRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..38cf54e94d63d0a4a8d7dc266dd189a53b4cf6d6 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/runtime/support/ConversationExecutionRequest.java @@ -0,0 +1,41 @@ +package com.jimuqu.claw.agent.runtime.support; + +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.runtime.api.NotificationSupport; +import com.jimuqu.claw.agent.runtime.api.RunQuerySupport; +import com.jimuqu.claw.agent.runtime.api.SpawnTaskSupport; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.noear.solon.ai.chat.message.ChatMessage; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 描述一次会话执行所需的上下文输入。 + */ +@Data +@NoArgsConstructor +public class ConversationExecutionRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** 当前会话对应的内部键。 */ + private String sessionKey; + /** 当前待处理的用户消息。 */ + private String currentMessage; + /** 当前消息的触发类型。 */ + private InboundTriggerType currentMessageTriggerType = InboundTriggerType.USER; + /** 当前运行是否为父任务派生出的子任务。 */ + private boolean childRun; + /** 当前子任务对应的父运行标识。 */ + private String parentRunId; + /** 历史消息列表。 */ + private List history = new ArrayList(); + /** 当前运行可用的子任务派生能力。 */ + private SpawnTaskSupport spawnTaskSupport; + /** 当前运行可用的任务状态查询能力。 */ + private RunQuerySupport runQuerySupport; + /** 当前运行可用的主动通知能力。 */ + private NotificationSupport notificationSupport; +} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/NotificationResult.java b/src/main/java/com/jimuqu/claw/agent/runtime/support/NotificationResult.java similarity index 30% rename from src/main/java/com/jimuqu/claw/agent/runtime/NotificationResult.java rename to src/main/java/com/jimuqu/claw/agent/runtime/support/NotificationResult.java index ebaae22d36c30491cfa0aaad0db54aa427200e66..e95c5c899e26e1853395c885f59873dd387bef52 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/NotificationResult.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/support/NotificationResult.java @@ -1,37 +1,22 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.support; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; /** * 描述一次主动通知的结果。 */ -public class NotificationResult { +@Data +@NoArgsConstructor +public class NotificationResult implements Serializable { + private static final long serialVersionUID = 1L; + /** 是否成功发送。 */ private boolean delivered; /** 实际投递的会话键。 */ private String sessionKey; /** 结果说明。 */ private String message; - - public boolean isDelivered() { - return delivered; - } - - public void setDelivered(boolean delivered) { - this.delivered = delivered; - } - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } } diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/support/ParentRunChildrenSummary.java b/src/main/java/com/jimuqu/claw/agent/runtime/support/ParentRunChildrenSummary.java new file mode 100644 index 0000000000000000000000000000000000000000..c471e310209e8f1f609a374eb795ed725f77e85b --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/runtime/support/ParentRunChildrenSummary.java @@ -0,0 +1,35 @@ +package com.jimuqu.claw.agent.runtime.support; + +import com.jimuqu.claw.agent.model.run.AgentRun; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 聚合某个父运行下的所有子任务状态。 + */ +@Data +@NoArgsConstructor +public class ParentRunChildrenSummary implements Serializable { + private static final long serialVersionUID = 1L; + + /** 父运行标识。 */ + private String parentRunId; + /** 聚合使用的批次键。 */ + private String batchKey; + /** 子任务总数。 */ + private int totalChildren; + /** 已成功子任务数。 */ + private int succeededChildren; + /** 已失败子任务数。 */ + private int failedChildren; + /** 仍未结束子任务数。 */ + private int pendingChildren; + /** 是否全部结束。 */ + private boolean allCompleted; + /** 聚合的子任务列表。 */ + private List children = new ArrayList(); +} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/support/SpawnTaskResult.java b/src/main/java/com/jimuqu/claw/agent/runtime/support/SpawnTaskResult.java new file mode 100644 index 0000000000000000000000000000000000000000..e57aa6397d2db15262d2a3fc078154c87fb95007 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/agent/runtime/support/SpawnTaskResult.java @@ -0,0 +1,24 @@ +package com.jimuqu.claw.agent.runtime.support; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述一次子任务派生的结果。 + */ +@Data +@NoArgsConstructor +public class SpawnTaskResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** 新建子运行标识。 */ + private String runId; + /** 子会话键。 */ + private String sessionKey; + /** 任务描述。 */ + private String taskDescription; + /** 子任务批次键。 */ + private String batchKey; +} diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSession.java b/src/main/java/com/jimuqu/claw/agent/runtime/support/SystemAwareAgentSession.java similarity index 97% rename from src/main/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSession.java rename to src/main/java/com/jimuqu/claw/agent/runtime/support/SystemAwareAgentSession.java index f14a48d22df68f55a9a56420be8e8cc707d42ab3..3b5e7c9e577d18dd7a5269f17026299828fe6e88 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSession.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/support/SystemAwareAgentSession.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.support; import org.noear.solon.ai.agent.Agent; import org.noear.solon.ai.agent.AgentSession; @@ -56,3 +56,4 @@ public class SystemAwareAgentSession extends InMemoryChatSession implements Agen return snapshot; } } + diff --git a/src/main/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulator.java b/src/main/java/com/jimuqu/claw/agent/runtime/support/VisibleProgressAccumulator.java similarity index 96% rename from src/main/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulator.java rename to src/main/java/com/jimuqu/claw/agent/runtime/support/VisibleProgressAccumulator.java index d7e87a74c10a2a3b2a0541f2b12709228d30a59e..891d085827a30df80e9f718717fe1bb7d9e08a21 100644 --- a/src/main/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulator.java +++ b/src/main/java/com/jimuqu/claw/agent/runtime/support/VisibleProgressAccumulator.java @@ -1,11 +1,11 @@ -package com.jimuqu.claw.agent.runtime; +package com.jimuqu.claw.agent.runtime.support; import cn.hutool.core.util.StrUtil; /** * 将模型流式输出折叠为“对用户可见的累计正文”。 */ -class VisibleProgressAccumulator { +public class VisibleProgressAccumulator { /** 最近一次对用户可见的累计正文。 */ private String visibleText = ""; @@ -85,3 +85,4 @@ class VisibleProgressAccumulator { return StrUtil.trim(currentValue + nextValue); } } + diff --git a/src/main/java/com/jimuqu/claw/agent/store/RuntimeStoreService.java b/src/main/java/com/jimuqu/claw/agent/store/RuntimeStoreService.java index a587191a296f515a4c8016d0f1a8e361406f75d3..b5f4556b9725c48f1ade0418f0949480994c9857 100644 --- a/src/main/java/com/jimuqu/claw/agent/store/RuntimeStoreService.java +++ b/src/main/java/com/jimuqu/claw/agent/store/RuntimeStoreService.java @@ -4,18 +4,18 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.json.JSONUtil; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.model.ChildRunCompletedData; -import com.jimuqu.claw.agent.model.ChildRunSpawnedData; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationEvent; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.agent.model.InboundTriggerType; -import com.jimuqu.claw.agent.model.LatestReplyRoute; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.agent.model.RunEvent; -import com.jimuqu.claw.agent.model.RunStatus; -import com.jimuqu.claw.agent.runtime.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.model.event.ChildRunCompletedData; +import com.jimuqu.claw.agent.model.event.ChildRunSpawnedData; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.event.ConversationEvent; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.model.route.LatestReplyRoute; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.model.event.RunEvent; +import com.jimuqu.claw.agent.model.enums.RunStatus; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; import org.noear.solon.ai.chat.message.ChatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -972,3 +972,5 @@ public class RuntimeStoreService { return pathLocks.computeIfAbsent(file.getAbsolutePath(), key -> new ReentrantLock()); } } + + diff --git a/src/main/java/com/jimuqu/claw/agent/tool/ConversationRuntimeTools.java b/src/main/java/com/jimuqu/claw/agent/tool/ConversationRuntimeTools.java index f8f1a96006c9fd59c1531224de87e3e8d67b1ad7..aaa9e560a395bb14751386aeba41cdccd4334551 100644 --- a/src/main/java/com/jimuqu/claw/agent/tool/ConversationRuntimeTools.java +++ b/src/main/java/com/jimuqu/claw/agent/tool/ConversationRuntimeTools.java @@ -1,13 +1,13 @@ package com.jimuqu.claw.agent.tool; import cn.hutool.core.util.StrUtil; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.runtime.NotificationResult; -import com.jimuqu.claw.agent.runtime.NotificationSupport; -import com.jimuqu.claw.agent.runtime.ParentRunChildrenSummary; -import com.jimuqu.claw.agent.runtime.SpawnTaskResult; -import com.jimuqu.claw.agent.runtime.SpawnTaskSupport; -import com.jimuqu.claw.agent.runtime.RunQuerySupport; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.runtime.support.NotificationResult; +import com.jimuqu.claw.agent.runtime.api.NotificationSupport; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.runtime.support.SpawnTaskResult; +import com.jimuqu.claw.agent.runtime.api.SpawnTaskSupport; +import com.jimuqu.claw.agent.runtime.api.RunQuerySupport; import org.noear.solon.ai.annotation.ToolMapping; import org.noear.solon.annotation.Param; @@ -94,11 +94,15 @@ public class ConversationRuntimeTools { return "当前运行不支持 spawn_task"; } - SpawnTaskResult result = spawnTaskSupport.spawnTask(taskDescription, batchKey); - return "已创建子任务。childRunId=" + result.getRunId() - + ", childSessionKey=" + result.getSessionKey() - + ", task=" + result.getTaskDescription() - + (StrUtil.isBlank(result.getBatchKey()) ? "" : ", batchKey=" + result.getBatchKey()); + try { + SpawnTaskResult result = spawnTaskSupport.spawnTask(taskDescription, batchKey); + return "已创建子任务。childRunId=" + result.getRunId() + + ", childSessionKey=" + result.getSessionKey() + + ", task=" + result.getTaskDescription() + + (StrUtil.isBlank(result.getBatchKey()) ? "" : ", batchKey=" + result.getBatchKey()); + } catch (RuntimeException e) { + return "创建子任务失败: " + StrUtil.blankToDefault(e.getMessage(), e.getClass().getSimpleName()); + } } @ToolMapping(name = "list_child_runs", description = "查看当前会话最近的子任务列表,返回 runId、状态、任务描述和结果摘要") @@ -227,3 +231,5 @@ public class ConversationRuntimeTools { return text.substring(0, maxChars) + "..."; } } + + diff --git a/src/main/java/com/jimuqu/claw/agent/workspace/WorkspacePromptService.java b/src/main/java/com/jimuqu/claw/agent/workspace/WorkspacePromptService.java index d3f0bd5e66b8df903fe17534f7dbe6c4096f25f3..b9dbf3afebd3b034de1fede4f27aa53b4678399c 100644 --- a/src/main/java/com/jimuqu/claw/agent/workspace/WorkspacePromptService.java +++ b/src/main/java/com/jimuqu/claw/agent/workspace/WorkspacePromptService.java @@ -3,6 +3,7 @@ package com.jimuqu.claw.agent.workspace; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; +import com.jimuqu.claw.agent.runtime.support.ConversationExecutionRequest; import java.io.File; import java.io.IOException; @@ -104,19 +105,24 @@ public class WorkspacePromptService { * @return 组装后的系统提示词 */ public String buildSystemPrompt() { + return buildSystemPrompt(null); + } + + /** + * 构造当前 Agent 运行使用的系统提示词。 + * + * @param request 当前执行请求;为空时按主任务模式构造 + * @return 组装后的系统提示词 + */ + public String buildSystemPrompt(ConversationExecutionRequest request) { + boolean childRun = request != null && request.isChildRun(); List lines = new ArrayList<>(); lines.add(baseSystemPrompt.trim()); + appendExecutionGuidance(lines, request, childRun); lines.add(""); lines.add("当前工作区: " + workspaceService.getWorkspaceDir().getAbsolutePath()); lines.add("除非用户明确要求,否则所有运行期文件与引导文件都以该工作区为根目录。"); - appendSection(lines, "工作区规则", AGENTS_FILE); - appendSection(lines, "灵魂设定", SOUL_FILE); - appendSection(lines, "身份记录", IDENTITY_FILE); - appendSection(lines, "用户画像", USER_FILE); - appendSection(lines, "工具备注", TOOLS_FILE); - appendSection(lines, "首次对话引导", BOOTSTRAP_FILE); - appendSection(lines, "长期记忆", MEMORY_FILE); - appendRecentDailyMemory(lines); + appendWorkspaceContext(lines, childRun); return String.join("\n", lines); } @@ -224,6 +230,64 @@ public class WorkspacePromptService { lines.add(content); } + /** + * 追加运行编排与子任务相关的固定提示。 + * + * @param lines 结果行集合 + * @param request 当前执行请求 + * @param childRun 是否为子任务模式 + */ + private void appendExecutionGuidance( + List lines, + ConversationExecutionRequest request, + boolean childRun + ) { + lines.add(""); + if (childRun) { + lines.add("## 子任务模式"); + if (request != null && StrUtil.isNotBlank(request.getParentRunId())) { + lines.add("当前父运行: " + request.getParentRunId()); + } + lines.add("- 你当前是父任务派生出的子任务,只负责完成一个单一、明确、可执行的目标。"); + lines.add("- 不要重复做父任务的整体规划,不要和用户闲聊,也不要把当前任务扩写成泛泛而谈的长回复。"); + lines.add("- 优先返回可供父任务聚合的结果:做了什么、关键发现、失败原因、剩余风险、建议下一步。"); + lines.add("- 默认不要修改 `MEMORY.md`、`USER.md`、`SOUL.md`、`IDENTITY.md` 这类长期文件,除非任务明确要求且确有必要。"); + lines.add("- 系统默认不鼓励子任务继续派生;如果 `spawn_task` 不可用或被拒绝,先把结果交回父任务。"); + lines.add("- 如果任务完成后不需要直接对外回复,请返回简洁结果供父任务继续汇总。"); + return; + } + + lines.add("## 长任务与子任务"); + lines.add("- 当任务涉及多个相对独立的步骤、多个目录或服务、长时间命令、并行探索多个方向,或用户会受益于后台持续推进时,优先考虑用 `spawn_task` 拆分,而不是在一个 run 里硬做到底。"); + lines.add("- 典型适合拆分的场景:仓库部署与启动、编译/测试/回归、并行查文档或代码、分模块排查日志或配置、批量检查多个候选方案。"); + lines.add("- 子任务描述应写成单一、清晰、可执行的目标,避免把整段原始对话原样塞给子任务。"); + lines.add("- 派生后,父任务可先继续规划、记录进度或返回 `NO_REPLY`;如果要等全部子任务完成后统一回复,可使用 `FINAL_REPLY_ONCE:` 只发送一次最终聚合结果。"); + lines.add("- 使用 `list_child_runs`、`get_run_status`、`get_child_summary` 跟踪子任务进度和批次聚合结果。"); + lines.add("- 对特别小、特别快、明显串行的动作不要滥用子任务;拆分是为了提高清晰度、并行度和长任务稳定性。"); + } + + /** + * 按运行模式追加工作区上下文。 + * + * @param lines 结果行集合 + * @param childRun 是否为子任务模式 + */ + private void appendWorkspaceContext(List lines, boolean childRun) { + appendSection(lines, childRun ? "子任务工作区规则" : "工作区规则", AGENTS_FILE); + if (childRun) { + appendSection(lines, "子任务工具备注", TOOLS_FILE); + return; + } + + appendSection(lines, "灵魂设定", SOUL_FILE); + appendSection(lines, "身份记录", IDENTITY_FILE); + appendSection(lines, "用户画像", USER_FILE); + appendSection(lines, "工具备注", TOOLS_FILE); + appendSection(lines, "首次对话引导", BOOTSTRAP_FILE); + appendSection(lines, "长期记忆", MEMORY_FILE); + appendRecentDailyMemory(lines); + } + /** * 将最近两天的每日记忆文件追加到系统提示词。 * @@ -313,3 +377,4 @@ public class WorkspacePromptService { return StrUtil.isNotBlank(name) && !name.startsWith("_"); } } + diff --git a/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapter.java b/src/main/java/com/jimuqu/claw/channel/dingtalk/adapter/DingTalkChannelAdapter.java similarity index 92% rename from src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapter.java rename to src/main/java/com/jimuqu/claw/channel/dingtalk/adapter/DingTalkChannelAdapter.java index 97798a4a6c1dd48679a252f0018ae2b5cf9f5e21..2e053c9dc58a7d128ccdbe9e0bc8722bf1838a09 100644 --- a/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapter.java +++ b/src/main/java/com/jimuqu/claw/channel/dingtalk/adapter/DingTalkChannelAdapter.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.channel.dingtalk; +package com.jimuqu.claw.channel.dingtalk.adapter; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; @@ -10,14 +10,14 @@ import com.dingtalk.open.app.api.models.bot.ChatbotMessage; import com.dingtalk.open.app.api.models.bot.MessageContent; import com.dingtalk.open.app.api.security.AuthClientCredential; import com.jimuqu.claw.agent.channel.ChannelAdapter; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.agent.runtime.AgentRuntimeService; -import com.jimuqu.claw.agent.store.RuntimeStoreService; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.runtime.impl.AgentRuntimeService; +import com.jimuqu.claw.channel.dingtalk.sender.DingTalkRobotSender; +import com.jimuqu.claw.config.props.DingTalkProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,12 +31,10 @@ public class DingTalkChannelAdapter implements private static final Logger log = LoggerFactory.getLogger(DingTalkChannelAdapter.class); /** Agent 运行时服务。 */ private final AgentRuntimeService agentRuntimeService; - /** 运行时存储服务。 */ - private final RuntimeStoreService runtimeStoreService; /** 钉钉消息发送服务。 */ private final DingTalkRobotSender dingTalkRobotSender; /** 钉钉渠道配置。 */ - private final SolonClawProperties.DingTalk properties; + private final DingTalkProperties properties; /** 钉钉 Stream 客户端。 */ private OpenDingTalkClient client; @@ -44,18 +42,15 @@ public class DingTalkChannelAdapter implements * 创建钉钉渠道适配器。 * * @param agentRuntimeService Agent 运行时服务 - * @param runtimeStoreService 运行时存储服务 * @param dingTalkRobotSender 钉钉消息发送服务 * @param properties 钉钉渠道配置 */ public DingTalkChannelAdapter( AgentRuntimeService agentRuntimeService, - RuntimeStoreService runtimeStoreService, DingTalkRobotSender dingTalkRobotSender, - SolonClawProperties.DingTalk properties + DingTalkProperties properties ) { this.agentRuntimeService = agentRuntimeService; - this.runtimeStoreService = runtimeStoreService; this.dingTalkRobotSender = dingTalkRobotSender; this.properties = properties; } @@ -150,7 +145,7 @@ public class DingTalkChannelAdapter implements * @param message 钉钉机器人消息 * @return 入站消息;若不应处理则返回 null */ - InboundEnvelope toInboundEnvelope(ChatbotMessage message) { + public InboundEnvelope toInboundEnvelope(ChatbotMessage message) { if (message == null) { return null; } @@ -328,3 +323,7 @@ public class DingTalkChannelAdapter implements return isBlank(second) ? null : second; } } + + + + diff --git a/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSender.java b/src/main/java/com/jimuqu/claw/channel/dingtalk/sender/DingTalkRobotSender.java similarity index 91% rename from src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSender.java rename to src/main/java/com/jimuqu/claw/channel/dingtalk/sender/DingTalkRobotSender.java index be73d9ba7cd1e407c95c40b82cbee3104e268af7..17002c50133d5bb208e0f4da75c0ae3832d92959 100644 --- a/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSender.java +++ b/src/main/java/com/jimuqu/claw/channel/dingtalk/sender/DingTalkRobotSender.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.channel.dingtalk; +package com.jimuqu.claw.channel.dingtalk.sender; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; @@ -8,9 +8,10 @@ import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTORequest; import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders; import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendRequest; import com.aliyun.teautil.models.RuntimeOptions; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.channel.dingtalk.service.DingTalkAccessTokenService; +import com.jimuqu.claw.config.props.DingTalkProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +29,7 @@ public class DingTalkRobotSender { /** access_token 服务。 */ private final DingTalkAccessTokenService accessTokenService; /** 钉钉配置。 */ - private final SolonClawProperties.DingTalk properties; + private final DingTalkProperties properties; /** 机器人 OpenAPI 客户端。 */ private final Client robotClient; @@ -41,7 +42,7 @@ public class DingTalkRobotSender { */ public DingTalkRobotSender( DingTalkAccessTokenService accessTokenService, - SolonClawProperties.DingTalk properties + DingTalkProperties properties ) throws Exception { this(accessTokenService, properties, createRobotClient()); } @@ -53,9 +54,9 @@ public class DingTalkRobotSender { * @param properties 钉钉配置 * @param robotClient 机器人客户端 */ - DingTalkRobotSender( + public DingTalkRobotSender( DingTalkAccessTokenService accessTokenService, - SolonClawProperties.DingTalk properties, + DingTalkProperties properties, Client robotClient ) { this.accessTokenService = accessTokenService; @@ -151,7 +152,7 @@ public class DingTalkRobotSender { * @param content 文本内容 * @return 消息类型键 */ - String resolveMsgKey(String content) { + public String resolveMsgKey(String content) { return "sampleMarkdown"; } @@ -171,7 +172,7 @@ public class DingTalkRobotSender { * @param content 回复内容 * @return JSON 字符串 */ - String markdownMessageParam(String content) { + public String markdownMessageParam(String content) { JSONObject jsonObject = new JSONObject(); jsonObject.put("title", resolveMarkdownTitle(content)); jsonObject.put("text", content); @@ -184,7 +185,7 @@ public class DingTalkRobotSender { * @param content 回复内容 * @return 标题文本 */ - String resolveMarkdownTitle(String content) { + public String resolveMarkdownTitle(String content) { if (StrUtil.isBlank(content)) { return "SolonClaw"; } @@ -212,3 +213,6 @@ public class DingTalkRobotSender { return new Client(config); } } + + + diff --git a/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkAccessTokenService.java b/src/main/java/com/jimuqu/claw/channel/dingtalk/service/DingTalkAccessTokenService.java similarity index 96% rename from src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkAccessTokenService.java rename to src/main/java/com/jimuqu/claw/channel/dingtalk/service/DingTalkAccessTokenService.java index 6462acf1b8d058cb61939214524c34bf8ee86ddf..bb31c5bad50dc956474a8587cbdae422f92e1d30 100644 --- a/src/main/java/com/jimuqu/claw/channel/dingtalk/DingTalkAccessTokenService.java +++ b/src/main/java/com/jimuqu/claw/channel/dingtalk/service/DingTalkAccessTokenService.java @@ -1,11 +1,11 @@ -package com.jimuqu.claw.channel.dingtalk; +package com.jimuqu.claw.channel.dingtalk.service; import cn.hutool.core.util.StrUtil; import com.aliyun.dingtalkoauth2_1_0.Client; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponseBody; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.config.props.DingTalkProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,7 +22,7 @@ public class DingTalkAccessTokenService { /** 日志记录器。 */ private static final Logger log = LoggerFactory.getLogger(DingTalkAccessTokenService.class); /** 钉钉配置。 */ - private final SolonClawProperties.DingTalk properties; + private final DingTalkProperties properties; /** 当前可用 token。 */ private volatile AccessToken currentToken; /** 定时刷新调度器。 */ @@ -35,7 +35,7 @@ public class DingTalkAccessTokenService { * * @param properties 钉钉配置 */ - public DingTalkAccessTokenService(SolonClawProperties.DingTalk properties) { + public DingTalkAccessTokenService(DingTalkProperties properties) { this.properties = properties; } @@ -178,3 +178,5 @@ public class DingTalkAccessTokenService { private long expireTimestamp; } } + + diff --git a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapter.java b/src/main/java/com/jimuqu/claw/channel/feishu/adapter/FeishuChannelAdapter.java similarity index 94% rename from src/main/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapter.java rename to src/main/java/com/jimuqu/claw/channel/feishu/adapter/FeishuChannelAdapter.java index dbcce1a79bfcacf13db571d7e456ddb5f331f416..0fbf7554b1e04854a41846ec5eddb69246daaf94 100644 --- a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapter.java +++ b/src/main/java/com/jimuqu/claw/channel/feishu/adapter/FeishuChannelAdapter.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.channel.feishu; +package com.jimuqu.claw.channel.feishu.adapter; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; @@ -12,13 +12,14 @@ import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1; import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1Data; import com.lark.oapi.service.im.v1.model.UserId; import com.jimuqu.claw.agent.channel.ChannelAdapter; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.agent.runtime.AgentRuntimeService; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.runtime.impl.AgentRuntimeService; +import com.jimuqu.claw.channel.feishu.sender.FeishuBotSender; +import com.jimuqu.claw.config.props.FeishuProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +36,7 @@ public class FeishuChannelAdapter implements ChannelAdapter { /** 飞书消息发送服务。 */ private final FeishuBotSender feishuBotSender; /** 飞书渠道配置。 */ - private final SolonClawProperties.Feishu properties; + private final FeishuProperties properties; /** 飞书长连接客户端。 */ private com.lark.oapi.ws.Client wsClient; @@ -49,7 +50,7 @@ public class FeishuChannelAdapter implements ChannelAdapter { public FeishuChannelAdapter( AgentRuntimeService agentRuntimeService, FeishuBotSender feishuBotSender, - SolonClawProperties.Feishu properties + FeishuProperties properties ) { this.agentRuntimeService = agentRuntimeService; this.feishuBotSender = feishuBotSender; @@ -149,7 +150,7 @@ public class FeishuChannelAdapter implements ChannelAdapter { * @param event 飞书消息事件 * @return 入站消息;若不应处理则返回 null */ - InboundEnvelope toInboundEnvelope(P2MessageReceiveV1 event) { + public InboundEnvelope toInboundEnvelope(P2MessageReceiveV1 event) { if (event == null || event.getEvent() == null || event.getEvent().getMessage() == null) { return null; } @@ -381,3 +382,7 @@ public class FeishuChannelAdapter implements ChannelAdapter { return null; } } + + + + diff --git a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuMessageGateway.java b/src/main/java/com/jimuqu/claw/channel/feishu/gateway/FeishuMessageGateway.java similarity index 93% rename from src/main/java/com/jimuqu/claw/channel/feishu/FeishuMessageGateway.java rename to src/main/java/com/jimuqu/claw/channel/feishu/gateway/FeishuMessageGateway.java index f969f3b7fbf4832b0d0c346eeb82125f741516fd..00ed8bed766700d3ed7789767bc2b0295cdb76d6 100644 --- a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuMessageGateway.java +++ b/src/main/java/com/jimuqu/claw/channel/feishu/gateway/FeishuMessageGateway.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.channel.feishu; +package com.jimuqu.claw.channel.feishu.gateway; /** * 抽象飞书消息创建与更新能力,便于在单元测试中替换底层 SDK。 @@ -23,3 +23,4 @@ public interface FeishuMessageGateway { */ void patchCardMessage(String messageId, String cardContent) throws Exception; } + diff --git a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuSdkMessageGateway.java b/src/main/java/com/jimuqu/claw/channel/feishu/gateway/FeishuSdkMessageGateway.java similarity index 94% rename from src/main/java/com/jimuqu/claw/channel/feishu/FeishuSdkMessageGateway.java rename to src/main/java/com/jimuqu/claw/channel/feishu/gateway/FeishuSdkMessageGateway.java index 71dea869f620d485dfbfd1cd11a370bbd3f06616..fbd5838007affced2bb8a0aea9b92e193ee4b3c1 100644 --- a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuSdkMessageGateway.java +++ b/src/main/java/com/jimuqu/claw/channel/feishu/gateway/FeishuSdkMessageGateway.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.channel.feishu; +package com.jimuqu.claw.channel.feishu.gateway; import cn.hutool.core.util.StrUtil; import com.lark.oapi.Client; @@ -10,7 +10,7 @@ import com.lark.oapi.service.im.v1.model.CreateMessageResp; import com.lark.oapi.service.im.v1.model.PatchMessageReq; import com.lark.oapi.service.im.v1.model.PatchMessageReqBody; import com.lark.oapi.service.im.v1.model.PatchMessageResp; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.config.props.FeishuProperties; /** * 基于飞书 Java SDK 的消息网关实现。 @@ -24,7 +24,7 @@ public class FeishuSdkMessageGateway implements FeishuMessageGateway { * * @param properties 飞书配置 */ - public FeishuSdkMessageGateway(SolonClawProperties.Feishu properties) { + public FeishuSdkMessageGateway(FeishuProperties properties) { Client.Builder builder = Client.newBuilder(properties.getAppId(), properties.getAppSecret()); if (StrUtil.isNotBlank(properties.getBaseDomain())) { builder.openBaseUrl(properties.getBaseDomain().trim()); @@ -75,3 +75,5 @@ public class FeishuSdkMessageGateway implements FeishuMessageGateway { } } } + + diff --git a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuBotSender.java b/src/main/java/com/jimuqu/claw/channel/feishu/sender/FeishuBotSender.java similarity index 90% rename from src/main/java/com/jimuqu/claw/channel/feishu/FeishuBotSender.java rename to src/main/java/com/jimuqu/claw/channel/feishu/sender/FeishuBotSender.java index fc5487860dd8b0d94139ec4d8e5e05d5af11ffe9..7dc7560c8d62b9555e5dbec17f6cacd67c3fb0e8 100644 --- a/src/main/java/com/jimuqu/claw/channel/feishu/FeishuBotSender.java +++ b/src/main/java/com/jimuqu/claw/channel/feishu/sender/FeishuBotSender.java @@ -1,11 +1,13 @@ -package com.jimuqu.claw.channel.feishu; +package com.jimuqu.claw.channel.feishu.sender; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.channel.feishu.gateway.FeishuMessageGateway; +import com.jimuqu.claw.channel.feishu.gateway.FeishuSdkMessageGateway; +import com.jimuqu.claw.config.props.FeishuProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,7 +21,7 @@ public class FeishuBotSender { /** 日志记录器。 */ private static final Logger log = LoggerFactory.getLogger(FeishuBotSender.class); /** 飞书配置。 */ - private final SolonClawProperties.Feishu properties; + private final FeishuProperties properties; /** 飞书消息网关。 */ private final FeishuMessageGateway messageGateway; /** 运行任务对应的飞书消息 ID,用于流式 patch 更新。 */ @@ -30,7 +32,7 @@ public class FeishuBotSender { * * @param properties 飞书配置 */ - public FeishuBotSender(SolonClawProperties.Feishu properties) { + public FeishuBotSender(FeishuProperties properties) { this(properties, new FeishuSdkMessageGateway(properties)); } @@ -40,7 +42,7 @@ public class FeishuBotSender { * @param properties 飞书配置 * @param messageGateway 消息网关 */ - FeishuBotSender(SolonClawProperties.Feishu properties, FeishuMessageGateway messageGateway) { + public FeishuBotSender(FeishuProperties properties, FeishuMessageGateway messageGateway) { this.properties = properties; this.messageGateway = messageGateway; } @@ -125,7 +127,7 @@ public class FeishuBotSender { * @param content markdown 文本 * @return 卡片 JSON 字符串 */ - String cardMessageParam(String content) { + public String cardMessageParam(String content) { JSONObject root = new JSONObject(); root.put("schema", "2.0"); @@ -175,3 +177,6 @@ public class FeishuBotSender { return builder.toString().trim(); } } + + + diff --git a/src/main/java/com/jimuqu/claw/config/SolonClawConfig.java b/src/main/java/com/jimuqu/claw/config/SolonClawConfig.java index 0af1111c832a31b972547d46ccefe1dc1e5dee4d..ba510b59a3b6d136c7b7acc48909d028e32f7fe0 100644 --- a/src/main/java/com/jimuqu/claw/config/SolonClawConfig.java +++ b/src/main/java/com/jimuqu/claw/config/SolonClawConfig.java @@ -3,21 +3,21 @@ import cn.hutool.core.io.FileUtil; import com.jimuqu.claw.agent.channel.ChannelRegistry; import com.jimuqu.claw.agent.job.JobStoreService; import com.jimuqu.claw.agent.job.WorkspaceJobService; -import com.jimuqu.claw.agent.runtime.AgentRuntimeService; -import com.jimuqu.claw.agent.runtime.ConversationAgent; -import com.jimuqu.claw.agent.runtime.ConversationScheduler; -import com.jimuqu.claw.agent.runtime.HeartbeatService; -import com.jimuqu.claw.agent.runtime.SolonAiConversationAgent; +import com.jimuqu.claw.agent.runtime.impl.AgentRuntimeService; +import com.jimuqu.claw.agent.runtime.api.ConversationAgent; +import com.jimuqu.claw.agent.runtime.impl.ConversationScheduler; +import com.jimuqu.claw.agent.runtime.impl.HeartbeatService; +import com.jimuqu.claw.agent.runtime.impl.SolonAiConversationAgent; import com.jimuqu.claw.agent.store.RuntimeStoreService; import com.jimuqu.claw.agent.tool.JobTools; import com.jimuqu.claw.agent.tool.WorkspaceAgentTools; import com.jimuqu.claw.agent.workspace.AgentWorkspaceService; import com.jimuqu.claw.agent.workspace.WorkspacePromptService; -import com.jimuqu.claw.channel.dingtalk.DingTalkAccessTokenService; -import com.jimuqu.claw.channel.dingtalk.DingTalkChannelAdapter; -import com.jimuqu.claw.channel.dingtalk.DingTalkRobotSender; -import com.jimuqu.claw.channel.feishu.FeishuBotSender; -import com.jimuqu.claw.channel.feishu.FeishuChannelAdapter; +import com.jimuqu.claw.channel.dingtalk.service.DingTalkAccessTokenService; +import com.jimuqu.claw.channel.dingtalk.adapter.DingTalkChannelAdapter; +import com.jimuqu.claw.channel.dingtalk.sender.DingTalkRobotSender; +import com.jimuqu.claw.channel.feishu.sender.FeishuBotSender; +import com.jimuqu.claw.channel.feishu.adapter.FeishuChannelAdapter; import org.noear.solon.ai.skills.cli.CliSkillProvider; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.annotation.Bean; @@ -285,14 +285,12 @@ public class SolonClawConfig { @Bean(initMethod = "start", destroyMethod = "stop") public DingTalkChannelAdapter dingTalkChannelAdapter( AgentRuntimeService agentRuntimeService, - RuntimeStoreService runtimeStoreService, DingTalkRobotSender dingTalkRobotSender, ChannelRegistry channelRegistry, SolonClawProperties properties ) { DingTalkChannelAdapter adapter = new DingTalkChannelAdapter( agentRuntimeService, - runtimeStoreService, dingTalkRobotSender, properties.getChannels().getDingtalk() ); @@ -342,3 +340,5 @@ public class SolonClawConfig { return new HeartbeatService(agentRuntimeService, runtimeStoreService, properties); } } + + diff --git a/src/main/java/com/jimuqu/claw/config/SolonClawProperties.java b/src/main/java/com/jimuqu/claw/config/SolonClawProperties.java index 4dac46812f5bf0c56ecbaa9e3ff406d5b5fb15e7..b63af67782c423e2f4fcf84522a9f17e66b4674f 100644 --- a/src/main/java/com/jimuqu/claw/config/SolonClawProperties.java +++ b/src/main/java/com/jimuqu/claw/config/SolonClawProperties.java @@ -1,592 +1,24 @@ package com.jimuqu.claw.config; -import java.util.ArrayList; -import java.util.List; +import com.jimuqu.claw.config.props.AgentProperties; +import com.jimuqu.claw.config.props.ChannelsProperties; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; /** * 聚合 SolonClaw 项目的自定义配置。 */ -public class SolonClawProperties { +@Data +@NoArgsConstructor +public class SolonClawProperties implements Serializable { + private static final long serialVersionUID = 1L; + /** 工作区目录。 */ private String workspace = "./workspace"; /** Agent 相关配置。 */ - private Agent agent = new Agent(); + private AgentProperties agent = new AgentProperties(); /** 渠道相关配置。 */ - private Channels channels = new Channels(); - - /** - * 返回工作区目录。 - * - * @return 工作区目录 - */ - public String getWorkspace() { - return workspace; - } - - /** - * 设置工作区目录。 - * - * @param workspace 工作区目录 - */ - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - /** - * 返回 Agent 配置。 - * - * @return Agent 配置 - */ - public Agent getAgent() { - return agent; - } - - /** - * 设置 Agent 配置。 - * - * @param agent Agent 配置 - */ - public void setAgent(Agent agent) { - this.agent = agent; - } - - /** - * 返回渠道配置。 - * - * @return 渠道配置 - */ - public Channels getChannels() { - return channels; - } - - /** - * 设置渠道配置。 - * - * @param channels 渠道配置 - */ - public void setChannels(Channels channels) { - this.channels = channels; - } - - /** - * 描述 Agent 行为配置。 - */ - public static class Agent { - /** 基础系统提示词。 */ - private String systemPrompt; - /** 调度器配置。 */ - private Scheduler scheduler = new Scheduler(); - /** 工具配置。 */ - private Tools tools = new Tools(); - /** 心跳配置。 */ - private Heartbeat heartbeat = new Heartbeat(); - - /** - * 返回系统提示词。 - * - * @return 系统提示词 - */ - public String getSystemPrompt() { - return systemPrompt; - } - - /** - * 设置系统提示词。 - * - * @param systemPrompt 系统提示词 - */ - public void setSystemPrompt(String systemPrompt) { - this.systemPrompt = systemPrompt; - } - - /** - * 返回调度器配置。 - * - * @return 调度器配置 - */ - public Scheduler getScheduler() { - return scheduler; - } - - /** - * 设置调度器配置。 - * - * @param scheduler 调度器配置 - */ - public void setScheduler(Scheduler scheduler) { - this.scheduler = scheduler; - } - - /** - * 返回工具配置。 - * - * @return 工具配置 - */ - public Tools getTools() { - return tools; - } - - /** - * 设置工具配置。 - * - * @param tools 工具配置 - */ - public void setTools(Tools tools) { - this.tools = tools; - } - - /** - * 返回心跳配置。 - * - * @return 心跳配置 - */ - public Heartbeat getHeartbeat() { - return heartbeat; - } - - /** - * 设置心跳配置。 - * - * @param heartbeat 心跳配置 - */ - public void setHeartbeat(Heartbeat heartbeat) { - this.heartbeat = heartbeat; - } - } - - /** - * 描述工具能力配置。 - */ - public static class Tools { - /** CLI TerminalSkill 是否启用沙盒模式。 */ - private boolean sandboxMode = true; - - /** - * 返回 TerminalSkill 是否启用沙盒模式。 - * - * @return 若启用则返回 true - */ - public boolean isSandboxMode() { - return sandboxMode; - } - - /** - * 设置 TerminalSkill 是否启用沙盒模式。 - * - * @param sandboxMode 启用标记 - */ - public void setSandboxMode(boolean sandboxMode) { - this.sandboxMode = sandboxMode; - } - } - - /** - * 描述并发调度配置。 - */ - public static class Scheduler { - /** 单会话最大并发数。 */ - private int maxConcurrentPerConversation = 4; - /** 忙时是否立即回执确认消息。 */ - private boolean ackWhenBusy = true; - - /** - * 返回单会话最大并发数。 - * - * @return 单会话最大并发数 - */ - public int getMaxConcurrentPerConversation() { - return maxConcurrentPerConversation; - } - - /** - * 设置单会话最大并发数。 - * - * @param maxConcurrentPerConversation 单会话最大并发数 - */ - public void setMaxConcurrentPerConversation(int maxConcurrentPerConversation) { - this.maxConcurrentPerConversation = maxConcurrentPerConversation; - } - - /** - * 返回是否忙时回执。 - * - * @return 若启用则返回 true - */ - public boolean isAckWhenBusy() { - return ackWhenBusy; - } - - /** - * 设置是否忙时回执。 - * - * @param ackWhenBusy 忙时回执标记 - */ - public void setAckWhenBusy(boolean ackWhenBusy) { - this.ackWhenBusy = ackWhenBusy; - } - } - - /** - * 描述心跳任务配置。 - */ - public static class Heartbeat { - /** 是否启用心跳。 */ - private boolean enabled = true; - /** 心跳触发间隔,单位秒。 */ - private int intervalSeconds = 1800; - - /** - * 返回是否启用心跳。 - * - * @return 若启用则返回 true - */ - public boolean isEnabled() { - return enabled; - } - - /** - * 设置是否启用心跳。 - * - * @param enabled 启用标记 - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** - * 返回心跳间隔。 - * - * @return 心跳间隔秒数 - */ - public int getIntervalSeconds() { - return intervalSeconds; - } - - /** - * 设置心跳间隔。 - * - * @param intervalSeconds 心跳间隔秒数 - */ - public void setIntervalSeconds(int intervalSeconds) { - this.intervalSeconds = intervalSeconds; - } - } - - /** - * 描述所有渠道配置的聚合对象。 - */ - public static class Channels { - /** 飞书渠道配置。 */ - private Feishu feishu = new Feishu(); - /** 钉钉渠道配置。 */ - private DingTalk dingtalk = new DingTalk(); - - /** - * 返回飞书配置。 - * - * @return 飞书配置 - */ - public Feishu getFeishu() { - return feishu; - } - - /** - * 设置飞书配置。 - * - * @param feishu 飞书配置 - */ - public void setFeishu(Feishu feishu) { - this.feishu = feishu; - } - - /** - * 返回钉钉配置。 - * - * @return 钉钉配置 - */ - public DingTalk getDingtalk() { - return dingtalk; - } - - /** - * 设置钉钉配置。 - * - * @param dingtalk 钉钉配置 - */ - public void setDingtalk(DingTalk dingtalk) { - this.dingtalk = dingtalk; - } - } - - /** - * 描述飞书机器人配置。 - */ - public static class Feishu { - /** 是否启用飞书渠道。 */ - private boolean enabled; - /** 飞书 appId。 */ - private String appId = ""; - /** 飞书 appSecret。 */ - private String appSecret = ""; - /** 飞书开放平台域名。 */ - private String baseDomain = "https://open.feishu.cn"; - /** 是否启用基于卡片 patch 的流式更新。 */ - private boolean streamingReply = true; - /** 私聊允许列表。 */ - private List allowFrom = new ArrayList<>(); - /** 群聊允许列表。 */ - private List groupAllowFrom = new ArrayList<>(); - - /** - * 返回飞书渠道启用状态。 - * - * @return 若启用则返回 true - */ - public boolean isEnabled() { - return enabled; - } - - /** - * 设置飞书渠道启用状态。 - * - * @param enabled 启用标记 - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** - * 返回 appId。 - * - * @return appId - */ - public String getAppId() { - return appId; - } - - /** - * 设置 appId。 - * - * @param appId appId - */ - public void setAppId(String appId) { - this.appId = appId; - } - - /** - * 返回 appSecret。 - * - * @return appSecret - */ - public String getAppSecret() { - return appSecret; - } - - /** - * 设置 appSecret。 - * - * @param appSecret appSecret - */ - public void setAppSecret(String appSecret) { - this.appSecret = appSecret; - } - - /** - * 返回开放平台域名。 - * - * @return 开放平台域名 - */ - public String getBaseDomain() { - return baseDomain; - } - - /** - * 设置开放平台域名。 - * - * @param baseDomain 开放平台域名 - */ - public void setBaseDomain(String baseDomain) { - this.baseDomain = baseDomain; - } - - /** - * 返回是否启用流式更新。 - * - * @return 若启用则返回 true - */ - public boolean isStreamingReply() { - return streamingReply; - } - - /** - * 设置是否启用流式更新。 - * - * @param streamingReply 流式更新标记 - */ - public void setStreamingReply(boolean streamingReply) { - this.streamingReply = streamingReply; - } - - /** - * 返回私聊白名单。 - * - * @return 私聊白名单 - */ - public List getAllowFrom() { - return allowFrom; - } - - /** - * 设置私聊白名单。 - * - * @param allowFrom 私聊白名单 - */ - public void setAllowFrom(List allowFrom) { - this.allowFrom = allowFrom; - } - - /** - * 返回群聊白名单。 - * - * @return 群聊白名单 - */ - public List getGroupAllowFrom() { - return groupAllowFrom; - } - - /** - * 设置群聊白名单。 - * - * @param groupAllowFrom 群聊白名单 - */ - public void setGroupAllowFrom(List groupAllowFrom) { - this.groupAllowFrom = groupAllowFrom; - } - } - - /** - * 描述钉钉机器人配置。 - */ - public static class DingTalk { - /** 是否启用钉钉渠道。 */ - private boolean enabled; - /** 钉钉 clientId。 */ - private String clientId = ""; - /** 钉钉 clientSecret。 */ - private String clientSecret = ""; - /** 钉钉 robotCode。 */ - private String robotCode = ""; - /** 私聊允许列表。 */ - private List allowFrom = new ArrayList<>(); - /** 群聊允许列表。 */ - private List groupAllowFrom = new ArrayList<>(); - - /** - * 返回钉钉渠道启用状态。 - * - * @return 若启用则返回 true - */ - public boolean isEnabled() { - return enabled; - } - - /** - * 设置钉钉渠道启用状态。 - * - * @param enabled 启用标记 - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** - * 返回 clientId。 - * - * @return clientId - */ - public String getClientId() { - return clientId; - } - - /** - * 设置 clientId。 - * - * @param clientId clientId - */ - public void setClientId(String clientId) { - this.clientId = clientId; - } - - /** - * 返回 clientSecret。 - * - * @return clientSecret - */ - public String getClientSecret() { - return clientSecret; - } - - /** - * 设置 clientSecret。 - * - * @param clientSecret clientSecret - */ - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - /** - * 返回 robotCode。 - * - * @return robotCode - */ - public String getRobotCode() { - return robotCode; - } - - /** - * 设置 robotCode。 - * - * @param robotCode robotCode - */ - public void setRobotCode(String robotCode) { - this.robotCode = robotCode; - } - - /** - * 返回私聊白名单。 - * - * @return 私聊白名单 - */ - public List getAllowFrom() { - return allowFrom; - } - - /** - * 设置私聊白名单。 - * - * @param allowFrom 私聊白名单 - */ - public void setAllowFrom(List allowFrom) { - this.allowFrom = allowFrom; - } - - /** - * 返回群聊白名单。 - * - * @return 群聊白名单 - */ - public List getGroupAllowFrom() { - return groupAllowFrom; - } - - /** - * 设置群聊白名单。 - * - * @param groupAllowFrom 群聊白名单 - */ - public void setGroupAllowFrom(List groupAllowFrom) { - this.groupAllowFrom = groupAllowFrom; - } - } + private ChannelsProperties channels = new ChannelsProperties(); } diff --git a/src/main/java/com/jimuqu/claw/config/props/AgentProperties.java b/src/main/java/com/jimuqu/claw/config/props/AgentProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..dc5e4575629f76dd334bbd7a922b9cf5b759d476 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/AgentProperties.java @@ -0,0 +1,26 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述 Agent 行为配置。 + */ +@Data +@NoArgsConstructor +public class AgentProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 基础系统提示词。 */ + private String systemPrompt; + /** 调度器配置。 */ + private SchedulerProperties scheduler = new SchedulerProperties(); + /** 工具配置。 */ + private ToolsProperties tools = new ToolsProperties(); + /** 子任务治理配置。 */ + private SubtasksProperties subtasks = new SubtasksProperties(); + /** 心跳配置。 */ + private HeartbeatProperties heartbeat = new HeartbeatProperties(); +} diff --git a/src/main/java/com/jimuqu/claw/config/props/ChannelsProperties.java b/src/main/java/com/jimuqu/claw/config/props/ChannelsProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..304c584302143326ad004f6cf46cd3a396a2d974 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/ChannelsProperties.java @@ -0,0 +1,20 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述所有渠道配置的聚合对象。 + */ +@Data +@NoArgsConstructor +public class ChannelsProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 飞书渠道配置。 */ + private FeishuProperties feishu = new FeishuProperties(); + /** 钉钉渠道配置。 */ + private DingTalkProperties dingtalk = new DingTalkProperties(); +} diff --git a/src/main/java/com/jimuqu/claw/config/props/DingTalkProperties.java b/src/main/java/com/jimuqu/claw/config/props/DingTalkProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..a61ef1c077787942f55aa7f8a5be46fb0619635b --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/DingTalkProperties.java @@ -0,0 +1,30 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 描述钉钉机器人配置。 + */ +@Data +@NoArgsConstructor +public class DingTalkProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 是否启用钉钉渠道。 */ + private boolean enabled; + /** 钉钉 clientId。 */ + private String clientId = ""; + /** 钉钉 clientSecret。 */ + private String clientSecret = ""; + /** 钉钉 robotCode。 */ + private String robotCode = ""; + /** 私聊允许列表。 */ + private List allowFrom = new ArrayList(); + /** 群聊允许列表。 */ + private List groupAllowFrom = new ArrayList(); +} diff --git a/src/main/java/com/jimuqu/claw/config/props/FeishuProperties.java b/src/main/java/com/jimuqu/claw/config/props/FeishuProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..e0ac4bb5d617ba03b9fc17590b113a3661aba5e5 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/FeishuProperties.java @@ -0,0 +1,32 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 描述飞书机器人配置。 + */ +@Data +@NoArgsConstructor +public class FeishuProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 是否启用飞书渠道。 */ + private boolean enabled; + /** 飞书 appId。 */ + private String appId = ""; + /** 飞书 appSecret。 */ + private String appSecret = ""; + /** 飞书开放平台域名。 */ + private String baseDomain = "https://open.feishu.cn"; + /** 是否启用基于卡片 patch 的流式更新。 */ + private boolean streamingReply = true; + /** 私聊允许列表。 */ + private List allowFrom = new ArrayList(); + /** 群聊允许列表。 */ + private List groupAllowFrom = new ArrayList(); +} diff --git a/src/main/java/com/jimuqu/claw/config/props/HeartbeatProperties.java b/src/main/java/com/jimuqu/claw/config/props/HeartbeatProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..4e27c5e140e8473a5d49059166373a8a1413cc28 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/HeartbeatProperties.java @@ -0,0 +1,20 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述心跳任务配置。 + */ +@Data +@NoArgsConstructor +public class HeartbeatProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 是否启用心跳。 */ + private boolean enabled = true; + /** 心跳触发间隔,单位秒。 */ + private int intervalSeconds = 1800; +} diff --git a/src/main/java/com/jimuqu/claw/config/props/SchedulerProperties.java b/src/main/java/com/jimuqu/claw/config/props/SchedulerProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..603ec6183ea5975315879cff74ffb3fce4d22525 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/SchedulerProperties.java @@ -0,0 +1,20 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述并发调度配置。 + */ +@Data +@NoArgsConstructor +public class SchedulerProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 单会话最大并发数。 */ + private int maxConcurrentPerConversation = 4; + /** 忙时是否立即回执确认消息。 */ + private boolean ackWhenBusy = true; +} diff --git a/src/main/java/com/jimuqu/claw/config/props/SubtasksProperties.java b/src/main/java/com/jimuqu/claw/config/props/SubtasksProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..4c29ba55b44dbe083649240590eb750f951bcd29 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/SubtasksProperties.java @@ -0,0 +1,18 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述子任务运行时治理配置。 + */ +@Data +@NoArgsConstructor +public class SubtasksProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** 是否允许子任务继续派生新的子任务。 */ + private boolean allowNestedSpawn = true; +} diff --git a/src/main/java/com/jimuqu/claw/config/props/ToolsProperties.java b/src/main/java/com/jimuqu/claw/config/props/ToolsProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..dc8f3e46997a84946bfb70a03909a088eddfecbe --- /dev/null +++ b/src/main/java/com/jimuqu/claw/config/props/ToolsProperties.java @@ -0,0 +1,18 @@ +package com.jimuqu.claw.config.props; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述工具能力配置。 + */ +@Data +@NoArgsConstructor +public class ToolsProperties implements Serializable { + private static final long serialVersionUID = 1L; + + /** CLI TerminalSkill 是否启用沙盒模式。 */ + private boolean sandboxMode = true; +} diff --git a/src/main/java/com/jimuqu/claw/web/DebugChatRequest.java b/src/main/java/com/jimuqu/claw/web/DebugChatRequest.java deleted file mode 100644 index 6b4f168d53e02efae981dc1247c15074e7023326..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/web/DebugChatRequest.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jimuqu.claw.web; - -/** - * 描述调试页发起聊天请求时的参数。 - */ -public class DebugChatRequest { - /** 调试会话标识。 */ - private String sessionId; - /** 用户输入文本。 */ - private String message; - - /** - * 返回调试会话标识。 - * - * @return 调试会话标识 - */ - public String getSessionId() { - return sessionId; - } - - /** - * 设置调试会话标识。 - * - * @param sessionId 调试会话标识 - */ - public void setSessionId(String sessionId) { - this.sessionId = sessionId; - } - - /** - * 返回用户输入文本。 - * - * @return 用户输入文本 - */ - public String getMessage() { - return message; - } - - /** - * 设置用户输入文本。 - * - * @param message 用户输入文本 - */ - public void setMessage(String message) { - this.message = message; - } -} diff --git a/src/main/java/com/jimuqu/claw/web/DebugChatResponse.java b/src/main/java/com/jimuqu/claw/web/DebugChatResponse.java deleted file mode 100644 index 4ea03582ddd71717086c7a0fb7e0b110fcadefd4..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/web/DebugChatResponse.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.jimuqu.claw.web; - -/** - * 描述调试页提交消息后的响应体。 - */ -public class DebugChatResponse { - /** 新创建的运行任务标识。 */ - private String runId; - /** 所属内部会话键。 */ - private String sessionKey; - /** 当前运行状态。 */ - private String status; - - /** - * 创建一个空响应对象。 - */ - public DebugChatResponse() { - } - - /** - * 使用完整字段创建响应对象。 - * - * @param runId 运行任务标识 - * @param sessionKey 会话键 - * @param status 当前状态 - */ - public DebugChatResponse(String runId, String sessionKey, String status) { - this.runId = runId; - this.sessionKey = sessionKey; - this.status = status; - } - - /** - * 返回运行任务标识。 - * - * @return 运行任务标识 - */ - public String getRunId() { - return runId; - } - - /** - * 设置运行任务标识。 - * - * @param runId 运行任务标识 - */ - public void setRunId(String runId) { - this.runId = runId; - } - - /** - * 返回会话键。 - * - * @return 会话键 - */ - public String getSessionKey() { - return sessionKey; - } - - /** - * 设置会话键。 - * - * @param sessionKey 会话键 - */ - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - /** - * 返回当前状态。 - * - * @return 当前状态 - */ - public String getStatus() { - return status; - } - - /** - * 设置当前状态。 - * - * @param status 当前状态 - */ - public void setStatus(String status) { - this.status = status; - } -} diff --git a/src/main/java/com/jimuqu/claw/web/DebugRunEventsResponse.java b/src/main/java/com/jimuqu/claw/web/DebugRunEventsResponse.java deleted file mode 100644 index 9853488ec80a9030517f7376cb3d10d717e09413..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/web/DebugRunEventsResponse.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.jimuqu.claw.web; - -import com.jimuqu.claw.agent.model.RunEvent; - -import java.util.ArrayList; -import java.util.List; - -/** - * 描述调试页轮询运行事件时返回的数据。 - */ -public class DebugRunEventsResponse { - /** 本次返回的事件列表。 */ - private List events = new ArrayList<>(); - /** 当前已返回到的最后事件序号。 */ - private long lastSeq; - - /** - * 返回事件列表。 - * - * @return 事件列表 - */ - public List getEvents() { - return events; - } - - /** - * 设置事件列表。 - * - * @param events 事件列表 - */ - public void setEvents(List events) { - this.events = events; - } - - /** - * 返回最后事件序号。 - * - * @return 最后事件序号 - */ - public long getLastSeq() { - return lastSeq; - } - - /** - * 设置最后事件序号。 - * - * @param lastSeq 最后事件序号 - */ - public void setLastSeq(long lastSeq) { - this.lastSeq = lastSeq; - } -} diff --git a/src/main/java/com/jimuqu/claw/web/DebugRunResponse.java b/src/main/java/com/jimuqu/claw/web/DebugRunResponse.java deleted file mode 100644 index 2a2d17244e276e1361ddf1e2eec20e5dd7cf77c7..0000000000000000000000000000000000000000 --- a/src/main/java/com/jimuqu/claw/web/DebugRunResponse.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jimuqu.claw.web; - -import com.jimuqu.claw.agent.model.AgentRun; - -/** - * 描述调试页查询运行任务详情时返回的数据。 - */ -public class DebugRunResponse { - /** 当前运行任务详情。 */ - private AgentRun run; - - /** - * 创建一个空响应对象。 - */ - public DebugRunResponse() { - } - - /** - * 使用运行任务创建响应对象。 - * - * @param run 运行任务 - */ - public DebugRunResponse(AgentRun run) { - this.run = run; - } - - /** - * 返回运行任务。 - * - * @return 运行任务 - */ - public AgentRun getRun() { - return run; - } - - /** - * 设置运行任务。 - * - * @param run 运行任务 - */ - public void setRun(AgentRun run) { - this.run = run; - } -} diff --git a/src/main/java/com/jimuqu/claw/web/DebugChatController.java b/src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java similarity index 85% rename from src/main/java/com/jimuqu/claw/web/DebugChatController.java rename to src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java index 96ddbb150433005d2fc0d2f10945302208284eca..4bfbd6e5d16fd8057a0fc8bd02e6ec5075b996c4 100644 --- a/src/main/java/com/jimuqu/claw/web/DebugChatController.java +++ b/src/main/java/com/jimuqu/claw/web/controller/DebugChatController.java @@ -1,11 +1,16 @@ -package com.jimuqu.claw.web; +package com.jimuqu.claw.web.controller; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.model.RunEvent; -import com.jimuqu.claw.agent.runtime.AgentRuntimeService; -import com.jimuqu.claw.agent.runtime.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.model.event.RunEvent; +import com.jimuqu.claw.agent.runtime.impl.AgentRuntimeService; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; +import com.jimuqu.claw.web.dto.DebugChatRequest; +import com.jimuqu.claw.web.dto.DebugChatResponse; +import com.jimuqu.claw.web.dto.DebugChildRunsResponse; +import com.jimuqu.claw.web.dto.DebugRunEventsResponse; +import com.jimuqu.claw.web.dto.DebugRunResponse; import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Param; @@ -93,3 +98,6 @@ public class DebugChatController { return response; } } + + + diff --git a/src/main/java/com/jimuqu/claw/web/RootController.java b/src/main/java/com/jimuqu/claw/web/controller/RootController.java similarity index 92% rename from src/main/java/com/jimuqu/claw/web/RootController.java rename to src/main/java/com/jimuqu/claw/web/controller/RootController.java index 00ddc9ffcd84b51bf9bf469f9ac4e1d435e2ec1e..384a742abadd86e72a80f32d640313060d5e9d99 100644 --- a/src/main/java/com/jimuqu/claw/web/RootController.java +++ b/src/main/java/com/jimuqu/claw/web/controller/RootController.java @@ -1,4 +1,4 @@ -package com.jimuqu.claw.web; +package com.jimuqu.claw.web.controller; import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; @@ -20,3 +20,4 @@ public class RootController { ctx.forward("/index.html"); } } + diff --git a/src/main/java/com/jimuqu/claw/web/dto/DebugChatRequest.java b/src/main/java/com/jimuqu/claw/web/dto/DebugChatRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..12e86074195057a4a47b3ee9d4ac9efd318220d3 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/web/dto/DebugChatRequest.java @@ -0,0 +1,20 @@ +package com.jimuqu.claw.web.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述调试页发起聊天请求时的参数。 + */ +@Data +@NoArgsConstructor +public class DebugChatRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** 调试会话标识。 */ + private String sessionId; + /** 用户输入文本。 */ + private String message; +} diff --git a/src/main/java/com/jimuqu/claw/web/dto/DebugChatResponse.java b/src/main/java/com/jimuqu/claw/web/dto/DebugChatResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..0103639373f905f9d022898def314ccf221ac71e --- /dev/null +++ b/src/main/java/com/jimuqu/claw/web/dto/DebugChatResponse.java @@ -0,0 +1,24 @@ +package com.jimuqu.claw.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述调试页提交消息后的响应体。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DebugChatResponse implements Serializable { + private static final long serialVersionUID = 1L; + + /** 新创建的运行任务标识。 */ + private String runId; + /** 所属内部会话键。 */ + private String sessionKey; + /** 当前运行状态。 */ + private String status; +} diff --git a/src/main/java/com/jimuqu/claw/web/DebugChildRunsResponse.java b/src/main/java/com/jimuqu/claw/web/dto/DebugChildRunsResponse.java similarity index 30% rename from src/main/java/com/jimuqu/claw/web/DebugChildRunsResponse.java rename to src/main/java/com/jimuqu/claw/web/dto/DebugChildRunsResponse.java index 5a6708036f2c5649aff11495e5a5872974affc8c..7abc466c5ccee086cc9cd60db39c4b0b6d5752e7 100644 --- a/src/main/java/com/jimuqu/claw/web/DebugChildRunsResponse.java +++ b/src/main/java/com/jimuqu/claw/web/dto/DebugChildRunsResponse.java @@ -1,33 +1,24 @@ -package com.jimuqu.claw.web; +package com.jimuqu.claw.web.dto; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.runtime.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * 描述调试页查询父子任务关系时返回的数据。 */ -public class DebugChildRunsResponse { +@Data +@NoArgsConstructor +public class DebugChildRunsResponse implements Serializable { + private static final long serialVersionUID = 1L; + /** 父运行下的子任务列表。 */ - private List children = new ArrayList<>(); + private List children = new ArrayList(); /** 子任务聚合摘要。 */ private ParentRunChildrenSummary summary; - - public List getChildren() { - return children; - } - - public void setChildren(List children) { - this.children = children; - } - - public ParentRunChildrenSummary getSummary() { - return summary; - } - - public void setSummary(ParentRunChildrenSummary summary) { - this.summary = summary; - } } diff --git a/src/main/java/com/jimuqu/claw/web/dto/DebugRunEventsResponse.java b/src/main/java/com/jimuqu/claw/web/dto/DebugRunEventsResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..eccfa809bb891bcbd8d637ceb8465f5aaf651196 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/web/dto/DebugRunEventsResponse.java @@ -0,0 +1,23 @@ +package com.jimuqu.claw.web.dto; + +import com.jimuqu.claw.agent.model.event.RunEvent; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 描述调试页轮询运行事件时返回的数据。 + */ +@Data +@NoArgsConstructor +public class DebugRunEventsResponse implements Serializable { + private static final long serialVersionUID = 1L; + + /** 本次返回的事件列表。 */ + private List events = new ArrayList(); + /** 当前已返回到的最后事件序号。 */ + private long lastSeq; +} diff --git a/src/main/java/com/jimuqu/claw/web/dto/DebugRunResponse.java b/src/main/java/com/jimuqu/claw/web/dto/DebugRunResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..fff6609166c1e3ad6bbadf6cdbb263fa653f8f96 --- /dev/null +++ b/src/main/java/com/jimuqu/claw/web/dto/DebugRunResponse.java @@ -0,0 +1,21 @@ +package com.jimuqu.claw.web.dto; + +import com.jimuqu.claw.agent.model.run.AgentRun; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 描述调试页查询运行任务详情时返回的数据。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DebugRunResponse implements Serializable { + private static final long serialVersionUID = 1L; + + /** 当前运行任务详情。 */ + private AgentRun run; +} diff --git a/src/main/resources/app.yml b/src/main/resources/app.yml index eec6bc558d9895e3d60c64bb0a4ba854c077a338..442b57da2e2aa7d0ed1cfda94d8a7478a221989f 100644 --- a/src/main/resources/app.yml +++ b/src/main/resources/app.yml @@ -46,6 +46,11 @@ solonclaw: # true: 只允许工作区相对路径、~/ 和 @skills 逻辑路径;禁止绝对路径与越界访问 # false: 允许绝对路径,CLI 能力更开放 sandboxMode: true + subtasks: + # 是否允许子任务继续派生新的子任务 + # true: 默认允许子任务继续拆分 + # false: 可用于收紧编排,避免长任务出现无边界扇出 + allowNestedSpawn: true heartbeat: enabled: true intervalSeconds: 1800 diff --git a/src/main/resources/template/AGENTS.md b/src/main/resources/template/AGENTS.md index 962e2515c06498123c2bf8de6fc8729bb1a05099..2b7e365141922d97652fb9c60f942077745e7438 100644 --- a/src/main/resources/template/AGENTS.md +++ b/src/main/resources/template/AGENTS.md @@ -83,6 +83,14 @@ - 任务结束后应清理无用临时文件,避免污染工作区 - 可以在工作区内自由阅读、探索、整理和学习 +## 长任务与子任务 + +- 遇到多步骤、长时间、可并行或跨多个模块的任务时,优先先拆解,再决定是否派生子任务 +- 适合派生子任务的场景包括:部署启动、编译测试、日志排查、并行查资料、分模块检查代码 +- 子任务描述应保持单一、明确、可执行,不要把整段原始对话无脑转发 +- 子任务完成后,父任务应负责统一汇总和最终对外回复 +- 不要为了很小的动作滥用子任务,也不要无边界连续扇出 + ## Heartbeat - 收到 heartbeat 时,优先读取 `HEARTBEAT.md` diff --git a/src/test/java/com/jimuqu/claw/agent/job/JobStoreServiceTest.java b/src/test/java/com/jimuqu/claw/agent/job/JobStoreServiceTest.java index 8c818f8508c0e8fe31d4a0296b16a188e631eeb5..e1122e535c1c98dd046c22a686355fb702606ae8 100644 --- a/src/test/java/com/jimuqu/claw/agent/job/JobStoreServiceTest.java +++ b/src/test/java/com/jimuqu/claw/agent/job/JobStoreServiceTest.java @@ -1,8 +1,8 @@ package com.jimuqu.claw.agent.job; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.ReplyTarget; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.route.ReplyTarget; import com.jimuqu.claw.agent.workspace.AgentWorkspaceService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -40,3 +40,4 @@ class JobStoreServiceTest { assertTrue(storeService.getJobsFile().exists()); } } + diff --git a/src/test/java/com/jimuqu/claw/agent/runtime/AgentRuntimeServiceTest.java b/src/test/java/com/jimuqu/claw/agent/runtime/AgentRuntimeServiceTest.java index f1f50cda97e32e5de083c64347f4c8d5697d1918..f319013e61cd8b914c45e9b9ba7a28a3654247d1 100644 --- a/src/test/java/com/jimuqu/claw/agent/runtime/AgentRuntimeServiceTest.java +++ b/src/test/java/com/jimuqu/claw/agent/runtime/AgentRuntimeServiceTest.java @@ -2,17 +2,22 @@ package com.jimuqu.claw.agent.runtime; import com.jimuqu.claw.agent.channel.ChannelAdapter; import com.jimuqu.claw.agent.channel.ChannelRegistry; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationEvent; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.agent.model.InboundTriggerType; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.agent.model.RunStatus; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.event.ConversationEvent; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.model.enums.RunStatus; +import com.jimuqu.claw.agent.runtime.api.ConversationAgent; +import com.jimuqu.claw.agent.runtime.impl.AgentRuntimeService; +import com.jimuqu.claw.agent.runtime.impl.ConversationScheduler; +import com.jimuqu.claw.agent.runtime.support.ConversationExecutionRequest; +import com.jimuqu.claw.agent.runtime.support.NotificationResult; import com.jimuqu.claw.agent.store.RuntimeStoreService; -import com.jimuqu.claw.agent.runtime.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; import com.jimuqu.claw.config.SolonClawProperties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -154,6 +159,60 @@ class AgentRuntimeServiceTest { } } + /** + * 验证子任务默认不能继续派生新的子任务,避免无边界扇出。 + * + * @throws Exception 执行异常 + */ + @Test + void childRunCannotSpawnNestedChildByDefault() throws Exception { + RuntimeStoreService store = new RuntimeStoreService(tempDir.toFile()); + ConversationScheduler scheduler = new ConversationScheduler(1); + ChannelRegistry registry = new ChannelRegistry(); + RecordingChannelAdapter adapter = new RecordingChannelAdapter(); + registry.register(adapter); + + ConversationAgent conversationAgent = (request, progressConsumer) -> { + String message = request.getCurrentMessage(); + if ("question-parent-nested".equals(message)) { + request.getSpawnTaskSupport().spawnTask("child-needs-more"); + return "parent-waiting"; + } + if ("child-needs-more".equals(message)) { + try { + request.getSpawnTaskSupport().spawnTask("nested-child"); + return "nested-allowed"; + } catch (IllegalStateException e) { + return "child-blocked:" + e.getMessage(); + } + } + if (message != null && message.contains("[内部事件] 子任务已完成")) { + return message.contains("当前子任务默认禁止继续派生子任务") + ? "parent-saw-nested-block" + : "parent-missed-nested-block"; + } + return "reply-" + message; + }; + + SolonClawProperties properties = new SolonClawProperties(); + properties.getAgent().getScheduler().setMaxConcurrentPerConversation(1); + properties.getAgent().getScheduler().setAckWhenBusy(false); + properties.getAgent().getSubtasks().setAllowNestedSpawn(false); + + try { + AgentRuntimeService runtimeService = new AgentRuntimeService(conversationAgent, store, scheduler, registry, properties); + String parentRunId = runtimeService.submitInbound(inbound("msg-parent-nested", "question-parent-nested")); + assertNotNull(parentRunId); + + assertTrue(waitUntil(() -> adapter.messages.contains("parent-saw-nested-block"), 5000)); + assertEquals(1, runtimeService.listChildRuns(parentRunId, null).size()); + assertTrue(store.getRunEvents(runtimeService.listChildRuns(parentRunId, null).get(0).getRunId(), 0).stream() + .anyMatch(event -> "spawn_task_blocked".equals(event.getEventType()))); + } finally { + scheduler.shutdown(); + } + } + /** * 验证后续一句“看看上个任务的情况”可以通过查询能力读取最近子任务状态。 * @@ -203,6 +262,68 @@ class AgentRuntimeServiceTest { } } + /** + * 验证子任务完成后,父运行会记录更清晰的调试事件,并向 continuation 注入结构化汇总。 + * + * @throws Exception 执行异常 + */ + @Test + void childCompletionAddsParentDebugEventsAndStructuredSummary() throws Exception { + RuntimeStoreService store = new RuntimeStoreService(tempDir.toFile()); + ConversationScheduler scheduler = new ConversationScheduler(1); + ChannelRegistry registry = new ChannelRegistry(); + RecordingChannelAdapter adapter = new RecordingChannelAdapter(); + registry.register(adapter); + + ConversationAgent conversationAgent = (request, progressConsumer) -> { + String message = request.getCurrentMessage(); + if ("question-parent-summary".equals(message)) { + request.getSpawnTaskSupport().spawnTask("summary-child"); + return "parent-waiting"; + } + if ("summary-child".equals(message)) { + return "summary-child-result"; + } + if (message != null && message.contains("[内部事件] 子任务已完成")) { + return message.contains("全部子任务汇总: total=1") + && message.contains("FINAL_REPLY_ONCE:") + && message.contains("NO_REPLY") + ? "parent-saw-summary-guidance" + : "parent-missed-summary-guidance"; + } + return "reply-" + message; + }; + + SolonClawProperties properties = new SolonClawProperties(); + properties.getAgent().getScheduler().setMaxConcurrentPerConversation(1); + properties.getAgent().getScheduler().setAckWhenBusy(false); + + try { + AgentRuntimeService runtimeService = new AgentRuntimeService(conversationAgent, store, scheduler, registry, properties); + String parentRunId = runtimeService.submitInbound(inbound("msg-parent-summary", "question-parent-summary")); + assertNotNull(parentRunId); + + assertTrue(waitUntil(() -> adapter.messages.contains("parent-saw-summary-guidance"), 5000)); + + List eventTypes = store.getRunEvents(parentRunId, 0).stream() + .map(event -> event.getEventType()) + .collect(java.util.stream.Collectors.toList()); + assertTrue(eventTypes.contains("child_completion_received")); + assertTrue(eventTypes.contains("children_all_completed")); + assertTrue(eventTypes.contains("child_continuation_submitted")); + + List events = store.readConversationEvents("dingtalk:group:group-1"); + assertTrue(events.stream().anyMatch(event -> + "system_event".equals(event.getEventType()) + && event.getContent() != null + && event.getContent().contains("全部子任务汇总: total=1") + && event.getContent().contains("FINAL_REPLY_ONCE:") + && event.getContent().contains("NO_REPLY"))); + } finally { + scheduler.shutdown(); + } + } + /** * 验证当前运行可通过主动通知能力直接向当前会话用户发消息。 * @@ -665,3 +786,5 @@ class AgentRuntimeServiceTest { } } } + + diff --git a/src/test/java/com/jimuqu/claw/agent/runtime/HeartbeatServiceTest.java b/src/test/java/com/jimuqu/claw/agent/runtime/HeartbeatServiceTest.java index 2485e5ddf382b13a64bad7fd8013e4b44c8c624c..2e3f01b7f8070766536f159c7b0aae4809f1acaf 100644 --- a/src/test/java/com/jimuqu/claw/agent/runtime/HeartbeatServiceTest.java +++ b/src/test/java/com/jimuqu/claw/agent/runtime/HeartbeatServiceTest.java @@ -3,10 +3,14 @@ package com.jimuqu.claw.agent.runtime; import cn.hutool.core.io.FileUtil; import com.jimuqu.claw.agent.channel.ChannelAdapter; import com.jimuqu.claw.agent.channel.ChannelRegistry; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.runtime.api.ConversationAgent; +import com.jimuqu.claw.agent.runtime.impl.AgentRuntimeService; +import com.jimuqu.claw.agent.runtime.impl.ConversationScheduler; +import com.jimuqu.claw.agent.runtime.impl.HeartbeatService; import com.jimuqu.claw.agent.store.RuntimeStoreService; import com.jimuqu.claw.config.SolonClawProperties; import org.junit.jupiter.api.Test; @@ -100,3 +104,4 @@ class HeartbeatServiceTest { } } } + diff --git a/src/test/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSessionTest.java b/src/test/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSessionTest.java index 67e95d0844f7d21ec1d8a08359555d00f13d89bd..9342b494d367d1c65145889c02e53580a14a95ea 100644 --- a/src/test/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSessionTest.java +++ b/src/test/java/com/jimuqu/claw/agent/runtime/SystemAwareAgentSessionTest.java @@ -1,5 +1,6 @@ package com.jimuqu.claw.agent.runtime; +import com.jimuqu.claw.agent.runtime.support.SystemAwareAgentSession; import org.junit.jupiter.api.Test; import org.noear.solon.ai.chat.message.ChatMessage; diff --git a/src/test/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulatorTest.java b/src/test/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulatorTest.java index a967c5e66288be9d2bdfa5a26487a0e613ede59d..25e5ff504fd3dd87c4aa4a84be3037c66b01cf09 100644 --- a/src/test/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulatorTest.java +++ b/src/test/java/com/jimuqu/claw/agent/runtime/VisibleProgressAccumulatorTest.java @@ -1,5 +1,6 @@ package com.jimuqu.claw.agent.runtime; +import com.jimuqu.claw.agent.runtime.support.VisibleProgressAccumulator; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/com/jimuqu/claw/agent/store/RuntimeStoreServiceTest.java b/src/test/java/com/jimuqu/claw/agent/store/RuntimeStoreServiceTest.java index efb92fc6afc6c17b49c92486c0edd9469e26b1ac..b5d7e7c04f06cefeacedce77f2a24bd37e285947 100644 --- a/src/test/java/com/jimuqu/claw/agent/store/RuntimeStoreServiceTest.java +++ b/src/test/java/com/jimuqu/claw/agent/store/RuntimeStoreServiceTest.java @@ -1,15 +1,15 @@ package com.jimuqu.claw.agent.store; -import com.jimuqu.claw.agent.model.AgentRun; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationEvent; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.agent.model.InboundTriggerType; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.agent.model.RunEvent; -import com.jimuqu.claw.agent.model.RunStatus; -import com.jimuqu.claw.agent.runtime.ParentRunChildrenSummary; +import com.jimuqu.claw.agent.model.run.AgentRun; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.event.ConversationEvent; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.agent.model.enums.InboundTriggerType; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.agent.model.event.RunEvent; +import com.jimuqu.claw.agent.model.enums.RunStatus; +import com.jimuqu.claw.agent.runtime.support.ParentRunChildrenSummary; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.noear.solon.ai.chat.message.ChatMessage; @@ -276,3 +276,5 @@ class RuntimeStoreServiceTest { return envelope; } } + + diff --git a/src/test/java/com/jimuqu/claw/agent/workspace/WorkspacePromptServiceTest.java b/src/test/java/com/jimuqu/claw/agent/workspace/WorkspacePromptServiceTest.java index 627c68b2ffb2c154e5823c020fa0e117d56b2abb..72b3ee4c3abb622bd9e81b3c081ad10124d0a37c 100644 --- a/src/test/java/com/jimuqu/claw/agent/workspace/WorkspacePromptServiceTest.java +++ b/src/test/java/com/jimuqu/claw/agent/workspace/WorkspacePromptServiceTest.java @@ -1,6 +1,7 @@ package com.jimuqu.claw.agent.workspace; import cn.hutool.core.io.FileUtil; +import com.jimuqu.claw.agent.runtime.support.ConversationExecutionRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -66,6 +67,45 @@ class WorkspacePromptServiceTest { assertEquals("Xiaolongxia", promptService.resolveAgentName()); } + /** + * 验证子任务模式会收敛提示词上下文,只保留完成子任务所需的最小信息。 + * + * @param tempDir 临时工作区 + */ + @Test + void childRunPromptUsesReducedWorkspaceContext(@TempDir Path tempDir) { + AgentWorkspaceService workspaceService = new AgentWorkspaceService(tempDir.toString()); + WorkspacePromptService promptService = new WorkspacePromptService(workspaceService, "基础系统提示"); + + FileUtil.writeUtf8String("# AGENTS\nAGENTS-ONLY-CONTENT", workspaceService.fileInWorkspace(WorkspacePromptService.AGENTS_FILE)); + FileUtil.writeUtf8String("# SOUL\nSOUL-ONLY-CONTENT", workspaceService.fileInWorkspace(WorkspacePromptService.SOUL_FILE)); + FileUtil.writeUtf8String("# USER\nUSER-ONLY-CONTENT", workspaceService.fileInWorkspace(WorkspacePromptService.USER_FILE)); + FileUtil.writeUtf8String("# TOOLS\nTOOLS-ONLY-CONTENT", workspaceService.fileInWorkspace(WorkspacePromptService.TOOLS_FILE)); + FileUtil.writeUtf8String("# BOOTSTRAP\nBOOTSTRAP-ONLY-CONTENT", workspaceService.fileInWorkspace(WorkspacePromptService.BOOTSTRAP_FILE)); + FileUtil.writeUtf8String("# MEMORY\nMEMORY-ONLY-CONTENT", workspaceService.fileInWorkspace(WorkspacePromptService.MEMORY_FILE)); + LocalDate today = LocalDate.now(); + FileUtil.writeUtf8String( + "# DAILY\nDAILY-ONLY-CONTENT", + workspaceService.fileInWorkspace("memory/" + DateTimeFormatter.ISO_LOCAL_DATE.format(today) + ".md") + ); + + ConversationExecutionRequest request = new ConversationExecutionRequest(); + request.setChildRun(true); + request.setParentRunId("parent-run-1"); + + String prompt = promptService.buildSystemPrompt(request); + + assertTrue(prompt.contains("## 子任务模式")); + assertTrue(prompt.contains("当前父运行: parent-run-1")); + assertTrue(prompt.contains("AGENTS-ONLY-CONTENT")); + assertTrue(prompt.contains("TOOLS-ONLY-CONTENT")); + assertFalse(prompt.contains("SOUL-ONLY-CONTENT")); + assertFalse(prompt.contains("USER-ONLY-CONTENT")); + assertFalse(prompt.contains("BOOTSTRAP-ONLY-CONTENT")); + assertFalse(prompt.contains("MEMORY-ONLY-CONTENT")); + assertFalse(prompt.contains("DAILY-ONLY-CONTENT")); + } + /** * 验证全新工作区会初始化内置模板,并包含首次对话引导文件。 * @@ -99,5 +139,7 @@ class WorkspacePromptServiceTest { assertTrue(prompt.contains("你是在 SolonClaw 内运行的个人助理。")); assertTrue(prompt.contains("## 工具使用")); + assertTrue(prompt.contains("## 长任务与子任务")); } } + diff --git a/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapterTest.java b/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapterTest.java index 21c3ac8aa3cffac0c72dfa7c2f4af073ee33f4ae..2827e9b024e7856ceb5648189ea1fa9f60ec24b0 100644 --- a/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapterTest.java +++ b/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkChannelAdapterTest.java @@ -2,9 +2,10 @@ package com.jimuqu.claw.channel.dingtalk; import com.dingtalk.open.app.api.models.bot.ChatbotMessage; import com.dingtalk.open.app.api.models.bot.MessageContent; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.channel.dingtalk.adapter.DingTalkChannelAdapter; +import com.jimuqu.claw.config.props.DingTalkProperties; import org.junit.jupiter.api.Test; import java.util.Collections; @@ -22,10 +23,10 @@ class DingTalkChannelAdapterTest { */ @Test void mapsGroupMessageIntoGroupSession() { - SolonClawProperties.DingTalk properties = new SolonClawProperties.DingTalk(); + DingTalkProperties properties = new DingTalkProperties(); properties.setGroupAllowFrom(Collections.singletonList("cid-group")); - DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, null, properties); + DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, properties); InboundEnvelope inboundEnvelope = adapter.toInboundEnvelope(groupMessage("cid-group", "staff-1", "群消息")); assertEquals(ConversationType.GROUP, inboundEnvelope.getConversationType()); @@ -38,10 +39,10 @@ class DingTalkChannelAdapterTest { */ @Test void mapsPrivateMessageIntoPrivateSession() { - SolonClawProperties.DingTalk properties = new SolonClawProperties.DingTalk(); + DingTalkProperties properties = new DingTalkProperties(); properties.setAllowFrom(Collections.singletonList("staff-private")); - DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, null, properties); + DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, properties); InboundEnvelope inboundEnvelope = adapter.toInboundEnvelope(privateMessage("cid-private", "staff-private", "私聊消息")); assertEquals(ConversationType.PRIVATE, inboundEnvelope.getConversationType()); @@ -54,9 +55,9 @@ class DingTalkChannelAdapterTest { */ @Test void allowsGroupMessageWhenAllowListIsEmpty() { - SolonClawProperties.DingTalk properties = new SolonClawProperties.DingTalk(); + DingTalkProperties properties = new DingTalkProperties(); - DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, null, properties); + DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, properties); InboundEnvelope inboundEnvelope = adapter.toInboundEnvelope(groupMessage("cid-other", "staff-1", "未授权群")); assertNotNull(inboundEnvelope); @@ -68,10 +69,10 @@ class DingTalkChannelAdapterTest { */ @Test void rejectsMessageOutsideAllowListWhenConfigured() { - SolonClawProperties.DingTalk properties = new SolonClawProperties.DingTalk(); + DingTalkProperties properties = new DingTalkProperties(); properties.setGroupAllowFrom(Collections.singletonList("cid-group")); - DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, null, properties); + DingTalkChannelAdapter adapter = new DingTalkChannelAdapter(null, null, properties); assertNull(adapter.toInboundEnvelope(groupMessage("cid-other", "staff-1", "未授权群"))); } @@ -124,3 +125,6 @@ class DingTalkChannelAdapterTest { return message; } } + + + diff --git a/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSenderTest.java b/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSenderTest.java index 3ca2cba4c580940f2c2479bae9a64891813ddd25..af3d819ec8291cdecef718b9fe62114b671bfa00 100644 --- a/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSenderTest.java +++ b/src/test/java/com/jimuqu/claw/channel/dingtalk/DingTalkRobotSenderTest.java @@ -1,7 +1,8 @@ package com.jimuqu.claw.channel.dingtalk; import com.alibaba.fastjson.JSONObject; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.channel.dingtalk.sender.DingTalkRobotSender; +import com.jimuqu.claw.config.props.DingTalkProperties; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -10,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class DingTalkRobotSenderTest { @Test void buildsMarkdownPayloadFromContent() throws Exception { - DingTalkRobotSender sender = new DingTalkRobotSender(null, new SolonClawProperties.DingTalk(), null); + DingTalkRobotSender sender = new DingTalkRobotSender(null, new DingTalkProperties(), null); String payload = sender.markdownMessageParam("#### 杭州天气\n> 9度,西北风1级"); JSONObject json = JSONObject.parseObject(payload); @@ -22,8 +23,10 @@ class DingTalkRobotSenderTest { @Test void fallsBackToDefaultTitleWhenContentIsBlank() throws Exception { - DingTalkRobotSender sender = new DingTalkRobotSender(null, new SolonClawProperties.DingTalk(), null); + DingTalkRobotSender sender = new DingTalkRobotSender(null, new DingTalkProperties(), null); assertEquals("SolonClaw", sender.resolveMarkdownTitle(" ")); } } + + diff --git a/src/test/java/com/jimuqu/claw/channel/feishu/FeishuBotSenderTest.java b/src/test/java/com/jimuqu/claw/channel/feishu/FeishuBotSenderTest.java index ba862ccc246f3f6d4fd74cff5a4f04a397713727..b7131e4c304f0e55f497490fd9942be789e07a65 100644 --- a/src/test/java/com/jimuqu/claw/channel/feishu/FeishuBotSenderTest.java +++ b/src/test/java/com/jimuqu/claw/channel/feishu/FeishuBotSenderTest.java @@ -1,11 +1,13 @@ package com.jimuqu.claw.channel.feishu; import com.alibaba.fastjson.JSONObject; -import com.jimuqu.claw.agent.model.ChannelType; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.OutboundEnvelope; -import com.jimuqu.claw.agent.model.ReplyTarget; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.enums.ChannelType; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.OutboundEnvelope; +import com.jimuqu.claw.agent.model.route.ReplyTarget; +import com.jimuqu.claw.channel.feishu.gateway.FeishuMessageGateway; +import com.jimuqu.claw.channel.feishu.sender.FeishuBotSender; +import com.jimuqu.claw.config.props.FeishuProperties; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -17,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class FeishuBotSenderTest { @Test void buildsMarkdownCardPayloadFromContent() { - FeishuBotSender sender = new FeishuBotSender(new SolonClawProperties.Feishu(), new RecordingGateway()); + FeishuBotSender sender = new FeishuBotSender(new FeishuProperties(), new RecordingGateway()); String payload = sender.cardMessageParam("#### 杭州天气\n> 9度,西北风1级"); JSONObject root = JSONObject.parseObject(payload); @@ -36,7 +38,7 @@ class FeishuBotSenderTest { @Test void patchesExistingProgressMessageForSameRun() { - SolonClawProperties.Feishu properties = new SolonClawProperties.Feishu(); + FeishuProperties properties = new FeishuProperties(); properties.setStreamingReply(true); RecordingGateway gateway = new RecordingGateway(); FeishuBotSender sender = new FeishuBotSender(properties, gateway); @@ -82,3 +84,6 @@ class FeishuBotSenderTest { } } } + + + diff --git a/src/test/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapterTest.java b/src/test/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapterTest.java index 218c787cb8aff294dad6bea209328b65e69070e4..9f118b8a86e2ac10b4885713fd70c1ca43af7e99 100644 --- a/src/test/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapterTest.java +++ b/src/test/java/com/jimuqu/claw/channel/feishu/FeishuChannelAdapterTest.java @@ -6,9 +6,10 @@ import com.lark.oapi.service.im.v1.model.EventSender; import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1; import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1Data; import com.lark.oapi.service.im.v1.model.UserId; -import com.jimuqu.claw.agent.model.ConversationType; -import com.jimuqu.claw.agent.model.InboundEnvelope; -import com.jimuqu.claw.config.SolonClawProperties; +import com.jimuqu.claw.agent.model.enums.ConversationType; +import com.jimuqu.claw.agent.model.envelope.InboundEnvelope; +import com.jimuqu.claw.channel.feishu.adapter.FeishuChannelAdapter; +import com.jimuqu.claw.config.props.FeishuProperties; import org.junit.jupiter.api.Test; import java.util.Collections; @@ -20,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; class FeishuChannelAdapterTest { @Test void mapsGroupMessageIntoGroupSession() { - SolonClawProperties.Feishu properties = new SolonClawProperties.Feishu(); + FeishuProperties properties = new FeishuProperties(); properties.setGroupAllowFrom(Collections.singletonList("oc-group")); FeishuChannelAdapter adapter = new FeishuChannelAdapter(null, null, properties); @@ -34,7 +35,7 @@ class FeishuChannelAdapterTest { @Test void mapsPrivateMessageIntoPrivateSession() { - SolonClawProperties.Feishu properties = new SolonClawProperties.Feishu(); + FeishuProperties properties = new FeishuProperties(); properties.setAllowFrom(Collections.singletonList("ou-private")); FeishuChannelAdapter adapter = new FeishuChannelAdapter(null, null, properties); @@ -48,7 +49,7 @@ class FeishuChannelAdapterTest { @Test void rejectsMessageOutsideAllowListWhenConfigured() { - SolonClawProperties.Feishu properties = new SolonClawProperties.Feishu(); + FeishuProperties properties = new FeishuProperties(); properties.setGroupAllowFrom(Collections.singletonList("oc-group")); FeishuChannelAdapter adapter = new FeishuChannelAdapter(null, null, properties); @@ -58,7 +59,7 @@ class FeishuChannelAdapterTest { @Test void extractsPlainTextFromPostMessage() { - SolonClawProperties.Feishu properties = new SolonClawProperties.Feishu(); + FeishuProperties properties = new FeishuProperties(); FeishuChannelAdapter adapter = new FeishuChannelAdapter(null, null, properties); InboundEnvelope inboundEnvelope = adapter.toInboundEnvelope(messageEvent("oc-post", "ou-2", "group", "post", postContent("标题", "正文"))); @@ -109,3 +110,6 @@ class FeishuChannelAdapterTest { return post.toJSONString(); } } + + +