1 line
734 KiB
JSON
1 line
734 KiB
JSON
{"repo": "chaineasy-ai/alice-agent", "files": {"alice-facade-cmd/src/main/java/org/cland/alice/facade/cmd/AliceCliLauncher.java": {"8ebb2c40d3906a86e5a1eaa8ea381b21d0df9bd9": {"t": 1785745680.1727407, "n": 24154, "h": "c44e229fbcac68d6", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 3a. Resume 命令 — 走 Agent 执行(通过 ExecutionCoordinator)\n if (config.resumeMode()) {\n // resume 命令可能带 --list 标志,直接列表展示\n if (config.resumeList()) {\n return handleResumeList(config);\n }\n // 否则进入 Agent 执行流程\n }\n\n // 4. 初始化 ModelProvider\n String configDefaultModel = initializeModelProvider();\n\n // 4a. 如果用户未通过 --model 指定,且 model.json 中配置了 default_model,则使用它\n String effectiveModel =\n (configDefaultModel != null && config.model().equals(RunConfig.DEFAULT_MODEL))\n ? configDefaultModel\n : config.model();\n if (configDefaultModel != null) {\n loadedDefaultModel = configDefaultModel;\n }\n\n // 4. 创建渲染器(将有效 model 回传给 config 上下文)\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer, effectiveModel);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = SnowflakeIdGenerator.generateSessionId();\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : SnowflakeIdGenerator.generateSessionId();\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ResumeSessionCmd resume -> {\n System.out.println(\"Resuming session: \" + resume.sessionId());\n if (resume.snapshotId() != null) {\n System.out.println(\" Snapshot: \" + resume.snapshotId());\n }\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = SnowflakeIdGenerator.generateSessionId();\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // Resume 会话列表\n // ========================================================================\n\n /** 处理 {@code alice resume --list} 子命令,列出 WAL 中可恢复的会话。 */\n private static int handleResumeList(RunConfig config) {\n try {\n // 扫描 ~/.alice/wal 目录下的会话\n var walDir = java.nio.file.Paths.get(System.getProperty(\"user.home\"), \".alice\", \"wal\");\n if (!java.nio.file.Files.isDirectory(walDir)) {\n System.out.println(\"No WAL storage found at: \" + walDir);\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n var sessions = new java.util.ArrayList<String>();\n try (var stream = java.nio.file.Files.newDirectoryStream(walDir, \"*.wal.jsonl\")) {\n for (var path : stream) {\n String fileName = path.getFileName().toString();\n String sessionId = fileName.substring(0, fileName.length() - \".wal.jsonl\".length());\n sessions.add(sessionId);\n }\n }\n\n if (sessions.isEmpty()) {\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Available sessions (\" + sessions.size() + \"):\");\n System.out.println();\n for (var sid : sessions) {\n var cpFile = walDir.resolve(sid + \".checkpoint.json\");\n String info = \" 📁 \" + sid;\n if (java.nio.file.Files.exists(cpFile)) {\n info += \" (checkpoint available)\";\n }\n System.out.println(info);\n }\n System.out.println();\n System.out.println(\"Use 'alice resume --session-id <id>' to restore a session.\");\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing sessions: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" -> {\n if (loadedDefaultModel != null) {\n System.out.println(\n \"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n }\n }\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else if (loadedDefaultModel != null) {\n System.out.println(\"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n /** 从 ~/.alice/model.json 加载的默认模型 ID,供 config 命令和 run 流程使用。 */\n private static String loadedDefaultModel;\n\n /** 从 ~/.alice/model.json 加载的默认模型配置对象(新格式),含 enable_thinking / reasoning_effort。 */\n private static ModelConfigLoader.DefaultModelConfig loadedDefaultModelConfig;\n\n /**\n * 初始化模型提供器。\n *\n * @return model.json 中配置的默认模型 ID,未设置则返回 {@code null}\n */\n private static String initializeModelProvider() {\n String configDefaultModel = null;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n configDefaultModel = configLoader.getDefaultModel();\n loadedDefaultModelConfig = configLoader.getDefaultModelConfig();\n logger.info(\n \"Loaded {} provider(s) from ~/.alice/model.json\", configLoader.getProviders().size());\n if (configDefaultModel != null) {\n logger.info(\"Default model from model.json: {}\", configDefaultModel);\n }\n if (loadedDefaultModelConfig != null) {\n logger.info(\n \"Default model config: enable_thinking={}, reasoning_effort={}\",\n loadedDefaultModelConfig.enableThinking(),\n loadedDefaultModelConfig.reasoningEffort());\n }\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-v4-flash\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n return configDefaultModel;\n }\n}\n"}, "542dfef86a8f56af43d54230584fe2edf9c80f6a": {"t": 1785755689.6241956, "n": 24154, "h": "c44e229fbcac68d6", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 3a. Resume 命令 — 走 Agent 执行(通过 ExecutionCoordinator)\n if (config.resumeMode()) {\n // resume 命令可能带 --list 标志,直接列表展示\n if (config.resumeList()) {\n return handleResumeList(config);\n }\n // 否则进入 Agent 执行流程\n }\n\n // 4. 初始化 ModelProvider\n String configDefaultModel = initializeModelProvider();\n\n // 4a. 如果用户未通过 --model 指定,且 model.json 中配置了 default_model,则使用它\n String effectiveModel =\n (configDefaultModel != null && config.model().equals(RunConfig.DEFAULT_MODEL))\n ? configDefaultModel\n : config.model();\n if (configDefaultModel != null) {\n loadedDefaultModel = configDefaultModel;\n }\n\n // 4. 创建渲染器(将有效 model 回传给 config 上下文)\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer, effectiveModel);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = SnowflakeIdGenerator.generateSessionId();\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : SnowflakeIdGenerator.generateSessionId();\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ResumeSessionCmd resume -> {\n System.out.println(\"Resuming session: \" + resume.sessionId());\n if (resume.snapshotId() != null) {\n System.out.println(\" Snapshot: \" + resume.snapshotId());\n }\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = SnowflakeIdGenerator.generateSessionId();\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // Resume 会话列表\n // ========================================================================\n\n /** 处理 {@code alice resume --list} 子命令,列出 WAL 中可恢复的会话。 */\n private static int handleResumeList(RunConfig config) {\n try {\n // 扫描 ~/.alice/wal 目录下的会话\n var walDir = java.nio.file.Paths.get(System.getProperty(\"user.home\"), \".alice\", \"wal\");\n if (!java.nio.file.Files.isDirectory(walDir)) {\n System.out.println(\"No WAL storage found at: \" + walDir);\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n var sessions = new java.util.ArrayList<String>();\n try (var stream = java.nio.file.Files.newDirectoryStream(walDir, \"*.wal.jsonl\")) {\n for (var path : stream) {\n String fileName = path.getFileName().toString();\n String sessionId = fileName.substring(0, fileName.length() - \".wal.jsonl\".length());\n sessions.add(sessionId);\n }\n }\n\n if (sessions.isEmpty()) {\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Available sessions (\" + sessions.size() + \"):\");\n System.out.println();\n for (var sid : sessions) {\n var cpFile = walDir.resolve(sid + \".checkpoint.json\");\n String info = \" 📁 \" + sid;\n if (java.nio.file.Files.exists(cpFile)) {\n info += \" (checkpoint available)\";\n }\n System.out.println(info);\n }\n System.out.println();\n System.out.println(\"Use 'alice resume --session-id <id>' to restore a session.\");\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing sessions: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" -> {\n if (loadedDefaultModel != null) {\n System.out.println(\n \"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n }\n }\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else if (loadedDefaultModel != null) {\n System.out.println(\"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n /** 从 ~/.alice/model.json 加载的默认模型 ID,供 config 命令和 run 流程使用。 */\n private static String loadedDefaultModel;\n\n /** 从 ~/.alice/model.json 加载的默认模型配置对象(新格式),含 enable_thinking / reasoning_effort。 */\n private static ModelConfigLoader.DefaultModelConfig loadedDefaultModelConfig;\n\n /**\n * 初始化模型提供器。\n *\n * @return model.json 中配置的默认模型 ID,未设置则返回 {@code null}\n */\n private static String initializeModelProvider() {\n String configDefaultModel = null;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n configDefaultModel = configLoader.getDefaultModel();\n loadedDefaultModelConfig = configLoader.getDefaultModelConfig();\n logger.info(\n \"Loaded {} provider(s) from ~/.alice/model.json\", configLoader.getProviders().size());\n if (configDefaultModel != null) {\n logger.info(\"Default model from model.json: {}\", configDefaultModel);\n }\n if (loadedDefaultModelConfig != null) {\n logger.info(\n \"Default model config: enable_thinking={}, reasoning_effort={}\",\n loadedDefaultModelConfig.enableThinking(),\n loadedDefaultModelConfig.reasoningEffort());\n }\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-v4-flash\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n return configDefaultModel;\n }\n}\n"}, "2bbe5ee1d8031e29c337a8ef11da323abd34bd40": {"t": 1785755691.3378158, "n": 23656, "h": "d19894fb0d903ef4", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 3a. Resume 命令 — 走 Agent 执行(通过 ExecutionCoordinator)\n if (config.resumeMode()) {\n // resume 命令可能带 --list 标志,直接列表展示\n if (config.resumeList()) {\n return handleResumeList(config);\n }\n // 否则进入 Agent 执行流程\n }\n\n // 4. 初始化 ModelProvider\n String configDefaultModel = initializeModelProvider();\n\n // 4a. 如果用户未通过 --model 指定,且 model.json 中配置了 default_model,则使用它\n String effectiveModel =\n (configDefaultModel != null && config.model().equals(RunConfig.DEFAULT_MODEL))\n ? configDefaultModel\n : config.model();\n if (configDefaultModel != null) {\n loadedDefaultModel = configDefaultModel;\n }\n\n // 4. 创建渲染器(将有效 model 回传给 config 上下文)\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer, effectiveModel);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = SnowflakeIdGenerator.generateSessionId();\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : SnowflakeIdGenerator.generateSessionId();\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ResumeSessionCmd resume -> {\n System.out.println(\"Resuming session: \" + resume.sessionId());\n if (resume.snapshotId() != null) {\n System.out.println(\" Snapshot: \" + resume.snapshotId());\n }\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = SnowflakeIdGenerator.generateSessionId();\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // Resume 会话列表\n // ========================================================================\n\n /** 处理 {@code alice resume --list} 子命令,列出 WAL 中可恢复的会话。 */\n private static int handleResumeList(RunConfig config) {\n try {\n // 扫描 ~/.alice/wal 目录下的会话\n var walDir = java.nio.file.Paths.get(System.getProperty(\"user.home\"), \".alice\", \"wal\");\n if (!java.nio.file.Files.isDirectory(walDir)) {\n System.out.println(\"No WAL storage found at: \" + walDir);\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n var sessions = new java.util.ArrayList<String>();\n try (var stream = java.nio.file.Files.newDirectoryStream(walDir, \"*.wal.jsonl\")) {\n for (var path : stream) {\n String fileName = path.getFileName().toString();\n String sessionId = fileName.substring(0, fileName.length() - \".wal.jsonl\".length());\n sessions.add(sessionId);\n }\n }\n\n if (sessions.isEmpty()) {\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Available sessions (\" + sessions.size() + \"):\");\n System.out.println();\n for (var sid : sessions) {\n var cpFile = walDir.resolve(sid + \".checkpoint.json\");\n String info = \" 📁 \" + sid;\n if (java.nio.file.Files.exists(cpFile)) {\n info += \" (checkpoint available)\";\n }\n System.out.println(info);\n }\n System.out.println();\n System.out.println(\"Use 'alice resume --session-id <id>' to restore a session.\");\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing sessions: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" -> {\n if (loadedDefaultModel != null) {\n System.out.println(\n \"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n }\n }\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else if (loadedDefaultModel != null) {\n System.out.println(\"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n /** 从 ~/.alice/model.json 加载的默认模型 ID,供 config 命令和 run 流程使用。 */\n private static String loadedDefaultModel;\n\n /**\n * 初始化模型提供器。\n *\n * @return model.json 中配置的默认模型 ID,未设置则返回 {@code null}\n */\n private static String initializeModelProvider() {\n String configDefaultModel = null;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n configDefaultModel = configLoader.getDefaultModel();\n logger.info(\n \"Loaded {} model provider(s) from ~/.alice/model.json\",\n configLoader.getProviders().size());\n if (configDefaultModel != null) {\n logger.info(\"Default model from model.json: {}\", configDefaultModel);\n }\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-v4-flash\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n return configDefaultModel;\n }\n}\n"}, "3e58892dd30fad4915a4efd6b5901a09ac3ccae5": {"t": 1785755692.903427, "n": 23608, "h": "8e0fb5edc52116d6", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 3a. Resume 命令 — 走 Agent 执行(通过 ExecutionCoordinator)\n if (config.resumeMode()) {\n // resume 命令可能带 --list 标志,直接列表展示\n if (config.resumeList()) {\n return handleResumeList(config);\n }\n // 否则进入 Agent 执行流程\n }\n\n // 4. 初始化 ModelProvider\n String configDefaultModel = initializeModelProvider();\n\n // 4a. 如果用户未通过 --model 指定,且 model.json 中配置了 default_model,则使用它\n String effectiveModel =\n (configDefaultModel != null && config.model().equals(RunConfig.DEFAULT_MODEL))\n ? configDefaultModel\n : config.model();\n if (configDefaultModel != null) {\n loadedDefaultModel = configDefaultModel;\n }\n\n // 4. 创建渲染器(将有效 model 回传给 config 上下文)\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer, effectiveModel);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ResumeSessionCmd resume -> {\n System.out.println(\"Resuming session: \" + resume.sessionId());\n if (resume.snapshotId() != null) {\n System.out.println(\" Snapshot: \" + resume.snapshotId());\n }\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // Resume 会话列表\n // ========================================================================\n\n /** 处理 {@code alice resume --list} 子命令,列出 WAL 中可恢复的会话。 */\n private static int handleResumeList(RunConfig config) {\n try {\n // 扫描 ~/.alice/wal 目录下的会话\n var walDir = java.nio.file.Paths.get(System.getProperty(\"user.home\"), \".alice\", \"wal\");\n if (!java.nio.file.Files.isDirectory(walDir)) {\n System.out.println(\"No WAL storage found at: \" + walDir);\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n var sessions = new java.util.ArrayList<String>();\n try (var stream = java.nio.file.Files.newDirectoryStream(walDir, \"*.wal.jsonl\")) {\n for (var path : stream) {\n String fileName = path.getFileName().toString();\n String sessionId = fileName.substring(0, fileName.length() - \".wal.jsonl\".length());\n sessions.add(sessionId);\n }\n }\n\n if (sessions.isEmpty()) {\n System.out.println(\"No sessions available.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Available sessions (\" + sessions.size() + \"):\");\n System.out.println();\n for (var sid : sessions) {\n var cpFile = walDir.resolve(sid + \".checkpoint.json\");\n String info = \" 📁 \" + sid;\n if (java.nio.file.Files.exists(cpFile)) {\n info += \" (checkpoint available)\";\n }\n System.out.println(info);\n }\n System.out.println();\n System.out.println(\"Use 'alice resume --session-id <id>' to restore a session.\");\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing sessions: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" -> {\n if (loadedDefaultModel != null) {\n System.out.println(\n \"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n }\n }\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else if (loadedDefaultModel != null) {\n System.out.println(\"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n /** 从 ~/.alice/model.json 加载的默认模型 ID,供 config 命令和 run 流程使用。 */\n private static String loadedDefaultModel;\n\n /**\n * 初始化模型提供器。\n *\n * @return model.json 中配置的默认模型 ID,未设置则返回 {@code null}\n */\n private static String initializeModelProvider() {\n String configDefaultModel = null;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n configDefaultModel = configLoader.getDefaultModel();\n logger.info(\n \"Loaded {} model provider(s) from ~/.alice/model.json\",\n configLoader.getProviders().size());\n if (configDefaultModel != null) {\n logger.info(\"Default model from model.json: {}\", configDefaultModel);\n }\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-v4-flash\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n return configDefaultModel;\n }\n}\n"}, "2bb6b3f7e483c34be142c1c9ed1daba4cd65067f": {"t": 1785755694.60479, "n": 21220, "h": "deff7556657ad355", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 4. 初始化 ModelProvider\n String configDefaultModel = initializeModelProvider();\n\n // 4a. 如果用户未通过 --model 指定,且 model.json 中配置了 default_model,则使用它\n String effectiveModel =\n (configDefaultModel != null && config.model().equals(RunConfig.DEFAULT_MODEL))\n ? configDefaultModel\n : config.model();\n if (configDefaultModel != null) {\n loadedDefaultModel = configDefaultModel;\n }\n\n // 4. 创建渲染器(将有效 model 回传给 config 上下文)\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer, effectiveModel);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" -> {\n if (loadedDefaultModel != null) {\n System.out.println(\n \"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n }\n }\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else if (loadedDefaultModel != null) {\n System.out.println(\"default.model = \" + loadedDefaultModel + \" (from ~/.alice/model.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n /** 从 ~/.alice/model.json 加载的默认模型 ID,供 config 命令和 run 流程使用。 */\n private static String loadedDefaultModel;\n\n /**\n * 初始化模型提供器。\n *\n * @return model.json 中配置的默认模型 ID,未设置则返回 {@code null}\n */\n private static String initializeModelProvider() {\n String configDefaultModel = null;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n configDefaultModel = configLoader.getDefaultModel();\n logger.info(\n \"Loaded {} model provider(s) from ~/.alice/model.json\",\n configLoader.getProviders().size());\n if (configDefaultModel != null) {\n logger.info(\"Default model from model.json: {}\", configDefaultModel);\n }\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-v4-flash\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n return configDefaultModel;\n }\n}\n"}, "c2c7eac01472b99309c1f867fa8a080907dba673": {"t": 1785755696.0762913, "n": 19994, "h": "41c6911f05cd4f82", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 4. 初始化 ModelProvider\n initializeModelProvider();\n\n // 4. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" ->\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n logger.info(\n \"Loaded {} model provider(s) from ~/.alice/model.json\",\n configLoader.getProviders().size());\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-v4-flash\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "ea266cc69fdfde12ecbe7c3df37743f6c42372fc": {"t": 1785755697.7079556, "n": 19990, "h": "e64cc7fa904ee99b", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.facade.cmd.chat.JLineChatSession;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 4. 初始化 ModelProvider\n initializeModelProvider();\n\n // 4. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" ->\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n logger.info(\n \"Loaded {} model provider(s) from ~/.alice/model.json\",\n configLoader.getProviders().size());\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-chat\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "89b2479f36a21903ca0de8000a61a4e11bf70224": {"t": 1785755699.5411308, "n": 19933, "h": "ebc9581e91bf157d", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 4. 初始化 ModelProvider\n initializeModelProvider();\n\n // 4. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" ->\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n\n // 1. 加载 ~/.alice/model.json 配置文件\n try {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n configLoader.load();\n configLoader.registerTo(provider);\n logger.info(\n \"Loaded {} model provider(s) from ~/.alice/model.json\",\n configLoader.getProviders().size());\n } catch (Exception e) {\n logger.debug(\"No model config found, using env vars: {}\", e.getMessage());\n }\n\n // 2. 注册内置模型枚举\n provider.registerBuiltinModels();\n\n // 3. 从环境变量注册供应商(优先级低于配置文件)\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n if (provider.getSupplier(\"gpt-4o-mini\") == null) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered from env var\");\n }\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n if (provider.getSupplier(\"claude-3-5-sonnet-latest\") == null) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered from env var\");\n }\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n\n // 4. DeepSeek (OpenAI-compatible) 从环境变量注册\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (provider.getSupplier(\"deepseek-chat\") == null) {\n provider.registerSupplier(\n new OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"DeepSeek supplier registered from env var (OpenAI-compatible)\");\n }\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "a4225676e3fe9533f6466874bfd10308b7f7f6fc": {"t": 1785755700.958288, "n": 18692, "h": "3b9a2afbe1a7e437", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.core.agent.AgentConfig;\nimport org.cland.alice.facade.cmd.config.AliceConfigStore;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印 → 成功退出\n return EXIT_SUCCESS;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 工具列表命令 — 不走 Agent 执行\n if (config.listTools()) {\n return handleListTools(config);\n }\n\n // 3. 配置管理命令 — 不走 Agent 执行\n if (config.configAction() != null) {\n return handleConfig(config);\n }\n\n // 4. 初始化 ModelProvider\n initializeModelProvider();\n\n // 4. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 5. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 工具列表\n // ========================================================================\n\n /**\n * 处理 {@code alice tools} 子命令,列出 ToolRegistry 中的已注册工具。\n *\n * @param config 运行配置\n * @return 退出码\n */\n private static int handleListTools(RunConfig config) {\n try {\n var registry = org.cland.alice.tool.gateway.ToolRegistryHolder.INSTANCE;\n var tools = registry.allTools();\n\n if (tools.isEmpty()) {\n System.out.println(\"No tools registered.\");\n System.out.println(\"Use 'alice run --skill <toolset>' or configure MCP to load tools.\");\n return EXIT_SUCCESS;\n }\n\n System.out.println(\"Registered tools (\" + tools.size() + \"):\");\n System.out.println();\n\n if (config.toolDetail()) {\n for (var meta : tools) {\n System.out.println(\" ── \" + meta.name() + \" ──\");\n System.out.println(\" Description: \" + meta.description());\n if (meta.inputSchema() != null && !meta.inputSchema().isEmpty()) {\n System.out.println(\" Parameters: \" + meta.inputSchema());\n }\n System.out.println();\n }\n } else {\n for (var meta : tools) {\n System.out.println(\" ⚡ \" + meta.name() + \" — \" + meta.description());\n }\n System.out.println();\n System.out.println(\"Use 'alice tools --detail' to see parameter schemas.\");\n }\n\n return EXIT_SUCCESS;\n } catch (Exception e) {\n System.err.println(\"Error listing tools: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n // ========================================================================\n // 配置管理\n // ========================================================================\n\n /**\n * 处理 {@code alice config} 子命令。\n *\n * <p>支持:\n *\n * <ul>\n * <li>{@code alice config} — 显示当前配置概览\n * <li>{@code alice config get <key>} — 获取单个配置项\n * <li>{@code alice config set <key> <value>} — 设置配置项(当前仅打印模拟)\n * </ul>\n */\n /** 全局配置存储实例(延迟初始化) */\n private static volatile AliceConfigStore configStore;\n\n /** 获取或创建配置存储实例。 */\n private static AliceConfigStore configStore() {\n if (configStore == null) {\n synchronized (AliceCliLauncher.class) {\n if (configStore == null) {\n configStore = new AliceConfigStore();\n }\n }\n }\n return configStore;\n }\n\n private static int handleConfig(RunConfig config) {\n try {\n String action = config.configAction();\n String key = config.configKey();\n String value = config.configValue();\n AliceConfigStore store = configStore();\n\n if (\"set\".equals(action)) {\n if (key == null || value == null) {\n System.err.println(\"Usage: alice config set <key> <value>\");\n return EXIT_PARAM_ERROR;\n }\n store.set(key, value);\n System.out.println(\n \"Config '\" + key + \"' set to '\" + value + \"' (persisted to ~/.alice/config.json)\");\n return EXIT_SUCCESS;\n }\n\n if (\"get\".equals(action)) {\n if (key == null) {\n System.err.println(\"Usage: alice config get <key>\");\n return EXIT_PARAM_ERROR;\n }\n printConfigValue(key, store);\n return EXIT_SUCCESS;\n }\n\n // \"show\" — 显示全部配置\n printAllConfig(store);\n return EXIT_SUCCESS;\n\n } catch (Exception e) {\n System.err.println(\"Error: \" + e.getMessage());\n return EXIT_RUNTIME_ERROR;\n }\n }\n\n /**\n * 获取配置值的实际值(优先级:环境变量 > 配置文件 > 内建默认值)。\n *\n * @param key 点分隔的键名\n * @param store 配置存储\n * @return 值,或 {@code null} 表示未设置\n */\n private static String resolveConfigValue(String key, AliceConfigStore store) {\n // 1. 环境变量(最高优先级)\n String envName = configKeyToEnv(key);\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n return envVal;\n }\n }\n // 2. 配置文件\n String stored = store.get(key);\n if (stored != null) {\n return stored;\n }\n // 3. 内建默认值\n return switch (key) {\n case \"default.model\" -> AgentConfig.DEFAULT_MODEL;\n case \"agent.max_iterations\" -> String.valueOf(AgentConfig.DEFAULT_MAX_ITERATIONS);\n default -> null;\n };\n }\n\n /** 将配置键名映射为环境变量名。 */\n private static String configKeyToEnv(String key) {\n return switch (key) {\n case \"openai.api_key\", \"providers.openai.api_key\" -> \"OPENAI_API_KEY\";\n case \"anthropic.api_key\", \"providers.anthropic.api_key\" -> \"ANTHROPIC_API_KEY\";\n default -> null;\n };\n }\n\n /** 获取并打印单个配置项的值及其来源。 */\n private static void printConfigValue(String key, AliceConfigStore store) {\n String envName = configKeyToEnv(key);\n\n // 检查环境变量\n if (envName != null) {\n String envVal = System.getenv(envName);\n if (envVal != null && !envVal.isEmpty()) {\n System.out.println(\n key\n + \" = \"\n + envVal.substring(0, Math.min(8, envVal.length()))\n + \"... (from env \"\n + envName\n + \")\");\n return;\n }\n }\n\n // 检查配置文件\n String stored = store.get(key);\n if (stored != null) {\n String display =\n key.contains(\"api_key\")\n ? stored.substring(0, Math.min(8, stored.length())) + \"...\"\n : stored;\n System.out.println(key + \" = \" + display + \" (from ~/.alice/config.json)\");\n return;\n }\n\n // 检查内建默认值\n switch (key) {\n case \"default.model\" ->\n System.out.println(\n \"default.model = \" + AgentConfig.DEFAULT_MODEL + \" (built-in default)\");\n case \"agent.max_iterations\" ->\n System.out.println(\n \"agent.max_iterations = \"\n + AgentConfig.DEFAULT_MAX_ITERATIONS\n + \" (built-in default)\");\n default -> System.out.println(key + \" = (not set)\");\n }\n }\n\n /** 打印所有配置项。 */\n private static void printAllConfig(AliceConfigStore store) {\n System.out.println(\"=== Alice Agent Configuration ===\");\n System.out.println();\n\n // 读取 providers 嵌套结构\n var all = store.getAll();\n System.out.println(\"-- Model Providers --\");\n @SuppressWarnings(\"unchecked\")\n var providers = (Map<String, Object>) all.get(\"providers\");\n if (providers != null && !providers.isEmpty()) {\n for (var entry : providers.entrySet()) {\n String providerName = entry.getKey();\n System.out.println(\" [\" + providerName + \"]\");\n @SuppressWarnings(\"unchecked\")\n var providerConfig = (Map<String, Object>) entry.getValue();\n if (providerConfig != null) {\n for (var pEntry : providerConfig.entrySet()) {\n String display =\n pEntry.getKey().contains(\"api_key\")\n ? pEntry\n .getValue()\n .toString()\n .substring(0, Math.min(8, pEntry.getValue().toString().length()))\n + \"...\"\n : pEntry.getValue().toString();\n System.out.println(\" \" + pEntry.getKey() + \" = \" + display);\n }\n }\n }\n } else {\n // 回退到扁平键\n printConfigValue(\"openai.api_key\", store);\n printConfigValue(\"anthropic.api_key\", store);\n }\n\n System.out.println();\n System.out.println(\"-- Agent Defaults --\");\n // 从嵌套或扁平读取\n String model = store.get(\"default.model\");\n if (model == null) model = store.get(\"default_model\");\n if (model != null) {\n System.out.println(\"default.model = \" + model + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"default.model\", store);\n }\n\n String iterStr = store.get(\"agent.max_iterations\");\n if (iterStr == null) iterStr = store.get(\"max_iterations\");\n if (iterStr != null) {\n System.out.println(\"agent.max_iterations = \" + iterStr + \" (from ~/.alice/config.json)\");\n } else {\n printConfigValue(\"agent.max_iterations\", store);\n }\n\n System.out.println();\n System.out.println(\"Config file: ~/.alice/config.json\");\n System.out.println(\"Use 'alice config get <key>' or 'alice config set <key> <value>'\");\n System.out.println(\n \"Known keys: openai.api_key, anthropic.api_key, default.model, agent.max_iterations\");\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered\");\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "7d35985d4f861780abf7f36eb3f2fa62bea9c786": {"t": 1785755702.4121075, "n": 10043, "h": "0b7481e2f7d6ac20", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.agent.subagent.SubAgentManager;\nimport org.cland.alice.agent.subagent.SubAgentRecord;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.ClaudeSupplier;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SpawnSubAgentCmd spawn -> {\n System.out.println(\"Spawning sub-agent: \" + spawn.goal());\n // 创建 SubAgentManager 并生成子 Agent\n String subSessionId = UUID.randomUUID().toString().substring(0, 8);\n SubAgentManager mgr = new SubAgentManager(subSessionId);\n SubAgentRecord record = mgr.spawnSubAgent(spawn.goal(), spawn.model());\n System.out.println(\"Sub-agent \" + record.id() + \" spawned with goal: \" + spawn.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ConnectSubAgentCmd connect -> {\n System.out.println(\n \"Connecting to ACP agent: \" + connect.name() + \" at \" + connect.acpEndpoint());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ListSubAgentsCmd list -> {\n System.out.println(\"Listing sub-agents (not yet wired to a SubAgentManager)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.CancelSubAgentCmd cancel -> {\n System.out.println(\"Cancel sub-agent: \" + cancel.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.GetSubAgentResultsCmd results -> {\n System.out.println(\n \"Get results for sub-agent: \" + results.subAgentId() + \" (not yet wired)\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.SendToSubAgentCmd send -> {\n System.out.println(\n \"Send message to sub-agent \" + send.subAgentId() + \": \" + send.message());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.PromptSubAgentCmd prompt -> {\n System.out.println(\"Prompt sub-agent \" + prompt.subAgentId() + \": \" + prompt.prompt());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String openAiKey = System.getenv(\"OPENAI_API_KEY\");\n if (openAiKey != null && !openAiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(openAiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n\n String anthropicKey = System.getenv(\"ANTHROPIC_API_KEY\");\n if (anthropicKey != null && !anthropicKey.isEmpty()) {\n provider.registerSupplier(new ClaudeSupplier(anthropicKey));\n logger.info(\"Anthropic Claude supplier registered\");\n } else {\n logger.warn(\"ANTHROPIC_API_KEY not set. LLM calls via Anthropic will be unavailable.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "0b23bcc1514cdbac79d0b5dcebfa9d5682211679": {"t": 1785755703.6172955, "n": 7692, "h": "689a2073074b3dd3", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.RoutineTimeCmd.RegisterRoutineCmd routine -> {\n System.out.println(\"Registering routine: \" + routine.cronExpression());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "6ac7c7c409fdeda5f270fc91665793da3d5507a0": {"t": 1785755705.1294005, "n": 7488, "h": "f674b38f33eccf69", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId =\n cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "cdaaead10134ed1474525a45acdd5b6fc2ff12da": {"t": 1785755706.4558299, "n": 7480, "h": "8cc072b8b4c6bef7", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n return dispatchCommand(cmd);\n }\n\n /**\n * 分发已解析的 {@link AgentCommand}。\n *\n * <p>由 {@link JLineChatSession} 等已预先解析的调用方使用,避免重复解析。\n *\n * @param cmd 已解析的 AgentCommand\n * @return 退出码\n */\n public static int dispatchCommand(AgentCommand cmd) {\n if (cmd == null) {\n System.err.println(\"Cannot dispatch null command\");\n return EXIT_PARAM_ERROR;\n }\n\n String sessionId = cmd.sessionId() != null ? cmd.sessionId() : UUID.randomUUID().toString().substring(0, 8);\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "54c104cd19d2e340e51ebaa92cae552fe6b2fc43": {"t": 1785755707.7977746, "n": 6990, "h": "8d369b6724a60229", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ClearContextCmd clear -> {\n System.out.println(\"Clearing context (session=\" + clear.sessionId() + \")\");\n System.out.println(\"上下文已清除\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.ViewContextCmd view -> {\n System.out.println(\"Viewing context (session=\" + view.sessionId() + \")\");\n System.out.println(\"── 上下文状态 ──\");\n System.out.println(\"会话 ID: \" + view.sessionId());\n System.out.println(\"Token 占用: N/A (Memory 模块待集成)\");\n System.out.println(\"消息滑动窗口: N/A\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ControlCmd.CompactContextCmd compact -> {\n System.out.println(\"Compacting context (session=\" + compact.sessionId() + \")\");\n System.out.println(\"上下文压缩请求已提交(待 Memory 模块提供总结接口)\");\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "ecba2ea793e0640c9d3e7fb067725c450ccae401": {"t": 1785755709.3734043, "n": 6086, "h": "e83e27b3c34155e2", "c": "package org.cland.alice.facade.cmd;\n\nimport java.util.UUID;\nimport org.cland.alice.agent.command.AgentCommand;\nimport org.cland.alice.agent.command.CapabilityCmd;\nimport org.cland.alice.agent.command.ControlCmd;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>对于 {@code run} 子命令,驱动 {@link ExecutionCoordinator} 执行任务\n * <li>对于其他子命令(预留),通过 {@link AgentCommand} 抽象层路由\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n /**\n * 将用户输入解析为 {@link AgentCommand} 并分发。\n *\n * <p>用于交互式 CLI 会话(chat 模式),根据 AgentCommand 类型路由:\n *\n * <ul>\n * <li>{@code AcquireGoalCmd} / {@code ExecuteRawCmd} → Agent 核心执行\n * <li>{@code SwitchModelCmd} → 模型切换\n * <li>{@code RegisterSkillCmd} / {@code UpdateRulesCmd} / {@code ReloadKernelCmd} → 能力装载\n * <li>{@code ResetSessionCmd} / {@code InterruptCmd} → 生命周期控制\n * </ul>\n *\n * @param input 用户原始输入\n * @return 退出码\n */\n public static int dispatchCommand(String input) {\n String sessionId = UUID.randomUUID().toString().substring(0, 8);\n String traceId = UUID.randomUUID().toString().substring(0, 12);\n\n AgentCommand cmd = AgentCommand.parse(input, sessionId, traceId);\n if (cmd == null) {\n System.err.println(\"Unrecognized command: \" + input);\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\n \"Dispatching AgentCommand: {} (session={})\", cmd.getClass().getSimpleName(), sessionId);\n\n return switch (cmd) {\n case org.cland.alice.agent.command.ExecutionCmd.AcquireGoalCmd run -> {\n System.out.println(\"Executing goal: \" + run.goal());\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.ExecutionCmd.ExecuteRawCmd exec -> {\n System.out.println(\"Executing raw: \" + exec.command());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.RegisterSkillCmd skill -> {\n System.out.println(\"Registering skill: \" + skill.skillRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.UpdateRulesCmd rules -> {\n System.out.println(\"Updating rules: \" + rules.rulesRef());\n yield EXIT_SUCCESS;\n }\n case CapabilityCmd.ReloadKernelCmd reload -> {\n System.out.println(\"Reloading kernel...\");\n yield EXIT_SUCCESS;\n }\n case org.cland.alice.agent.command.AlignmentCmd.SwitchModelCmd model -> {\n System.out.println(\"Switching model to: \" + model.modelId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.ResetSessionCmd reset -> {\n System.out.println(\"Resetting session: \" + reset.sessionId());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.FeedbackCmd fb -> {\n System.out.println(\"Feedback received: \" + fb.message());\n yield EXIT_SUCCESS;\n }\n case ControlCmd.InterruptCmd interrupt -> {\n System.out.println(\"Interrupted: \" + interrupt.cause());\n yield EXIT_SUCCESS;\n }\n case null, default -> {\n System.err.println(\"Unknown command type\");\n yield EXIT_PARAM_ERROR;\n }\n };\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "cf772f464ef785e85743100f561e6292b64c024a": {"t": 1785755710.593858, "n": 3315, "h": "c52ff15a51e74a09", "c": "package org.cland.alice.facade.cmd;\n\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Alice CLI 模块的入口点。\n *\n * <p>对应设计文档中 {@code AliceCliLauncher} 的角色:\n *\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})\n * <li>初始化核心环境(ModelProvider 等)\n * <li>驱动 {@link ExecutionCoordinator} 执行任务\n * <li>映射退出码并退出 JVM\n * </ul>\n *\n * <p>退出码映射(符合设计文档):\n *\n * <ul>\n * <li>{@code 0} — 任务执行成功\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)\n * <li>{@code 2} — 命令参数错误\n * <li>{@code 130} — 用户手动中断(Ctrl+C)\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n // 注册 JVM 关闭钩子处理 Ctrl+C\n Runtime.getRuntime()\n .addShutdownHook(\n new Thread(\n () -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n *\n * <p>注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "74837030dfba4e7d4c820989eed8c4dd79acc3aa": {"t": 1785755712.0336196, "n": 3576, "h": "e2b9982ec4dc3189", "c": "package org.cland.alice.facade.cmd;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\n\n/**\n * Alice CLI 模块的入口点。\n * <p>\n * 对应设计文档中 {@code AliceCliLauncher} 的角色:\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})</li>\n * <li>初始化核心环境(ModelProvider 等)</li>\n * <li>驱动 {@link ExecutionCoordinator} 执行任务</li>\n * <li>映射退出码并退出 JVM</li>\n * </ul>\n *\n * <p>\n * 退出码映射(符合设计文档):\n * <ul>\n * <li>{@code 0} — 任务执行成功</li>\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)</li>\n * <li>{@code 2} — 命令参数错误</li>\n * <li>{@code 130} — 用户手动中断(Ctrl+C)</li>\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final Logger logger = LoggerFactory.getLogger(AliceCliLauncher.class);\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n // 注册 JVM 关闭钩子处理 Ctrl+C\n Runtime.getRuntime().addShutdownHook(new Thread(() -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.info(\"RunConfig: {}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n * <p>\n * 注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.info(\"OpenAI supplier registered\");\n } else {\n logger.warn(\"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.warn(\"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "b72ce13b6595188b8c47f91d0409c1cb187b621f": {"t": 1785755713.2476056, "n": 3680, "h": "143badcb424dc7cd", "c": "package org.cland.alice.facade.cmd;\n\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\n\n/**\n * Alice CLI 模块的入口点。\n * <p>\n * 对应设计文档中 {@code AliceCliLauncher} 的角色:\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})</li>\n * <li>初始化核心环境(ModelProvider 等)</li>\n * <li>驱动 {@link ExecutionCoordinator} 执行任务</li>\n * <li>映射退出码并退出 JVM</li>\n * </ul>\n *\n * <p>\n * 退出码映射(符合设计文档):\n * <ul>\n * <li>{@code 0} — 任务执行成功</li>\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)</li>\n * <li>{@code 2} — 命令参数错误</li>\n * <li>{@code 130} — 用户手动中断(Ctrl+C)</li>\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final System.Logger logger = System.getLogger(AliceCliLauncher.class.getName());\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n // 注册 JVM 关闭钩子处理 Ctrl+C\n Runtime.getRuntime().addShutdownHook(new Thread(() -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n public static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.log(System.Logger.Level.INFO, \"RunConfig: {0}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n * <p>\n * 注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.log(System.Logger.Level.INFO, \"OpenAI supplier registered\");\n } else {\n logger.log(System.Logger.Level.WARNING,\n \"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.log(System.Logger.Level.WARNING,\n \"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}, "ee70bd0a0bab774568fba363216d26f0e925e276": {"t": 1785755714.5662138, "n": 3673, "h": "835a33a2e85f446d", "c": "package org.cland.alice.facade.cmd;\n\nimport org.cland.alice.facade.cmd.config.CommandParser;\nimport org.cland.alice.facade.cmd.config.CommandParser.ParseException;\nimport org.cland.alice.facade.cmd.config.RunConfig;\nimport org.cland.alice.facade.cmd.render.OutputRenderer;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.model.supplier.OpenAiSupplier;\n\n/**\n * Alice CLI 模块的入口点。\n * <p>\n * 对应设计文档中 {@code AliceCliLauncher} 的角色:\n * <ul>\n * <li>解析 CLI 命令参数(委托给 {@link CommandParser})</li>\n * <li>初始化核心环境(ModelProvider 等)</li>\n * <li>驱动 {@link ExecutionCoordinator} 执行任务</li>\n * <li>映射退出码并退出 JVM</li>\n * </ul>\n *\n * <p>\n * 退出码映射(符合设计文档):\n * <ul>\n * <li>{@code 0} — 任务执行成功</li>\n * <li>{@code 1} — 运行时错误(Agent 无法完成目标)</li>\n * <li>{@code 2} — 命令参数错误</li>\n * <li>{@code 130} — 用户手动中断(Ctrl+C)</li>\n * </ul>\n */\npublic final class AliceCliLauncher {\n\n private static final System.Logger logger = System.getLogger(AliceCliLauncher.class.getName());\n\n // 退出码常量\n public static final int EXIT_SUCCESS = 0;\n public static final int EXIT_RUNTIME_ERROR = 1;\n public static final int EXIT_PARAM_ERROR = 2;\n public static final int EXIT_INTERRUPTED = 130;\n\n private AliceCliLauncher() {\n // 工具类,不可实例化\n }\n\n /**\n * CLI 主入口点。\n *\n * @param args 命令行参数\n */\n public static void main(String[] args) {\n // 注册 JVM 关闭钩子处理 Ctrl+C\n Runtime.getRuntime().addShutdownHook(new Thread(() -> {\n System.err.println(\"\\nReceived shutdown signal. Exiting...\");\n }));\n\n int exitCode = run(args);\n System.exit(exitCode);\n }\n\n /**\n * CLI 运行逻辑(可测试的主逻辑)。\n *\n * @param args 命令行参数\n * @return 退出码\n */\n static int run(String[] args) {\n try {\n // 1. 解析参数\n CommandParser parser = new CommandParser();\n RunConfig config = parser.parse(args);\n\n if (config == null) {\n // parse 返回 null 表示帮助/版本信息已打印\n return EXIT_PARAM_ERROR;\n }\n\n logger.log(System.Logger.Level.INFO, \"RunConfig: {0}\", config);\n\n // 2. 初始化 ModelProvider\n initializeModelProvider();\n\n // 3. 创建渲染器\n OutputRenderer renderer = OutputRenderer.create(config);\n\n // 4. 执行任务\n ExecutionCoordinator coordinator = new ExecutionCoordinator(config, renderer);\n return coordinator.execute();\n\n } catch (ParseException e) {\n System.err.println(\"Error: \" + e.getMessage());\n return e.exitCode();\n }\n }\n\n // ========================================================================\n // 初始化\n // ========================================================================\n\n /**\n * 初始化模型提供器。\n * <p>\n * 注册内置模型,并从环境变量中读取 API Key 注册外部供应商。\n */\n private static void initializeModelProvider() {\n try {\n ModelProvider provider = ModelProvider.getInstance();\n provider.registerBuiltinModels();\n\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n if (apiKey != null && !apiKey.isEmpty()) {\n provider.registerSupplier(new OpenAiSupplier(apiKey));\n logger.log(System.Logger.Level.INFO, \"OpenAI supplier registered\");\n } else {\n logger.log(System.Logger.Level.WARNING,\n \"OPENAI_API_KEY not set. Set it via environment variable to enable LLM calls.\");\n }\n } catch (Exception e) {\n logger.log(System.Logger.Level.WARNING,\n \"ModelProvider initialization failed (some features may be unavailable)\", e);\n }\n }\n}\n"}}, "alice-core-agent/src/main/java/org/cland/alice/core/agent/Agent.java": {"8ebb2c40d3906a86e5a1eaa8ea381b21d0df9bd9": {"t": 1785745700.2901363, "n": 23299, "h": "10f7e7960c0ed5a8", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.Plan;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.core.planner.strategy.FastPathStrategy;\nimport org.cland.alice.core.planner.strategy.SlowPathStrategy;\nimport org.cland.alice.core.planner.strategy.StrategySelector;\nimport org.cland.alice.core.planner.tree.ThinkingTree;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.cland.alice.tool.gateway.ToolRegistryHolder;\nimport org.cland.alice.tool.gateway.builtin.BuiltinTools;\nimport org.cland.alice.tool.gateway.engine.ToolDiscovery;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent implements AgentFacade {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n @Override\n public String agentId() {\n return agentId;\n }\n\n @Override\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n /**\n * 注入静态规划(SOP)函数,重建 {@link PlannerService} 以支持 SOP 匹配。\n *\n * <p>如果当前已有 PlannerService,会复用其 StrategySelector 和现有的 SOP 函数。 该函数通常来自 {@code alice-memory-vault}\n * 的 {@code StaticPlanner::plan}。\n *\n * @param staticPlannerFn 接收上下文 Map 返回 Plan 的函数(null 表示无 SOP)\n */\n public Agent withStaticPlanner(\n java.util.function.Function<Map<String, Object>, Plan> staticPlannerFn) {\n if (this.plannerService != null) {\n this.plannerService =\n PlannerService.builder()\n .strategySelector(this.plannerService.strategySelector())\n .staticPlannerFn(staticPlannerFn)\n .build();\n }\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n @Override\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n @Override\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n @Override\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n @Override\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n @Override\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n @Override\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n /** 取消当前 PPAO 执行。可用于 TUI ESC / CLI Ctrl+C 中断。 */\n public void cancel() {\n executor.cancel();\n logger.info(\"[Agent] Execution cancelled\");\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n\n // ================================================================\n // 静态工厂方法 — 供 TUI/CLI 外观模块使用,避免外观直接依赖子模块\n // ================================================================\n\n /**\n * 初始化 ModelProvider:从 {@code ~/.alice/model.json} 加载配置,注册提供商和内置模型枚举。\n *\n * <p>此方法负责:\n *\n * <ul>\n * <li>加载 {@code ~/.alice/model.json} 配置文件\n * <li>将配置中的提供商注册到 {@link ModelProvider}\n * <li>注册内置模型枚举\n * <li>如果环境变量中存在 DEEPSEEK_API_KEY,注册 DeepSeek 供应商\n * </ul>\n *\n * @return 默认模型 ID(配置中指定),若无则返回 {@code null}\n */\n public static String initModelProvider() {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n try {\n configLoader.load();\n configLoader.registerTo(ModelProvider.getInstance());\n logger.info(\"[Agent] Loaded {} model(s) from config\", configLoader.getModelPool().size());\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config, using defaults: {}\", e.getMessage());\n }\n\n // 注册内置模型枚举\n ModelProvider.getInstance().registerBuiltinModels();\n\n // 确定默认模型\n String defaultModel = configLoader.getDefaultModel();\n if (defaultModel == null || defaultModel.isBlank()) {\n defaultModel = \"gpt-4o-mini\";\n logger.info(\n \"[Agent] No default_model in ~/.alice/model.json, using built-in: {}\", defaultModel);\n } else {\n logger.info(\"[Agent] Using default model from config: {}\", defaultModel);\n }\n\n // 注册 DeepSeek 供应商(如果环境变量存在且尚未注册)\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (ModelProvider.getInstance().getSupplier(\"deepseek-v4-flash\") == null) {\n ModelProvider.getInstance()\n .registerSupplier(\n new org.cland.alice.model.supplier.OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"[Agent] Registered DeepSeek supplier via OpenAiSupplier (OpenAI-compatible)\");\n }\n }\n\n return defaultModel;\n }\n\n /**\n * 创建完全初始化的 Agent 实例,自动装配所有子模块(PlannerService、ToolRegistry 等)。\n *\n * <p>外观模块可以直接使用此工厂方法创建 Agent,无需直接依赖 planner/tool-gateway/model 模块。\n *\n * @param config Agent 配置\n * @return 已装配所有子模块的 Agent 实例\n */\n public static Agent createDefault(AgentConfig config) {\n Agent agent = new Agent(config);\n\n // 1. 初始化工具注册中心并发现内置工具\n ToolRegistry toolRegistry = ToolRegistryHolder.INSTANCE.registry();\n try {\n int count = new ToolDiscovery(toolRegistry).scanAndRegister(List.of(new BuiltinTools()));\n logger.info(\"[Agent] Registered {} builtin tool(s)\", count);\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to discover builtin tools\", e);\n }\n agent.withToolRegistry(toolRegistry);\n\n // 2. 确定双路径模型:\n // - 推理/慢路径 (System 2):使用 config.defaultModelId()\n // - 指令/快路径 (System 1):从 model.json 的 planner.instruction_model_id 读取,\n // 未设置则回退到 defaultModelId()\n String reasoningModelId = config.defaultModelId();\n String instructionModelId = reasoningModelId;\n org.cland.alice.model.ModelConfigLoader.PlannerConfig plannerCfg = null;\n try {\n var configLoader = new org.cland.alice.model.ModelConfigLoader();\n configLoader.load();\n plannerCfg = configLoader.getPlannerConfig();\n if (plannerCfg != null) {\n if (plannerCfg.instructionModelId() != null) {\n instructionModelId = plannerCfg.instructionModelId();\n }\n if (plannerCfg.reasoningModelId() != null) {\n reasoningModelId = plannerCfg.reasoningModelId();\n }\n }\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config: {}\", e.getMessage());\n }\n\n // 3. 初始化 PlannerService(双路径规划引擎)\n var plannerSupplier =\n DefaultPlannerModelSupplier.builder()\n .provider(ModelProvider.getInstance())\n .instructionModelId(instructionModelId)\n .reasoningModelId(reasoningModelId)\n .plannerConfig(plannerCfg)\n .build();\n var fastPath = new FastPathStrategy(plannerSupplier);\n var thinkingTree = new ThinkingTree(Map.of());\n var slowPath =\n SlowPathStrategy.builder()\n .tree(thinkingTree)\n .modelSupplier(plannerSupplier)\n .mctsIterations(10)\n .build();\n var selector = StrategySelector.builder().fastPath(fastPath).slowPath(slowPath).build();\n // SOP 静态规划器(可选)可通过 withPlannerService() 注入。\n // 调用者(如 alice-bootstrap 或 facade 模块)若有 alice-memory-vault 访问权限,\n // 可创建 StaticPlanner + SopRegistry 并通过 PlannerService.builder().staticPlannerFn() 注入。\n var planner = PlannerService.builder().strategySelector(selector).build();\n agent.withPlannerService(planner);\n\n logger.info(\n \"[Agent] Default Agent created: reasoningModel={}, instructionModel={}\",\n config.defaultModelId(),\n instructionModelId);\n\n return agent;\n }\n}\n"}, "799ea32095e6bad0d1a694bdcc629958685c6397": {"t": 1785755689.045456, "n": 23147, "h": "b1ca170cf4dde6df", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.Plan;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.core.planner.strategy.FastPathStrategy;\nimport org.cland.alice.core.planner.strategy.SlowPathStrategy;\nimport org.cland.alice.core.planner.strategy.StrategySelector;\nimport org.cland.alice.core.planner.tree.ThinkingTree;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.cland.alice.tool.gateway.ToolRegistryHolder;\nimport org.cland.alice.tool.gateway.builtin.BuiltinTools;\nimport org.cland.alice.tool.gateway.engine.ToolDiscovery;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent implements AgentFacade {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n @Override\n public String agentId() {\n return agentId;\n }\n\n @Override\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n /**\n * 注入静态规划(SOP)函数,重建 {@link PlannerService} 以支持 SOP 匹配。\n *\n * <p>如果当前已有 PlannerService,会复用其 StrategySelector 和现有的 SOP 函数。 该函数通常来自 {@code alice-memory-vault}\n * 的 {@code StaticPlanner::plan}。\n *\n * @param staticPlannerFn 接收上下文 Map 返回 Plan 的函数(null 表示无 SOP)\n */\n public Agent withStaticPlanner(\n java.util.function.Function<Map<String, Object>, Plan> staticPlannerFn) {\n if (this.plannerService != null) {\n this.plannerService =\n PlannerService.builder()\n .strategySelector(this.plannerService.strategySelector())\n .staticPlannerFn(staticPlannerFn)\n .build();\n }\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n @Override\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n @Override\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n @Override\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n @Override\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n @Override\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n @Override\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n\n // ================================================================\n // 静态工厂方法 — 供 TUI/CLI 外观模块使用,避免外观直接依赖子模块\n // ================================================================\n\n /**\n * 初始化 ModelProvider:从 {@code ~/.alice/model.json} 加载配置,注册提供商和内置模型枚举。\n *\n * <p>此方法负责:\n *\n * <ul>\n * <li>加载 {@code ~/.alice/model.json} 配置文件\n * <li>将配置中的提供商注册到 {@link ModelProvider}\n * <li>注册内置模型枚举\n * <li>如果环境变量中存在 DEEPSEEK_API_KEY,注册 DeepSeek 供应商\n * </ul>\n *\n * @return 默认模型 ID(配置中指定),若无则返回 {@code null}\n */\n public static String initModelProvider() {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n try {\n configLoader.load();\n configLoader.registerTo(ModelProvider.getInstance());\n logger.info(\"[Agent] Loaded {} model(s) from config\", configLoader.getModelPool().size());\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config, using defaults: {}\", e.getMessage());\n }\n\n // 注册内置模型枚举\n ModelProvider.getInstance().registerBuiltinModels();\n\n // 确定默认模型\n String defaultModel = configLoader.getDefaultModel();\n if (defaultModel == null || defaultModel.isBlank()) {\n defaultModel = \"gpt-4o-mini\";\n logger.info(\n \"[Agent] No default_model in ~/.alice/model.json, using built-in: {}\", defaultModel);\n } else {\n logger.info(\"[Agent] Using default model from config: {}\", defaultModel);\n }\n\n // 注册 DeepSeek 供应商(如果环境变量存在且尚未注册)\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (ModelProvider.getInstance().getSupplier(\"deepseek-v4-flash\") == null) {\n ModelProvider.getInstance()\n .registerSupplier(\n new org.cland.alice.model.supplier.OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"[Agent] Registered DeepSeek supplier via OpenAiSupplier (OpenAI-compatible)\");\n }\n }\n\n return defaultModel;\n }\n\n /**\n * 创建完全初始化的 Agent 实例,自动装配所有子模块(PlannerService、ToolRegistry 等)。\n *\n * <p>外观模块可以直接使用此工厂方法创建 Agent,无需直接依赖 planner/tool-gateway/model 模块。\n *\n * @param config Agent 配置\n * @return 已装配所有子模块的 Agent 实例\n */\n public static Agent createDefault(AgentConfig config) {\n Agent agent = new Agent(config);\n\n // 1. 初始化工具注册中心并发现内置工具\n ToolRegistry toolRegistry = ToolRegistryHolder.INSTANCE.registry();\n try {\n int count = new ToolDiscovery(toolRegistry).scanAndRegister(List.of(new BuiltinTools()));\n logger.info(\"[Agent] Registered {} builtin tool(s)\", count);\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to discover builtin tools\", e);\n }\n agent.withToolRegistry(toolRegistry);\n\n // 2. 确定双路径模型:\n // - 推理/慢路径 (System 2):使用 config.defaultModelId()\n // - 指令/快路径 (System 1):从 model.json 的 planner.instruction_model_id 读取,\n // 未设置则回退到 defaultModelId()\n String reasoningModelId = config.defaultModelId();\n String instructionModelId = reasoningModelId;\n org.cland.alice.model.ModelConfigLoader.PlannerConfig plannerCfg = null;\n try {\n var configLoader = new org.cland.alice.model.ModelConfigLoader();\n configLoader.load();\n plannerCfg = configLoader.getPlannerConfig();\n if (plannerCfg != null) {\n if (plannerCfg.instructionModelId() != null) {\n instructionModelId = plannerCfg.instructionModelId();\n }\n if (plannerCfg.reasoningModelId() != null) {\n reasoningModelId = plannerCfg.reasoningModelId();\n }\n }\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config: {}\", e.getMessage());\n }\n\n // 3. 初始化 PlannerService(双路径规划引擎)\n var plannerSupplier =\n DefaultPlannerModelSupplier.builder()\n .provider(ModelProvider.getInstance())\n .instructionModelId(instructionModelId)\n .reasoningModelId(reasoningModelId)\n .plannerConfig(plannerCfg)\n .build();\n var fastPath = new FastPathStrategy(plannerSupplier);\n var thinkingTree = new ThinkingTree(Map.of());\n var slowPath =\n SlowPathStrategy.builder()\n .tree(thinkingTree)\n .modelSupplier(plannerSupplier)\n .mctsIterations(10)\n .build();\n var selector = StrategySelector.builder().fastPath(fastPath).slowPath(slowPath).build();\n // SOP 静态规划器(可选)可通过 withPlannerService() 注入。\n // 调用者(如 alice-bootstrap 或 facade 模块)若有 alice-memory-vault 访问权限,\n // 可创建 StaticPlanner + SopRegistry 并通过 PlannerService.builder().staticPlannerFn() 注入。\n var planner = PlannerService.builder().strategySelector(selector).build();\n agent.withPlannerService(planner);\n\n logger.info(\n \"[Agent] Default Agent created: reasoningModel={}, instructionModel={}\",\n config.defaultModelId(),\n instructionModelId);\n\n return agent;\n }\n}\n"}, "42915a7f3e5187792481e9a3b8034547a906d6a3": {"t": 1785755690.421439, "n": 23028, "h": "c95e96f46a9a599e", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.Plan;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.core.planner.strategy.FastPathStrategy;\nimport org.cland.alice.core.planner.strategy.SlowPathStrategy;\nimport org.cland.alice.core.planner.strategy.StrategySelector;\nimport org.cland.alice.core.planner.tree.ThinkingTree;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.cland.alice.tool.gateway.ToolRegistryHolder;\nimport org.cland.alice.tool.gateway.builtin.BuiltinTools;\nimport org.cland.alice.tool.gateway.engine.ToolDiscovery;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n /**\n * 注入静态规划(SOP)函数,重建 {@link PlannerService} 以支持 SOP 匹配。\n *\n * <p>如果当前已有 PlannerService,会复用其 StrategySelector 和现有的 SOP 函数。 该函数通常来自 {@code alice-memory-vault}\n * 的 {@code StaticPlanner::plan}。\n *\n * @param staticPlannerFn 接收上下文 Map 返回 Plan 的函数(null 表示无 SOP)\n */\n public Agent withStaticPlanner(\n java.util.function.Function<Map<String, Object>, Plan> staticPlannerFn) {\n if (this.plannerService != null) {\n this.plannerService =\n PlannerService.builder()\n .strategySelector(this.plannerService.strategySelector())\n .staticPlannerFn(staticPlannerFn)\n .build();\n }\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n\n // ================================================================\n // 静态工厂方法 — 供 TUI/CLI 外观模块使用,避免外观直接依赖子模块\n // ================================================================\n\n /**\n * 初始化 ModelProvider:从 {@code ~/.alice/model.json} 加载配置,注册提供商和内置模型枚举。\n *\n * <p>此方法负责:\n *\n * <ul>\n * <li>加载 {@code ~/.alice/model.json} 配置文件\n * <li>将配置中的提供商注册到 {@link ModelProvider}\n * <li>注册内置模型枚举\n * <li>如果环境变量中存在 DEEPSEEK_API_KEY,注册 DeepSeek 供应商\n * </ul>\n *\n * @return 默认模型 ID(配置中指定),若无则返回 {@code null}\n */\n public static String initModelProvider() {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n try {\n configLoader.load();\n configLoader.registerTo(ModelProvider.getInstance());\n logger.info(\"[Agent] Loaded {} model(s) from config\", configLoader.getModelPool().size());\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config, using defaults: {}\", e.getMessage());\n }\n\n // 注册内置模型枚举\n ModelProvider.getInstance().registerBuiltinModels();\n\n // 确定默认模型\n String defaultModel = configLoader.getDefaultModel();\n if (defaultModel == null || defaultModel.isBlank()) {\n defaultModel = \"gpt-4o-mini\";\n logger.info(\n \"[Agent] No default_model in ~/.alice/model.json, using built-in: {}\", defaultModel);\n } else {\n logger.info(\"[Agent] Using default model from config: {}\", defaultModel);\n }\n\n // 注册 DeepSeek 供应商(如果环境变量存在且尚未注册)\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (ModelProvider.getInstance().getSupplier(\"deepseek-v4-flash\") == null) {\n ModelProvider.getInstance()\n .registerSupplier(\n new org.cland.alice.model.supplier.OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"[Agent] Registered DeepSeek supplier via OpenAiSupplier (OpenAI-compatible)\");\n }\n }\n\n return defaultModel;\n }\n\n /**\n * 创建完全初始化的 Agent 实例,自动装配所有子模块(PlannerService、ToolRegistry 等)。\n *\n * <p>外观模块可以直接使用此工厂方法创建 Agent,无需直接依赖 planner/tool-gateway/model 模块。\n *\n * @param config Agent 配置\n * @return 已装配所有子模块的 Agent 实例\n */\n public static Agent createDefault(AgentConfig config) {\n Agent agent = new Agent(config);\n\n // 1. 初始化工具注册中心并发现内置工具\n ToolRegistry toolRegistry = ToolRegistryHolder.INSTANCE.registry();\n try {\n int count = new ToolDiscovery(toolRegistry).scanAndRegister(List.of(new BuiltinTools()));\n logger.info(\"[Agent] Registered {} builtin tool(s)\", count);\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to discover builtin tools\", e);\n }\n agent.withToolRegistry(toolRegistry);\n\n // 2. 确定双路径模型:\n // - 推理/慢路径 (System 2):使用 config.defaultModelId()\n // - 指令/快路径 (System 1):从 model.json 的 planner.instruction_model_id 读取,\n // 未设置则回退到 defaultModelId()\n String reasoningModelId = config.defaultModelId();\n String instructionModelId = reasoningModelId;\n org.cland.alice.model.ModelConfigLoader.PlannerConfig plannerCfg = null;\n try {\n var configLoader = new org.cland.alice.model.ModelConfigLoader();\n configLoader.load();\n plannerCfg = configLoader.getPlannerConfig();\n if (plannerCfg != null) {\n if (plannerCfg.instructionModelId() != null) {\n instructionModelId = plannerCfg.instructionModelId();\n }\n if (plannerCfg.reasoningModelId() != null) {\n reasoningModelId = plannerCfg.reasoningModelId();\n }\n }\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config: {}\", e.getMessage());\n }\n\n // 3. 初始化 PlannerService(双路径规划引擎)\n var plannerSupplier =\n DefaultPlannerModelSupplier.builder()\n .provider(ModelProvider.getInstance())\n .instructionModelId(instructionModelId)\n .reasoningModelId(reasoningModelId)\n .plannerConfig(plannerCfg)\n .build();\n var fastPath = new FastPathStrategy(plannerSupplier);\n var thinkingTree = new ThinkingTree(Map.of());\n var slowPath =\n SlowPathStrategy.builder()\n .tree(thinkingTree)\n .modelSupplier(plannerSupplier)\n .mctsIterations(10)\n .build();\n var selector = StrategySelector.builder().fastPath(fastPath).slowPath(slowPath).build();\n // SOP 静态规划器(可选)可通过 withPlannerService() 注入。\n // 调用者(如 alice-bootstrap 或 facade 模块)若有 alice-memory-vault 访问权限,\n // 可创建 StaticPlanner + SopRegistry 并通过 PlannerService.builder().staticPlannerFn() 注入。\n var planner = PlannerService.builder().strategySelector(selector).build();\n agent.withPlannerService(planner);\n\n logger.info(\n \"[Agent] Default Agent created: reasoningModel={}, instructionModel={}\",\n config.defaultModelId(),\n instructionModelId);\n\n return agent;\n }\n}\n"}, "542dfef86a8f56af43d54230584fe2edf9c80f6a": {"t": 1785755691.7581124, "n": 22353, "h": "d1b6d3faedae46ee", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.core.planner.sop.SopRegistry;\nimport org.cland.alice.core.planner.sop.StaticPlanner;\nimport org.cland.alice.core.planner.strategy.FastPathStrategy;\nimport org.cland.alice.core.planner.strategy.SlowPathStrategy;\nimport org.cland.alice.core.planner.strategy.StrategySelector;\nimport org.cland.alice.core.planner.tree.ThinkingTree;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.cland.alice.tool.gateway.ToolRegistryHolder;\nimport org.cland.alice.tool.gateway.builtin.BuiltinTools;\nimport org.cland.alice.tool.gateway.engine.ToolDiscovery;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n\n // ================================================================\n // 静态工厂方法 — 供 TUI/CLI 外观模块使用,避免外观直接依赖子模块\n // ================================================================\n\n /**\n * 初始化 ModelProvider:从 {@code ~/.alice/model.json} 加载配置,注册提供商和内置模型枚举。\n *\n * <p>此方法负责:\n *\n * <ul>\n * <li>加载 {@code ~/.alice/model.json} 配置文件\n * <li>将配置中的提供商注册到 {@link ModelProvider}\n * <li>注册内置模型枚举\n * <li>如果环境变量中存在 DEEPSEEK_API_KEY,注册 DeepSeek 供应商\n * </ul>\n *\n * @return 默认模型 ID(配置中指定),若无则返回 {@code null}\n */\n public static String initModelProvider() {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n try {\n configLoader.load();\n configLoader.registerTo(ModelProvider.getInstance());\n logger.info(\"[Agent] Loaded {} model(s) from config\", configLoader.getModelPool().size());\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config, using defaults: {}\", e.getMessage());\n }\n\n // 注册内置模型枚举\n ModelProvider.getInstance().registerBuiltinModels();\n\n // 确定默认模型\n String defaultModel = configLoader.getDefaultModel();\n if (defaultModel == null || defaultModel.isBlank()) {\n defaultModel = \"gpt-4o-mini\";\n logger.info(\n \"[Agent] No default_model in ~/.alice/model.json, using built-in: {}\", defaultModel);\n } else {\n logger.info(\"[Agent] Using default model from config: {}\", defaultModel);\n }\n\n // 注册 DeepSeek 供应商(如果环境变量存在且尚未注册)\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (ModelProvider.getInstance().getSupplier(\"deepseek-v4-flash\") == null) {\n ModelProvider.getInstance()\n .registerSupplier(\n new org.cland.alice.model.supplier.OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"[Agent] Registered DeepSeek supplier via OpenAiSupplier (OpenAI-compatible)\");\n }\n }\n\n return defaultModel;\n }\n\n /**\n * 创建完全初始化的 Agent 实例,自动装配所有子模块(PlannerService、ToolRegistry 等)。\n *\n * <p>外观模块可以直接使用此工厂方法创建 Agent,无需直接依赖 planner/tool-gateway/model 模块。\n *\n * @param config Agent 配置\n * @return 已装配所有子模块的 Agent 实例\n */\n public static Agent createDefault(AgentConfig config) {\n Agent agent = new Agent(config);\n\n // 1. 初始化工具注册中心并发现内置工具\n ToolRegistry toolRegistry = ToolRegistryHolder.INSTANCE.registry();\n try {\n int count = new ToolDiscovery(toolRegistry).scanAndRegister(List.of(new BuiltinTools()));\n logger.info(\"[Agent] Registered {} builtin tool(s)\", count);\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to discover builtin tools\", e);\n }\n agent.withToolRegistry(toolRegistry);\n\n // 2. 确定双路径模型:\n // - 推理/慢路径 (System 2):使用 config.defaultModelId()\n // - 指令/快路径 (System 1):从 model.json 的 planner.instruction_model_id 读取,\n // 未设置则回退到 defaultModelId()\n String reasoningModelId = config.defaultModelId();\n String instructionModelId = reasoningModelId;\n org.cland.alice.model.ModelConfigLoader.PlannerConfig plannerCfg = null;\n try {\n var configLoader = new org.cland.alice.model.ModelConfigLoader();\n configLoader.load();\n plannerCfg = configLoader.getPlannerConfig();\n if (plannerCfg != null) {\n if (plannerCfg.instructionModelId() != null) {\n instructionModelId = plannerCfg.instructionModelId();\n }\n if (plannerCfg.reasoningModelId() != null) {\n reasoningModelId = plannerCfg.reasoningModelId();\n }\n }\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config: {}\", e.getMessage());\n }\n\n // 3. 初始化 PlannerService(双路径规划引擎)\n var plannerSupplier =\n DefaultPlannerModelSupplier.builder()\n .provider(ModelProvider.getInstance())\n .instructionModelId(instructionModelId)\n .reasoningModelId(reasoningModelId)\n .plannerConfig(plannerCfg)\n .build();\n var fastPath = new FastPathStrategy(plannerSupplier);\n var thinkingTree = new ThinkingTree(Map.of());\n var slowPath =\n SlowPathStrategy.builder()\n .tree(thinkingTree)\n .modelSupplier(plannerSupplier)\n .mctsIterations(10)\n .build();\n var selector = StrategySelector.builder().fastPath(fastPath).slowPath(slowPath).build();\n var sopRegistry = new SopRegistry();\n var staticPlanner = new StaticPlanner(sopRegistry);\n var planner =\n PlannerService.builder().strategySelector(selector).staticPlanner(staticPlanner).build();\n agent.withPlannerService(planner);\n\n logger.info(\n \"[Agent] Default Agent created: reasoningModel={}, instructionModel={}\",\n config.defaultModelId(),\n instructionModelId);\n\n return agent;\n }\n}\n"}, "244f833a17dd9e983dda744e688a93601dac1197": {"t": 1785755693.408358, "n": 22095, "h": "1098c21e0e6402ee", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.core.planner.sop.SopRegistry;\nimport org.cland.alice.core.planner.sop.StaticPlanner;\nimport org.cland.alice.core.planner.strategy.FastPathStrategy;\nimport org.cland.alice.core.planner.strategy.SlowPathStrategy;\nimport org.cland.alice.core.planner.strategy.StrategySelector;\nimport org.cland.alice.core.planner.tree.ThinkingTree;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.cland.alice.tool.gateway.ToolRegistryHolder;\nimport org.cland.alice.tool.gateway.builtin.BuiltinTools;\nimport org.cland.alice.tool.gateway.engine.ToolDiscovery;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n\n // ================================================================\n // 静态工厂方法 — 供 TUI/CLI 外观模块使用,避免外观直接依赖子模块\n // ================================================================\n\n /**\n * 初始化 ModelProvider:从 {@code ~/.alice/model.json} 加载配置,注册提供商和内置模型枚举。\n *\n * <p>此方法负责:\n *\n * <ul>\n * <li>加载 {@code ~/.alice/model.json} 配置文件\n * <li>将配置中的提供商注册到 {@link ModelProvider}\n * <li>注册内置模型枚举\n * <li>如果环境变量中存在 DEEPSEEK_API_KEY,注册 DeepSeek 供应商\n * </ul>\n *\n * @return 默认模型 ID(配置中指定),若无则返回 {@code null}\n */\n public static String initModelProvider() {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n try {\n configLoader.load();\n configLoader.registerTo(ModelProvider.getInstance());\n logger.info(\n \"[Agent] Loaded {} model provider(s) from config\", configLoader.getProviders().size());\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config, using defaults: {}\", e.getMessage());\n }\n\n // 注册内置模型枚举\n ModelProvider.getInstance().registerBuiltinModels();\n\n // 确定默认模型\n String defaultModel = configLoader.getDefaultModel();\n if (defaultModel == null || defaultModel.isBlank()) {\n defaultModel = \"gpt-4o-mini\";\n logger.info(\n \"[Agent] No default_model in ~/.alice/model.json, using built-in: {}\", defaultModel);\n } else {\n logger.info(\"[Agent] Using default model from config: {}\", defaultModel);\n }\n\n // 注册 DeepSeek 供应商(如果环境变量存在且尚未注册)\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (ModelProvider.getInstance().getSupplier(\"deepseek-v4-flash\") == null) {\n ModelProvider.getInstance()\n .registerSupplier(\n new org.cland.alice.model.supplier.OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"[Agent] Registered DeepSeek supplier via OpenAiSupplier (OpenAI-compatible)\");\n }\n }\n\n return defaultModel;\n }\n\n /**\n * 创建完全初始化的 Agent 实例,自动装配所有子模块(PlannerService、ToolRegistry 等)。\n *\n * <p>外观模块可以直接使用此工厂方法创建 Agent,无需直接依赖 planner/tool-gateway/model 模块。\n *\n * @param config Agent 配置\n * @return 已装配所有子模块的 Agent 实例\n */\n public static Agent createDefault(AgentConfig config) {\n Agent agent = new Agent(config);\n\n // 1. 初始化工具注册中心并发现内置工具\n ToolRegistry toolRegistry = ToolRegistryHolder.INSTANCE.registry();\n try {\n int count = new ToolDiscovery(toolRegistry).scanAndRegister(List.of(new BuiltinTools()));\n logger.info(\"[Agent] Registered {} builtin tool(s)\", count);\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to discover builtin tools\", e);\n }\n agent.withToolRegistry(toolRegistry);\n\n // 2. 确定双路径模型:\n // - 推理/慢路径 (System 2):使用 config.defaultModelId()\n // - 指令/快路径 (System 1):从 model.json 的 instruction_model 读取,\n // 未设置则回退到 defaultModelId()\n String instructionModelId = config.defaultModelId();\n try {\n var configLoader = new org.cland.alice.model.ModelConfigLoader();\n configLoader.load();\n String configuredInstruction = configLoader.getInstructionModel();\n if (configuredInstruction != null && !configuredInstruction.isBlank()) {\n instructionModelId = configuredInstruction;\n }\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config for instruction model: {}\", e.getMessage());\n }\n\n // 3. 初始化 PlannerService(双路径规划引擎)\n var plannerSupplier =\n DefaultPlannerModelSupplier.builder()\n .provider(ModelProvider.getInstance())\n .instructionModelId(instructionModelId)\n .reasoningModelId(config.defaultModelId())\n .build();\n var fastPath = new FastPathStrategy(plannerSupplier);\n var thinkingTree = new ThinkingTree(Map.of());\n var slowPath =\n SlowPathStrategy.builder()\n .tree(thinkingTree)\n .modelSupplier(plannerSupplier)\n .mctsIterations(10)\n .build();\n var selector = StrategySelector.builder().fastPath(fastPath).slowPath(slowPath).build();\n var sopRegistry = new SopRegistry();\n var staticPlanner = new StaticPlanner(sopRegistry);\n var planner =\n PlannerService.builder().strategySelector(selector).staticPlanner(staticPlanner).build();\n agent.withPlannerService(planner);\n\n logger.info(\n \"[Agent] Default Agent created: reasoningModel={}, instructionModel={}\",\n config.defaultModelId(),\n instructionModelId);\n\n return agent;\n }\n}\n"}, "f19ac8fe18e0fa7bc3f0c607030d04ee4c9298ff": {"t": 1785755694.95558, "n": 21332, "h": "1d50b31620fcb63d", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.core.planner.sop.SopRegistry;\nimport org.cland.alice.core.planner.sop.StaticPlanner;\nimport org.cland.alice.core.planner.strategy.FastPathStrategy;\nimport org.cland.alice.core.planner.strategy.SlowPathStrategy;\nimport org.cland.alice.core.planner.strategy.StrategySelector;\nimport org.cland.alice.core.planner.tree.ThinkingTree;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelConfigLoader;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.cland.alice.tool.gateway.ToolRegistryHolder;\nimport org.cland.alice.tool.gateway.builtin.BuiltinTools;\nimport org.cland.alice.tool.gateway.engine.ToolDiscovery;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n\n // ================================================================\n // 静态工厂方法 — 供 TUI/CLI 外观模块使用,避免外观直接依赖子模块\n // ================================================================\n\n /**\n * 初始化 ModelProvider:从 {@code ~/.alice/model.json} 加载配置,注册提供商和内置模型枚举。\n *\n * <p>此方法负责:\n *\n * <ul>\n * <li>加载 {@code ~/.alice/model.json} 配置文件\n * <li>将配置中的提供商注册到 {@link ModelProvider}\n * <li>注册内置模型枚举\n * <li>如果环境变量中存在 DEEPSEEK_API_KEY,注册 DeepSeek 供应商\n * </ul>\n *\n * @return 默认模型 ID(配置中指定),若无则返回 {@code null}\n */\n public static String initModelProvider() {\n ModelConfigLoader configLoader = new ModelConfigLoader();\n try {\n configLoader.load();\n configLoader.registerTo(ModelProvider.getInstance());\n logger.info(\n \"[Agent] Loaded {} model provider(s) from config\", configLoader.getProviders().size());\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to load model config, using defaults: {}\", e.getMessage());\n }\n\n // 注册内置模型枚举\n ModelProvider.getInstance().registerBuiltinModels();\n\n // 确定默认模型\n String defaultModel = configLoader.getDefaultModel();\n if (defaultModel == null || defaultModel.isBlank()) {\n defaultModel = \"gpt-4o-mini\";\n logger.info(\n \"[Agent] No default_model in ~/.alice/model.json, using built-in: {}\", defaultModel);\n } else {\n logger.info(\"[Agent] Using default model from config: {}\", defaultModel);\n }\n\n // 注册 DeepSeek 供应商(如果环境变量存在且尚未注册)\n String deepseekKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (deepseekKey != null && !deepseekKey.isEmpty()) {\n if (ModelProvider.getInstance().getSupplier(\"deepseek-v4-flash\") == null) {\n ModelProvider.getInstance()\n .registerSupplier(\n new org.cland.alice.model.supplier.OpenAiSupplier(\n \"deepseek\", deepseekKey, \"https://api.deepseek.com/v1/chat/completions\"));\n logger.info(\"[Agent] Registered DeepSeek supplier via OpenAiSupplier (OpenAI-compatible)\");\n }\n }\n\n return defaultModel;\n }\n\n /**\n * 创建完全初始化的 Agent 实例,自动装配所有子模块(PlannerService、ToolRegistry 等)。\n *\n * <p>外观模块可以直接使用此工厂方法创建 Agent,无需直接依赖 planner/tool-gateway/model 模块。\n *\n * @param config Agent 配置\n * @return 已装配所有子模块的 Agent 实例\n */\n public static Agent createDefault(AgentConfig config) {\n Agent agent = new Agent(config);\n\n // 1. 初始化工具注册中心并发现内置工具\n ToolRegistry toolRegistry = ToolRegistryHolder.INSTANCE.registry();\n try {\n int count = new ToolDiscovery(toolRegistry).scanAndRegister(List.of(new BuiltinTools()));\n logger.info(\"[Agent] Registered {} builtin tool(s)\", count);\n } catch (Exception e) {\n logger.warn(\"[Agent] Failed to discover builtin tools\", e);\n }\n agent.withToolRegistry(toolRegistry);\n\n // 2. 初始化 PlannerService(双路径规划引擎)\n var plannerSupplier =\n DefaultPlannerModelSupplier.builder()\n .provider(ModelProvider.getInstance())\n .instructionModelId(config.defaultModelId())\n .reasoningModelId(config.defaultModelId())\n .build();\n var fastPath = new FastPathStrategy(plannerSupplier);\n var thinkingTree = new ThinkingTree(Map.of());\n var slowPath =\n SlowPathStrategy.builder()\n .tree(thinkingTree)\n .modelSupplier(plannerSupplier)\n .mctsIterations(10)\n .build();\n var selector = StrategySelector.builder().fastPath(fastPath).slowPath(slowPath).build();\n var sopRegistry = new SopRegistry();\n var staticPlanner = new StaticPlanner(sopRegistry);\n var planner =\n PlannerService.builder().strategySelector(selector).staticPlanner(staticPlanner).build();\n agent.withPlannerService(planner);\n\n logger.info(\"[Agent] Default Agent created: model={}\", config.defaultModelId());\n\n return agent;\n }\n}\n"}, "fe30f878fd5a8725f8d36a221c40b053b2e0476f": {"t": 1785755696.5573947, "n": 16941, "h": "d40d1a4c6d7cfe7d", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n /**\n * 获取 Agent 执行器,用于注册事件监听等。\n *\n * @return AgentExecutor 实例\n */\n public AgentExecutor getExecutor() {\n return executor;\n }\n\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "a7847d0d4e799b6888fe11fe04bf188620590956": {"t": 1785755698.0833604, "n": 16800, "h": "ede7d36f35a7a061", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n /** 最近一次 LLM 调用的推理/思考过程,供前端渲染 */\n private String lastReasoning;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n // 提取推理内容供前端渲染\n lastReasoning =\n ctx.containsKey(\"__llm_reasoning\") ? ctx.get(\"__llm_reasoning\").toString() : \"\";\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n /**\n * 获取最近一次 LLM 调用的推理内容(reasoning_content),供 TUI 渲染。\n *\n * @return 推理文本,若无则返回空字符串\n */\n public String getLastReasoning() {\n return lastReasoning != null ? lastReasoning : \"\";\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "2bbe5ee1d8031e29c337a8ef11da323abd34bd40": {"t": 1785755699.6652026, "n": 16373, "h": "368977a91796e194", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.SnowflakeIdGenerator;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, String sessionId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = sessionId != null ? sessionId : SnowflakeIdGenerator.generateSessionId();\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this(agentId, null, config);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(this.sessionId, config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "cba99d96dd0d1b5a42c3db426295c398120108ad": {"t": 1785755701.109385, "n": 16160, "h": "54ff046eb9acefa2", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.memory.AgentSession;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.agent.wal.RawMessage;\nimport org.cland.alice.core.agent.wal.WalSession;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "2bb6b3f7e483c34be142c1c9ed1daba4cd65067f": {"t": 1785755702.7085521, "n": 16147, "h": "7619886731957ad7", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.memory.wal.RawMessage;\nimport org.cland.alice.memory.wal.WalSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n try {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n } catch (Exception e) {\n errorRef.set(e);\n } finally {\n latch.countDown();\n }\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "8168b802097323564028ecfcb924fc8c81ab1a63": {"t": 1785755704.3939183, "n": 16000, "h": "67b6a42e30ebc110", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.memory.wal.RawMessage;\nimport org.cland.alice.memory.wal.WalSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this.agentId = agentId != null ? agentId : UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "067678919a34b6ebe282810b9857313209380ef5": {"t": 1785755705.64542, "n": 15970, "h": "141344a2122a3a20", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Collectors;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.memory.wal.RawMessage;\nimport org.cland.alice.memory.wal.WalSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.CallStatus;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n /** 注入 {@link WalSession},启用 WAL 双轨制持久化与上下文压缩能力。 */\n public Agent withWal(WalSession wal) {\n this.executor.withWal(wal);\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * <p>完整流程:\n *\n * <ol>\n * <li>从 WAL 获取该 session 所有消息(排除已存在的 compact + system)\n * <li>组装为对话文本,调用 LLM 总结为一段紧凑摘要\n * <li>以 role {@code compact} 写入 WAL\n * <li>写 Checkpoint 标记旧消息可被 WalCompactor 清理\n * </ol>\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n\n WalSession wal = executor.wal();\n if (wal == null) {\n return \"上下文压缩失败:WAL 未注入,无法获取历史消息\";\n }\n\n // 1. 获取该 session 所有消息\n java.util.List<RawMessage> allMessages = wal.getAllMessages(sessionId);\n if (allMessages.isEmpty()) {\n return \"没有历史消息需要压缩\";\n }\n\n // 2. 选出可压缩的消息(排除 system + 已存在的 compact)\n java.util.List<RawMessage> compressible =\n allMessages.stream()\n .filter(m -> !\"compact\".equals(m.role()))\n .filter(m -> !\"system\".equals(m.role()))\n .collect(Collectors.toList());\n if (compressible.isEmpty()) {\n return \"没有可压缩的消息(全部已为 compact 或 system)\";\n }\n\n // 3. 组装 LLM 总结 prompt\n StringBuilder dialogBuilder = new StringBuilder();\n dialogBuilder.append(\"请将以下对话提炼为一段紧凑的中文摘要,保留关键事实、决策和工具调用结果。\\n\\n\");\n for (RawMessage msg : compressible) {\n String roleLabel =\n switch (msg.role()) {\n case \"user\" -> \"用户\";\n case \"assistant\" -> \"助手\";\n case \"tool\" -> \"工具结果\";\n default -> msg.role();\n };\n dialogBuilder.append(\"【\").append(roleLabel).append(\"】\");\n if (msg.content() != null) {\n dialogBuilder.append(\" \").append(msg.content());\n }\n if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {\n String toolCallStr =\n msg.toolCalls().stream()\n .map(tc -> tc.function().name() + \"(\" + tc.function().arguments() + \")\")\n .collect(Collectors.joining(\", \"));\n dialogBuilder.append(\" [调用工具: \").append(toolCallStr).append(\"]\");\n }\n dialogBuilder.append(\"\\n\");\n }\n\n // 4. 调用 LLM 总结\n String summaryContent;\n try {\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(config.defaultModelId(), dialogBuilder.toString());\n if (result.status() == CallStatus.FINISHED && result.result() != null) {\n summaryContent = result.result().content();\n } else {\n logger.warn(\"LLM compact summary failed: status={}\", result.status());\n summaryContent = \"[压缩摘要生成失败] \" + compressible.size() + \" 条历史消息\";\n }\n } catch (Exception e) {\n logger.error(\"LLM compact summary error\", e);\n summaryContent = \"[压缩摘要生成异常] \" + compressible.size() + \" 条历史消息: \" + e.getMessage();\n }\n\n // 5. 以 compact role 写入 WAL\n wal.compact(sessionId, summaryContent);\n\n // 6. 写时间戳到 longTermMemory\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, java.time.Instant.now().toString());\n }\n\n logger.info(\n \"Context compacted: {} messages → 1 compact summary (session={})\",\n compressible.size(),\n sessionId);\n return \"上下文压缩完成:将 \" + compressible.size() + \" 条历史消息提炼为 1 条摘要\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "7d35985d4f861780abf7f36eb3f2fa62bea9c786": {"t": 1785755706.950339, "n": 12962, "h": "a374724a8302b899", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n public Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n // 如果有 WAL,先写入(持久化短期记忆到长期记忆作为 checkpoint 的替代)\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, java.time.Instant.now().toString());\n }\n // TODO: 触发 LLM 总结(需等待 Memory 模块提供总结接口)\n return \"上下文压缩完成(释放 Token: N/A,待 Memory 模块提供总结接口)\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "4ce49cdd19b4e7104c64117ccee3c297b2997aac": {"t": 1785755708.3939693, "n": 12955, "h": "1c09a89e77dcc4c9", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final String sessionId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.sessionId = java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(ctx.sessionId());\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n // 如果有 WAL,先写入(持久化短期记忆到长期记忆作为 checkpoint 的替代)\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, java.time.Instant.now().toString());\n }\n // TODO: 触发 LLM 总结(需等待 Memory 模块提供总结接口)\n return \"上下文压缩完成(释放 Token: N/A,待 Memory 模块提供总结接口)\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "e99821cb1d30ed6f81c5c3bfea208c0fb9cb4642": {"t": 1785755709.737224, "n": 12811, "h": "e7b6e8ab42f35415", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(sessionId);\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \").append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet()).append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n // 如果有 WAL,先写入(持久化短期记忆到长期记忆作为 checkpoint 的替代)\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, java.time.Instant.now().toString());\n }\n // TODO: 触发 LLM 总结(需等待 Memory 模块提供总结接口)\n return \"上下文压缩完成(释放 Token: N/A,待 Memory 模块提供总结接口)\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "6ac7c7c409fdeda5f270fc91665793da3d5507a0": {"t": 1785755711.068583, "n": 12832, "h": "e947a9192a0145f6", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(agentId);\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \")\n .append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet())\n .append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(agentId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n // 如果有 WAL,先写入(持久化短期记忆到长期记忆作为 checkpoint 的替代)\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + agentId, java.time.Instant.now().toString());\n }\n // TODO: 触发 LLM 总结(需等待 Memory 模块提供总结接口)\n return \"上下文压缩完成(释放 Token: N/A,待 Memory 模块提供总结接口)\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\n \"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "54c104cd19d2e340e51ebaa92cae552fe6b2fc43": {"t": 1785755712.30239, "n": 12811, "h": "e7b6e8ab42f35415", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n // ========== 上下文管理与查询接口 ==========\n\n /**\n * 获取当前全量 Context 状态(Token 占用、消息滑动窗口、变量快照)。\n *\n * <p>支持 {@code /context} 命令查询。返回的字符串以 Markdown 格式呈现。\n *\n * @return 格式化后的上下文状态信息\n */\n public String getActiveContext() {\n AgentContext ctx = new AgentContext();\n StringBuilder sb = new StringBuilder();\n sb.append(\"── 上下文状态 ──\\n\");\n sb.append(\"| 属性 | 值 |\\n\");\n sb.append(\"|---|---|\\n\");\n sb.append(\"| 会话 ID | \").append(ctx.sessionId()).append(\" |\\n\");\n sb.append(\"| Agent ID | \").append(agentId).append(\" |\\n\");\n sb.append(\"| 默认模型 | \").append(config.defaultModelId()).append(\" |\\n\");\n sb.append(\"| 最大迭代 | \").append(config.maxIterations()).append(\" |\\n\");\n\n if (memory != null) {\n try {\n String shortTerm = memory.getShortTerm(sessionId);\n int msgCount = shortTerm.isEmpty() ? 0 : shortTerm.split(\"\\n\").length;\n sb.append(\"| 消息条数 | \").append(msgCount).append(\" |\\n\");\n sb.append(\"| Token 占用 | N/A (Token 计数器待集成) |\\n\");\n } catch (Exception e) {\n sb.append(\"| Memory 状态 | 异常: \").append(e.getMessage()).append(\" |\\n\");\n }\n } else {\n sb.append(\"| Token 占用 | N/A (Memory 未注入) |\\n\");\n sb.append(\"| 消息滑动窗口 | N/A |\\n\");\n }\n\n sb.append(\"| 变量快照 | \").append(ctx.asMap().isEmpty() ? \"空\" : ctx.asMap().keySet()).append(\" |\\n\");\n return sb.toString();\n }\n\n /**\n * 清空短期记忆(保留 System Prompt / Rules),重置 Token 计数器。\n *\n * <p>支持 {@code /clear} 命令执行。\n */\n public void clearMemory() {\n logger.info(\"Clearing memory for agent {}\", agentId);\n if (memory != null) {\n memory.clearSession(sessionId);\n }\n }\n\n /**\n * 将历史对话写入 WAL(如果启用了 WAL),提炼历史为 Summary 事实快照,释放 Context Window。\n *\n * <p>支持 {@code /compact} 命令执行。\n *\n * @return 压缩结果信息\n */\n public String compactContext() {\n logger.info(\"Compacting context for agent {}\", agentId);\n // 如果有 WAL,先写入(持久化短期记忆到长期记忆作为 checkpoint 的替代)\n if (memory != null) {\n memory.putLongTerm(\"__last_compact_ts_\" + sessionId, java.time.Instant.now().toString());\n }\n // TODO: 触发 LLM 总结(需等待 Memory 模块提供总结接口)\n return \"上下文压缩完成(释放 Token: N/A,待 Memory 模块提供总结接口)\";\n }\n\n /**\n * 动态切换 LLM 引擎。\n *\n * <p>支持 {@code /model} 命令执行。切换后同步刷新 Verification 模块的审计敏感度。\n *\n * @param modelId 目标模型标识\n */\n public void switchModel(String modelId) {\n logger.info(\"Switching model from {} to {} for agent {}\", config.defaultModelId(), modelId, agentId);\n // 更新配置中的默认模型\n // TODO: 当 AgentConfig 支持动态修改时,更新 defaultModelId\n }\n\n /**\n * 注入人类反馈到 Context。\n *\n * <p>支持 {@code /feedback} 命令执行。\n *\n * @param feedback 用户的反馈内容\n */\n public void injectFeedback(String feedback) {\n logger.info(\"Injecting feedback for agent {}: {}\", agentId, feedback);\n // 将反馈注入到 Agent 上下文\n AgentContext ctx = new AgentContext();\n ctx.put(\"lastFeedback\", feedback);\n // TODO: 解除 Agent 的 HITL 挂起状态(待 AgentExecutor 暴露 HumanInTheLoop 接口)\n if (executor != null) {\n executor.resumeWithFeedback(feedback);\n }\n }\n\n /**\n * 获取最后一条反馈(如果有)。\n *\n * @return 反馈内容,或 {@code null}\n */\n public String feedback() {\n // TODO: 从 AgentContext 或 Memory 中提取最后一条反馈\n return null;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "ecba2ea793e0640c9d3e7fb067725c450ccae401": {"t": 1785755713.5827382, "n": 9663, "h": "39ea945538102bf6", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.agent.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "e1f1e0b38c7a15c8be8e1eeb149fbf0adaa200e7": {"t": 1785755714.9592817, "n": 9657, "h": "14db3c272149cbbd", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "25dd4a984cba6c0d1dfee910461b9abb0dbcff7b": {"t": 1785755716.4490776, "n": 9719, "h": "84f075d83fae6de2", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.lifecycle.Action;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.core.planner.PlannerService;\nimport org.cland.alice.env.adapter.EnvEvent;\nimport org.cland.alice.guardrail.Verificator;\nimport org.cland.alice.memory.AgentSession;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.cland.alice.tool.gateway.ToolRegistry;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。\n *\n * <p>Agent 同时承担了 {@code AgentCore} 的角色,作为 PPAO 循环的核心协调者, 持有所有子模块(规划器、安全校验、工具注册中心、记忆、环境适配器)的引用。\n *\n * <p>生命周期状态机:\n *\n * <pre>\n * START -> PERCEIVING -> PLANNING -> VERIFYING_PRE -> ACTING (Micro-ReAct)\n * -> OBSERVING -> VERIFYING_POST -> REFLECTING -> (loop|FINISH)\n * </pre>\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 子模块引用(原 AgentCore 字段) ==========\n\n private PlannerService plannerService;\n private Verificator guardrail;\n private ToolRegistry toolRegistry;\n private AgentSession memory;\n private EnvEvent envAdapter;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, this);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n public Vertx vertx() {\n return vertx;\n }\n\n // ========== 依赖注入(原 AgentCore 的 with* 方法) ==========\n\n /** 注入 {@link PlannerService} — 规划器引擎。 */\n public Agent withPlannerService(PlannerService plannerService) {\n this.plannerService = plannerService;\n return this;\n }\n\n public Agent withGuardrail(Verificator guardrail) {\n this.guardrail = guardrail;\n return this;\n }\n\n public Agent withToolRegistry(ToolRegistry toolRegistry) {\n this.toolRegistry = toolRegistry;\n return this;\n }\n\n public Agent withMemory(AgentSession memory) {\n this.memory = memory;\n return this;\n }\n\n public Agent withEnvAdapter(EnvEvent envAdapter) {\n this.envAdapter = envAdapter;\n return this;\n }\n\n // ========== 子模块 Getters(原 AgentCore getters) ==========\n\n public PlannerService plannerService() {\n return plannerService;\n }\n\n public Verificator guardrail() {\n return guardrail;\n }\n\n public ToolRegistry toolRegistry() {\n return toolRegistry;\n }\n\n public AgentSession memory() {\n return memory;\n }\n\n public EnvEvent envAdapter() {\n return envAdapter;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n // ========== 验证钩子(原 AgentCore 方法,供 AgentExecutor 调用) ==========\n\n /**\n * Pre-Verify: 在执行 Action 前拦截检查安全性和策略合规性。\n *\n * @param action 待验证的 Action\n * @return true 表示通过,false 表示被拦截\n */\n public boolean verifyPre(Action action) {\n if (!config.preVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Pre-verify action: {}\", action);\n return guardrail.intercept(\n Map.of(\n \"type\", action.type().name(),\n \"target\", action.target() != null ? action.target() : \"\",\n \"actionId\", action.actionId()));\n }\n\n /**\n * Post-Verify: 执行完成后审计观测结果。\n *\n * @param stepResult 当前步骤的结果\n * @return true 表示通过,false 表示需要 Revision\n */\n public boolean verifyPost(StepResult stepResult) {\n if (!config.postVerifyEnabled() || guardrail == null) {\n return true;\n }\n logger.debug(\"Post-verify result: {}\", stepResult);\n return guardrail.audit(stepResult);\n }\n\n /** 判断 PPAO 循环是否需要终止。 */\n public boolean shouldFinish(AgentContext context, StepResult result) {\n if (result instanceof StepResult.Finish) {\n return true;\n }\n if (result instanceof StepResult.Failure) {\n return true;\n }\n if (context.currentPhase() == AgentContext.Phase.FINISH) {\n return true;\n }\n if (context.isMaxIterationsReached()) {\n logger.warn(\"Agent {} reached max iterations ({})\", agentId, config.maxIterations());\n return true;\n }\n return false;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "cf772f464ef785e85743100f561e6292b64c024a": {"t": 1785755718.0845606, "n": 6530, "h": "ac7276097558da8b", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n *\n * <p>基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。 通过 ModelProvider 与底层模型交互,通过 AgentExecutor\n * 驱动响应式执行循环。\n *\n * <p>使用示例:\n *\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentCore agentCore;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n private Agent(String agentId, AgentConfig config) {\n this.agentId =\n agentId != null ? agentId : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.agentCore = new AgentCore(this.agentId, config);\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, agentCore);\n }\n\n // ========== 属性 ==========\n\n public String agentId() {\n return agentId;\n }\n\n public AgentCore agentCore() {\n return agentCore;\n }\n\n public AgentConfig config() {\n return config;\n }\n\n // ========== 同步 API ==========\n\n /** 使用默认上下文运行 Agent(同步阻塞)。 */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n *\n * <p>内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\") ? context.get(\"prompt\").toString() : \"Hello!\";\n\n String modelId =\n context.containsKey(\"model\") ? context.get(\"model\").toString() : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(\n err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n *\n * <p>对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n *\n * <p>使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer, 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor\n .execute(prompt, context)\n .onSuccess(\n ctx -> {\n String result = ctx.containsKey(\"result\") ? ctx.get(\"result\").toString() : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(\n err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n /** 获取底层的 Vertx 实例,便于调用方集成。 */\n public Vertx vertx() {\n return vertx;\n }\n\n /** 关闭 Agent 释放资源。 */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\n \"Agent {} direct LLM response status={}, tokens={}\",\n agentId,\n result.status(),\n result.metrics().tokenUsage() != null\n ? result.metrics().tokenUsage().totalTokens()\n : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "74837030dfba4e7d4c820989eed8c4dd79acc3aa": {"t": 1785755719.6479437, "n": 7131, "h": "96da363605b19633", "c": "package org.cland.alice.core.agent;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport io.vertx.core.Vertx;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n * <p>\n * 基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。\n * 通过 ModelProvider 与底层模型交互,通过 AgentExecutor 驱动响应式执行循环。\n * <p>\n * 使用示例:\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final Logger logger = LoggerFactory.getLogger(Agent.class);\n\n private final String agentId;\n private final AgentCore agentCore;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n private Agent(String agentId, AgentConfig config) {\n this.agentId = agentId != null\n ? agentId\n : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.agentCore = new AgentCore(this.agentId, config);\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, agentCore);\n }\n\n // ========== 属性 ==========\n\n public String agentId() { return agentId; }\n public AgentCore agentCore() { return agentCore; }\n public AgentConfig config() { return config; }\n\n // ========== 同步 API ==========\n\n /**\n * 使用默认上下文运行 Agent(同步阻塞)。\n */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n * <p>\n * 内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\")\n ? context.get(\"prompt\").toString()\n : \"Hello!\";\n\n String modelId = context.containsKey(\"model\")\n ? context.get(\"model\").toString()\n : config.defaultModelId();\n\n logger.info(\"Agent {} running with model {}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor.execute(prompt, context)\n .onSuccess(ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(err -> {\n logger.error(\"Agent {} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.info(\"Agent {} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n * <p>\n * 对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n * <p>\n * 使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer,\n * 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.info(\"Agent {} ask model={}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor.execute(prompt, context)\n .onSuccess(ctx -> {\n String result = ctx.containsKey(\"result\")\n ? ctx.get(\"result\").toString()\n : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.info(\"Agent {} response length={}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n /**\n * 获取底层的 Vertx 实例,便于调用方集成。\n */\n public Vertx vertx() {\n return vertx;\n }\n\n /**\n * 关闭 Agent 释放资源。\n */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.debug(\"Falling back to direct LLM call: model={}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.info(\"Agent {} direct LLM response status={}, tokens={}\", agentId, result.status(),\n result.metrics().tokenUsage() != null ? result.metrics().tokenUsage().totalTokens() : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "c7878a1738a7ea5258cb17b6c4efb771b5d877b3": {"t": 1785755721.0662656, "n": 7255, "h": "6cdbbbc6bb3ee909", "c": "package org.cland.alice.core.agent;\n\nimport io.vertx.core.Vertx;\nimport org.cland.alice.core.agent.executor.AgentExecutor;\nimport org.cland.alice.core.agent.result.StepResult;\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n * <p>\n * 基于 PPAO (Perceive-Plan-Act-Observe-Verify) 核心循环。\n * 通过 ModelProvider 与底层模型交互,通过 AgentExecutor 驱动响应式执行循环。\n * <p>\n * 使用示例:\n * <pre>\n * Agent agent = new Agent();\n * String result = agent.ask(\"What is the capital of France?\");\n * System.out.println(result); // \"Paris\"\n * </pre>\n */\npublic class Agent {\n\n private static final System.Logger logger = System.getLogger(Agent.class.getName());\n\n private final String agentId;\n private final AgentCore agentCore;\n private final AgentConfig config;\n private final Vertx vertx;\n private final AgentExecutor executor;\n\n // ========== 构造 ==========\n\n public Agent() {\n this(null, AgentConfig.defaults());\n }\n\n public Agent(String agentId) {\n this(agentId, AgentConfig.defaults());\n }\n\n public Agent(AgentConfig config) {\n this(null, config);\n }\n\n private Agent(String agentId, AgentConfig config) {\n this.agentId = agentId != null\n ? agentId\n : java.util.UUID.randomUUID().toString().substring(0, 8);\n this.config = config;\n this.agentCore = new AgentCore(this.agentId, config);\n this.vertx = Vertx.vertx();\n this.executor = new AgentExecutor(vertx, agentCore);\n }\n\n // ========== 属性 ==========\n\n public String agentId() { return agentId; }\n public AgentCore agentCore() { return agentCore; }\n public AgentConfig config() { return config; }\n\n // ========== 同步 API ==========\n\n /**\n * 使用默认上下文运行 Agent(同步阻塞)。\n */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent(同步阻塞)。\n * <p>\n * 内部执行 PPAO 循环,等待结果返回。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\")\n ? context.get(\"prompt\").toString()\n : \"Hello!\";\n\n String modelId = context.containsKey(\"model\")\n ? context.get(\"model\").toString()\n : config.defaultModelId();\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} running with model {1}\", agentId, modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<AgentContext> resultRef = new AtomicReference<>();\n\n executor.execute(prompt, context)\n .onSuccess(ctx -> {\n resultRef.set(ctx);\n latch.countDown();\n })\n .onFailure(err -> {\n logger.log(System.Logger.Level.ERROR, \"Agent {0} PPAO loop failed\", agentId, err);\n context.put(\"error\", err.getMessage());\n context.put(\"status\", \"FATAL_ERROR\");\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n context.put(\"error\", \"Interrupted: \" + e.getMessage());\n }\n\n AgentContext resultCtx = resultRef.get();\n if (resultCtx != null) {\n context.putAll(resultCtx.asMap());\n }\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} completed\");\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容(同步阻塞)。\n * <p>\n * 对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, config.defaultModelId());\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型(同步阻塞)。\n * <p>\n * 使用 PPAO 循环执行,如果循环产生 Finish 结果则返回 answer,\n * 否则回退到直接调用 ModelProvider。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.log(System.Logger.Level.INFO, \"Agent {0} ask model={1}\", agentId, modelId);\n\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n context.put(\"model\", modelId);\n\n // 同步执行 PPAO 循环\n CountDownLatch latch = new CountDownLatch(1);\n AtomicReference<String> resultRef = new AtomicReference<>();\n AtomicReference<Throwable> errorRef = new AtomicReference<>();\n\n executor.execute(prompt, context)\n .onSuccess(ctx -> {\n String result = ctx.containsKey(\"result\")\n ? ctx.get(\"result\").toString()\n : null;\n if (result == null) {\n // 回退:直接调用 LLM\n result = callLlmDirect(prompt, modelId);\n }\n resultRef.set(result);\n latch.countDown();\n })\n .onFailure(err -> {\n errorRef.set(err);\n latch.countDown();\n });\n\n try {\n latch.await(config.actionTimeoutMs() * 2, TimeUnit.MILLISECONDS);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw new RuntimeException(\"Agent ask interrupted\", e);\n }\n\n if (errorRef.get() != null) {\n throw new RuntimeException(\"Agent ask failed\", errorRef.get());\n }\n\n String result = resultRef.get();\n if (result == null) {\n throw new RuntimeException(\"Agent ask returned null result\");\n }\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} response length={1}\", agentId, result.length());\n return result;\n }\n\n // ========== 异步 API ==========\n\n /**\n * 异步执行 PPAO 循环。\n *\n * @param prompt 用户输入\n * @return 异步结果(io.vertx.core.Future)\n */\n public io.vertx.core.Future<AgentContext> askAsync(String prompt) {\n AgentContext context = new AgentContext(config.maxIterations());\n context.put(\"prompt\", prompt);\n return executor.execute(prompt, context);\n }\n\n /**\n * 获取底层的 Vertx 实例,便于调用方集成。\n */\n public Vertx vertx() {\n return vertx;\n }\n\n /**\n * 关闭 Agent 释放资源。\n */\n public void close() {\n vertx.close();\n }\n\n // ========== 辅助 ==========\n\n /** 直接调用 LLM(回退逻辑) */\n private String callLlmDirect(String prompt, String modelId) {\n logger.log(System.Logger.Level.DEBUG, \"Falling back to direct LLM call: model={0}\", modelId);\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} direct LLM response status={1}, tokens={2}\",\n agentId, result.status(),\n result.metrics().tokenUsage() != null ? result.metrics().tokenUsage().totalTokens() : \"N/A\");\n\n return result.result().content();\n }\n}\n"}, "5cd4ded9241b820badb62cc22bbcb97b77d2bb24": {"t": 1785755722.4880087, "n": 2668, "h": "3684491b72ec0e8a", "c": "package org.cland.alice.core.agent;\n\nimport org.cland.alice.model.Call;\nimport org.cland.alice.model.ModelProvider;\n\n/**\n * Agent 核心类,代表一个 AI Agent 实例。\n * 通过 ModelProvider 与底层模型交互。\n */\npublic class Agent {\n\n private static final System.Logger logger = System.getLogger(Agent.class.getName());\n\n private final String agentId;\n\n public Agent() {\n this.agentId = java.util.UUID.randomUUID().toString().substring(0, 8);\n }\n\n public Agent(String agentId) {\n this.agentId = agentId;\n }\n\n public String agentId() {\n return agentId;\n }\n\n /**\n * 使用默认上下文运行 Agent。\n */\n public void run() {\n run(new AgentContext());\n }\n\n /**\n * 使用指定上下文运行 Agent。\n */\n public void run(AgentContext context) {\n String prompt = context.containsKey(\"prompt\")\n ? context.get(\"prompt\").toString()\n : \"Hello!\";\n\n String modelId = context.containsKey(\"model\")\n ? context.get(\"model\").toString()\n : \"gpt-4o-mini\";\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} running with model {1}\", agentId, modelId);\n\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} completed: status={1}, latency={2}ms\",\n agentId, result.status(), result.metrics().latencyMs());\n\n context.put(\"result\", result.result() != null ? result.result().content() : null);\n context.put(\"status\", result.status());\n context.put(\"traceId\", result.traceId());\n }\n\n /**\n * 向 Agent 发送 prompt,返回响应内容。\n * <p>\n * 对应设计文档中 Agent 的 ask() 方法。\n *\n * @param prompt 用户输入的提示词\n * @return 模型返回的响应文本\n */\n public String ask(String prompt) {\n return ask(prompt, \"gpt-4o-mini\");\n }\n\n /**\n * 向 Agent 发送 prompt,指定模型。\n *\n * @param prompt 用户输入的提示词\n * @param modelId 目标模型 ID\n * @return 模型返回的响应文本\n */\n public String ask(String prompt, String modelId) {\n logger.log(System.Logger.Level.INFO, \"Agent {0} ask model={1}\", agentId, modelId);\n\n ModelProvider provider = ModelProvider.getInstance();\n Call result = provider.dispatch(modelId, prompt);\n\n logger.log(System.Logger.Level.INFO, \"Agent {0} response status={1}, tokens={2}\",\n agentId, result.status(),\n result.metrics().tokenUsage() != null ? result.metrics().tokenUsage().totalTokens() : \"N/A\");\n\n if (result.result() == null) {\n throw new RuntimeException(\"Agent call failed: \" + result.status());\n }\n return result.result().content();\n }\n}\n"}}, "docs/alice-model/CONFIG.md": {"8ebb2c40d3906a86e5a1eaa8ea381b21d0df9bd9": {"t": 1785746607.6394594, "n": 5696, "h": "578da7753bf2f31a", "c": "---\ntitle: \"alice-model 配置文件说明\"\nsummary: \"~/.alice/model.json 配置文件完整说明 — providers.<name>.available_models[] 结构,Jackson 解析\"\nread_when:\n - \"配置模型提供商或修改默认模型\"\n - \"理解 enable_thinking / reasoning_effort 参数\"\n - \"排查模型加载问题\"\n---\n\n## model.json 配置说明文档\n\n### 1. 文件位置\n\n```\n~/.alice/model.json\n```\n\n| 操作系统 | 完整路径 |\n|----------|----------|\n| Linux / macOS | `/home/[username]/.alice/model.json` |\n| Windows | `C:\\Users\\[username]\\.alice\\model.json` |\n\n### 2. 创建配置文件\n\n```bash\nmkdir -p ~/.alice\ntouch ~/.alice/model.json\n```\n\n### 3. 配置结构\n\n```\n~/.alice/model.json\n├── default_model { provider, model, enable_thinking, reasoning_effort }\n├── planner { instruction_model_id, reasoning_model_id,\n│ instruction{ enable_thinking, reasoning_effort },\n│ reasoning{ enable_thinking, reasoning_effort } }\n└── providers { <name>:\n ├── base_url\n ├── api_key (支持 ${ENV_VAR})\n └── available_models[]\n └── { name, model, max_tokens, max_output_tokens, capabilities }\n```\n\n---\n\n### 4. 完整配置模板\n\n```json\n{\n \"default_model\": {\n \"provider\": \"deepseek\",\n \"model\": \"deepseek-v4-flash\",\n \"enable_thinking\": true,\n \"reasoning_effort\": \"high\"\n },\n \"planner\": {\n \"instruction_model_id\": \"deepseek-v4-flash\",\n \"reasoning_model_id\": \"deepseek-v4-flash\",\n \"instruction\": {\n \"enable_thinking\": false,\n \"reasoning_effort\": \"low\"\n },\n \"reasoning\": {\n \"enable_thinking\": true,\n \"reasoning_effort\": \"high\"\n }\n },\n \"providers\": {\n \"deepseek\": {\n \"base_url\": \"https://api.deepseek.com/v1\",\n \"api_key\": \"${DEEPSEEK_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"deepseek-v4-flash\",\n \"model\": \"deepseek-v4-flash\",\n \"max_tokens\": 131072,\n \"max_output_tokens\": 32000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": false,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": true,\n \"chat_completions\": true\n }\n }\n ]\n },\n \"openai\": {\n \"base_url\": \"https://api.openai.com/v1\",\n \"api_key\": \"${OPENAI_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"o3-mini\",\n \"model\": \"o3-mini\",\n \"max_tokens\": 128000,\n \"max_output_tokens\": 16000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": false,\n \"parallel_tool_calls\": false,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n }\n ]\n }\n }\n}\n```\n\n### 5. 字段说明\n\n#### 5.1 default_model\n\n| 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `provider` | string | ✅ | 提供商名称,对应 `providers` 中的键 |\n| `model` | string | ✅ | 模型标识符,对应 `available_models[].name` |\n| `enable_thinking` | boolean | ❌ | 全局思考开关。默认 `true` |\n| `reasoning_effort` | string | ❌ | `low` / `medium` / `high` / `xhigh`。默认 `high`(thinking=true)或 `low`(thinking=false) |\n\n#### 5.2 planner\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `instruction_model_id` | string | 快路径模型 ID,未设置回退到 `default_model.model` |\n| `reasoning_model_id` | string | 慢路径模型 ID,未设置回退到 `default_model.model` |\n| `instruction` | object | 快路径 thinking 参数 `{ enable_thinking, reasoning_effort }` |\n| `reasoning` | object | 慢路径 thinking 参数 `{ enable_thinking, reasoning_effort }` |\n\n#### 5.3 providers.<name>\n\n| 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `base_url` | string | ✅ | API 基础 URL,须以 `http://` 或 `https://` 开头 |\n| `api_key` | string | ❌ | API 密钥,支持 `${ENV_VAR}` 环境变量引用 |\n| `available_models` | array | ✅ | 可用模型列表 |\n\n#### 5.4 available_models[]\n\n| 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `name` | string | ✅ | 模型名称(调用标识符) |\n| `model` | string | ❌ | 发送给 API 的模型名,默认同 `name` |\n| `max_tokens` | integer | ✅ | 最大上下文总长度 |\n| `max_output_tokens` | integer | ❌ | 单次最大输出 token 数,默认同 `max_tokens` |\n| `capabilities` | object | ✅ | 模型能力标识集 |\n\n#### 5.5 capabilities\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `tools` | boolean | 是否支持工具/函数调用 |\n| `images` | boolean | 是否支持图像输入(多模态) |\n| `parallel_tool_calls` | boolean | 是否支持并行工具调用 |\n| `prompt_cache_key` | boolean | 是否支持提示缓存键 |\n| `chat_completions` | boolean | 是否支持 `/chat/completions` 端点 |\n\n---\n\n### 6. 映射规则\n\n`enable_thinking` / `reasoning_effort` 在 `OpenAiSupplier` 中按供应商映射:\n\n| 供应商 | enable_thinking | API 行为 |\n|--------|----------------|----------|\n| DeepSeek | `false` | `thinking:{\"type\":\"disabled\"}` |\n| DeepSeek | `true` + `effort` | `thinking:{\"type\":\"enabled\", \"effort\":\"...\"}` |\n| OpenAI o 系列 | `false` | 强制 `reasoning_effort=\"low\"` |\n| OpenAI o 系列 | `true` + `effort` | 透传 `reasoning_effort` |\n\n### 7. 双路径模型\n\n`DefaultPlannerModelSupplier` 根据路径自动注入 thinking 参数:\n\n| 路径 | enable_thinking | reasoning_effort | 用途 |\n|------|----------------|------------------|------|\n| **FastPath** (指令模型) | `false` | `low` | 快速响应,关闭 LLM 思考 |\n| **SlowPath** (推理模型) | `true` | `high` | 深度推理、MCTS 规划 |\n\n### 8. 环境变量配置\n\n```bash\nexport DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxx\n```\n\n### 9. 校验规则\n\n| 校验项 | 规则 | 错误示例 |\n|--------|------|----------|\n| `max_tokens` | ≥ `max_output_tokens` | `max_tokens: 1000, max_output_tokens: 2000` ❌ |\n| `base_url` | 必须以 `http://` 或 `https://` 开头 | `api.example.com/v1` ❌ |\n| `name` | 非空字符串 | `\"\"` ❌ |\n| `available_models` | 至少 1 个模型 | `[]` ❌ |\n\n### 10. 权限建议\n\n```bash\nchmod 700 ~/.alice\nchmod 600 ~/.alice/model.json\n```\n\n### 11. 常见问题\n\n| 问题 | 解决方案 |\n|------|----------|\n| 配置文件找不到 | 确认 `~/.alice/model.json` 是否存在 |\n| API Key 未生效 | 检查环境变量名称是否与 `${VAR_NAME}` 一致 |\n| 权限错误 | 执行 `chmod 600 ~/.alice/model.json` |\n| JSON 格式错误 | 使用 `jq . ~/.alice/model.json` 验证 |\n"}, "542dfef86a8f56af43d54230584fe2edf9c80f6a": {"t": 1785755688.5572386, "n": 5696, "h": "578da7753bf2f31a", "c": "---\ntitle: \"alice-model 配置文件说明\"\nsummary: \"~/.alice/model.json 配置文件完整说明 — providers.<name>.available_models[] 结构,Jackson 解析\"\nread_when:\n - \"配置模型提供商或修改默认模型\"\n - \"理解 enable_thinking / reasoning_effort 参数\"\n - \"排查模型加载问题\"\n---\n\n## model.json 配置说明文档\n\n### 1. 文件位置\n\n```\n~/.alice/model.json\n```\n\n| 操作系统 | 完整路径 |\n|----------|----------|\n| Linux / macOS | `/home/[username]/.alice/model.json` |\n| Windows | `C:\\Users\\[username]\\.alice\\model.json` |\n\n### 2. 创建配置文件\n\n```bash\nmkdir -p ~/.alice\ntouch ~/.alice/model.json\n```\n\n### 3. 配置结构\n\n```\n~/.alice/model.json\n├── default_model { provider, model, enable_thinking, reasoning_effort }\n├── planner { instruction_model_id, reasoning_model_id,\n│ instruction{ enable_thinking, reasoning_effort },\n│ reasoning{ enable_thinking, reasoning_effort } }\n└── providers { <name>:\n ├── base_url\n ├── api_key (支持 ${ENV_VAR})\n └── available_models[]\n └── { name, model, max_tokens, max_output_tokens, capabilities }\n```\n\n---\n\n### 4. 完整配置模板\n\n```json\n{\n \"default_model\": {\n \"provider\": \"deepseek\",\n \"model\": \"deepseek-v4-flash\",\n \"enable_thinking\": true,\n \"reasoning_effort\": \"high\"\n },\n \"planner\": {\n \"instruction_model_id\": \"deepseek-v4-flash\",\n \"reasoning_model_id\": \"deepseek-v4-flash\",\n \"instruction\": {\n \"enable_thinking\": false,\n \"reasoning_effort\": \"low\"\n },\n \"reasoning\": {\n \"enable_thinking\": true,\n \"reasoning_effort\": \"high\"\n }\n },\n \"providers\": {\n \"deepseek\": {\n \"base_url\": \"https://api.deepseek.com/v1\",\n \"api_key\": \"${DEEPSEEK_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"deepseek-v4-flash\",\n \"model\": \"deepseek-v4-flash\",\n \"max_tokens\": 131072,\n \"max_output_tokens\": 32000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": false,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": true,\n \"chat_completions\": true\n }\n }\n ]\n },\n \"openai\": {\n \"base_url\": \"https://api.openai.com/v1\",\n \"api_key\": \"${OPENAI_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"o3-mini\",\n \"model\": \"o3-mini\",\n \"max_tokens\": 128000,\n \"max_output_tokens\": 16000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": false,\n \"parallel_tool_calls\": false,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n }\n ]\n }\n }\n}\n```\n\n### 5. 字段说明\n\n#### 5.1 default_model\n\n| 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `provider` | string | ✅ | 提供商名称,对应 `providers` 中的键 |\n| `model` | string | ✅ | 模型标识符,对应 `available_models[].name` |\n| `enable_thinking` | boolean | ❌ | 全局思考开关。默认 `true` |\n| `reasoning_effort` | string | ❌ | `low` / `medium` / `high` / `xhigh`。默认 `high`(thinking=true)或 `low`(thinking=false) |\n\n#### 5.2 planner\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `instruction_model_id` | string | 快路径模型 ID,未设置回退到 `default_model.model` |\n| `reasoning_model_id` | string | 慢路径模型 ID,未设置回退到 `default_model.model` |\n| `instruction` | object | 快路径 thinking 参数 `{ enable_thinking, reasoning_effort }` |\n| `reasoning` | object | 慢路径 thinking 参数 `{ enable_thinking, reasoning_effort }` |\n\n#### 5.3 providers.<name>\n\n| 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `base_url` | string | ✅ | API 基础 URL,须以 `http://` 或 `https://` 开头 |\n| `api_key` | string | ❌ | API 密钥,支持 `${ENV_VAR}` 环境变量引用 |\n| `available_models` | array | ✅ | 可用模型列表 |\n\n#### 5.4 available_models[]\n\n| 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|\n| `name` | string | ✅ | 模型名称(调用标识符) |\n| `model` | string | ❌ | 发送给 API 的模型名,默认同 `name` |\n| `max_tokens` | integer | ✅ | 最大上下文总长度 |\n| `max_output_tokens` | integer | ❌ | 单次最大输出 token 数,默认同 `max_tokens` |\n| `capabilities` | object | ✅ | 模型能力标识集 |\n\n#### 5.5 capabilities\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `tools` | boolean | 是否支持工具/函数调用 |\n| `images` | boolean | 是否支持图像输入(多模态) |\n| `parallel_tool_calls` | boolean | 是否支持并行工具调用 |\n| `prompt_cache_key` | boolean | 是否支持提示缓存键 |\n| `chat_completions` | boolean | 是否支持 `/chat/completions` 端点 |\n\n---\n\n### 6. 映射规则\n\n`enable_thinking` / `reasoning_effort` 在 `OpenAiSupplier` 中按供应商映射:\n\n| 供应商 | enable_thinking | API 行为 |\n|--------|----------------|----------|\n| DeepSeek | `false` | `thinking:{\"type\":\"disabled\"}` |\n| DeepSeek | `true` + `effort` | `thinking:{\"type\":\"enabled\", \"effort\":\"...\"}` |\n| OpenAI o 系列 | `false` | 强制 `reasoning_effort=\"low\"` |\n| OpenAI o 系列 | `true` + `effort` | 透传 `reasoning_effort` |\n\n### 7. 双路径模型\n\n`DefaultPlannerModelSupplier` 根据路径自动注入 thinking 参数:\n\n| 路径 | enable_thinking | reasoning_effort | 用途 |\n|------|----------------|------------------|------|\n| **FastPath** (指令模型) | `false` | `low` | 快速响应,关闭 LLM 思考 |\n| **SlowPath** (推理模型) | `true` | `high` | 深度推理、MCTS 规划 |\n\n### 8. 环境变量配置\n\n```bash\nexport DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxx\n```\n\n### 9. 校验规则\n\n| 校验项 | 规则 | 错误示例 |\n|--------|------|----------|\n| `max_tokens` | ≥ `max_output_tokens` | `max_tokens: 1000, max_output_tokens: 2000` ❌ |\n| `base_url` | 必须以 `http://` 或 `https://` 开头 | `api.example.com/v1` ❌ |\n| `name` | 非空字符串 | `\"\"` ❌ |\n| `available_models` | 至少 1 个模型 | `[]` ❌ |\n\n### 10. 权限建议\n\n```bash\nchmod 700 ~/.alice\nchmod 600 ~/.alice/model.json\n```\n\n### 11. 常见问题\n\n| 问题 | 解决方案 |\n|------|----------|\n| 配置文件找不到 | 确认 `~/.alice/model.json` 是否存在 |\n| API Key 未生效 | 检查环境变量名称是否与 `${VAR_NAME}` 一致 |\n| 权限错误 | 执行 `chmod 600 ~/.alice/model.json` |\n| JSON 格式错误 | 使用 `jq . ~/.alice/model.json` 验证 |\n"}, "c2c7eac01472b99309c1f867fa8a080907dba673": {"t": 1785755689.9465733, "n": 7541, "h": "7bf59a926be0f3f6", "c": "明白了,配置文件路径为 `~/.alice/model.json`。以下是针对该路径的完整配置说明文档。\n\n---\n\n## model.json 配置说明文档\n\n### 1. 文件位置\n\n```\n~/.alice/model.json\n```\n\n即用户主目录下的 `.alice` 目录中:\n\n| 操作系统 | 完整路径 |\n|----------|----------|\n| Linux / macOS | `/home/[username]/.alice/model.json` |\n| macOS | `/Users/[username]/.alice/model.json` |\n| Windows | `C:\\Users\\[username]\\.alice\\model.json` |\n\n---\n\n### 2. 创建配置文件\n\n```bash\n# 创建配置目录\nmkdir -p ~/.alice\n\n# 创建配置文件\ntouch ~/.alice/model.json\n```\n\n---\n\n### 3. 完整配置模板\n\n将以下内容写入 `~/.alice/model.json`:\n\n```json\n{\n \"language_models\": {\n \"openai_compatible\": {\n \"my-provider\": {\n \"api_url\": \"https://api.myprovider.com/v1\",\n \"api_key\": \"${MY_PROVIDER_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"deepseek\",\n \"max_tokens\": 200000,\n \"max_output_tokens\": 32000,\n \"max_completion_tokens\": 200000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": true,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": true,\n \"chat_completions\": true\n }\n }\n ]\n }\n }\n }\n}\n```\n\n---\n\n### 4. 字段说明表\n\n| 层级 | 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|------|\n| 根 | `language_models` | object | ✅ | 语言模型配置根节点 |\n| 根 | `openai_compatible` | object | ✅ | OpenAI 兼容接口标识 |\n| 2 | `[provider_name]` | object | ✅ | 提供商自定义名称(如 `my-provider`) |\n| 3 | `api_url` | string | ✅ | API 基础 URL,如 `https://api.xxx.com/v1` |\n| 3 | `api_key` | string | ❌ | API 密钥,支持 `${ENV_VAR}` 环境变量引用 |\n| 3 | `available_models` | array | ✅ | 支持的模型列表 |\n| 4 | `name` | string | ✅ | 模型名称,调用时使用的标识符 |\n| 4 | `max_tokens` | integer | ✅ | 最大上下文总长度(输入 + 输出 token 数) |\n| 4 | `max_output_tokens` | integer | ✅ | 单次最大输出 token 数 |\n| 4 | `max_completion_tokens` | integer | ✅ | 补全接口最大 token 数 |\n| 5 | `capabilities.tools` | boolean | ✅ | 是否支持工具/函数调用 |\n| 5 | `capabilities.images` | boolean | ✅ | 是否支持图像输入(多模态) |\n| 5 | `capabilities.parallel_tool_calls` | boolean | ✅ | 是否支持并行工具调用 |\n| 5 | `capabilities.prompt_cache_key` | boolean | ✅ | 是否支持提示缓存键 |\n| 5 | `capabilities.chat_completions` | boolean | ✅ | 是否支持 `/chat/completions` 端点 |\n\n---\n\n### 5. 多提供商配置示例\n\n```json\n{\n \"language_models\": {\n \"openai_compatible\": {\n \"openai\": {\n \"api_url\": \"https://api.openai.com/v1\",\n \"api_key\": \"${OPENAI_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"gpt-4-turbo\",\n \"max_tokens\": 128000,\n \"max_output_tokens\": 4096,\n \"max_completion_tokens\": 128000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": true,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n },\n {\n \"name\": \"gpt-4o\",\n \"max_tokens\": 128000,\n \"max_output_tokens\": 16384,\n \"max_completion_tokens\": 128000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": true,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n }\n ]\n },\n \"deepseek\": {\n \"api_url\": \"https://api.deepseek.com/v1\",\n \"api_key\": \"${DEEPSEEK_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"deepseek-v4-flash\",\n \"max_tokens\": 200000,\n \"max_output_tokens\": 32000,\n \"max_completion_tokens\": 200000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": false,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": true,\n \"chat_completions\": true\n }\n }\n ]\n },\n \"local\": {\n \"api_url\": \"http://localhost:8080/v1\",\n \"available_models\": [\n {\n \"name\": \"llama3\",\n \"max_tokens\": 8192,\n \"max_output_tokens\": 2048,\n \"max_completion_tokens\": 8192,\n \"capabilities\": {\n \"tools\": false,\n \"images\": false,\n \"parallel_tool_calls\": false,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n }\n ]\n }\n }\n }\n}\n```\n\n---\n\n### 6. 环境变量配置\n\n在 `~/.bashrc`、`~/.zshrc` 或 `.env` 文件中设置:\n\n```bash\n# ~/.bashrc 或 ~/.zshrc\nexport MY_PROVIDER_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n或创建 `~/.alice/.env`:\n\n```bash\n# ~/.alice/.env\nMY_PROVIDER_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nOPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nDEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n---\n\n### 7. 代码加载示例\n\n```python\nimport json\nimport os\nfrom pathlib import Path\n\ndef load_model_config():\n config_path = Path.home() / \".alice\" / \"model.json\"\n \n if not config_path.exists():\n raise FileNotFoundError(f\"Config file not found: {config_path}\")\n \n with open(config_path, 'r') as f:\n config = json.load(f)\n \n # 展开环境变量\n def expand_env(value):\n if isinstance(value, str) and value.startswith(\"${\") and value.endswith(\"}\"):\n env_var = value[2:-1]\n return os.environ.get(env_var, value)\n return value\n \n # 递归展开所有 api_key\n providers = config.get(\"language_models\", {}).get(\"openai_compatible\", {})\n for provider in providers.values():\n if \"api_key\" in provider:\n provider[\"api_key\"] = expand_env(provider[\"api_key\"])\n \n return config\n\n# 使用\nconfig = load_model_config()\nprint(config[\"language_models\"][\"openai_compatible\"][\"my-provider\"][\"api_url\"])\n```\n\n```javascript\n// Node.js\nconst fs = require('fs');\nconst path = require('path');\nconst os = require('os');\n\nfunction loadModelConfig() {\n const configPath = path.join(os.homedir(), '.alice', 'model.json');\n \n if (!fs.existsSync(configPath)) {\n throw new Error(`Config file not found: ${configPath}`);\n }\n \n const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n \n // 展开环境变量\n const expandEnv = (value) => {\n if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {\n const envVar = value.slice(2, -1);\n return process.env[envVar] || value;\n }\n return value;\n };\n \n const providers = config.language_models?.openai_compatible || {};\n for (const key of Object.keys(providers)) {\n if (providers[key].api_key) {\n providers[key].api_key = expandEnv(providers[key].api_key);\n }\n }\n \n return config;\n}\n\n// 使用\nconst config = loadModelConfig();\nconsole.log(config.language_models.openai_compatible['my-provider'].api_url);\n```\n\n---\n\n### 8. 配置校验规则\n\n| 校验项 | 规则 | 错误示例 |\n|--------|------|----------|\n| `max_tokens` | ≥ `max_output_tokens` | `max_tokens: 1000, max_output_tokens: 2000` ❌ |\n| `max_completion_tokens` | 建议等于 `max_tokens` | 相差较大时可能产生歧义 |\n| `api_url` | 必须以 `http://` 或 `https://` 开头 | `api.example.com/v1` ❌ |\n| `name` | 非空字符串 | `\"\"` ❌ |\n| `capabilities` | 所有布尔字段必须显式设置 | 缺失字段可能导致运行时错误 |\n\n---\n\n### 9. 配置文件权限建议\n\n```bash\n# 设置目录权限(仅用户可读写)\nchmod 700 ~/.alice\n\n# 设置文件权限(仅用户可读写)\nchmod 600 ~/.alice/model.json\n```\n\n---\n\n### 10. 常见问题\n\n| 问题 | 解决方案 |\n|------|----------|\n| 配置文件找不到 | 确认 `~/.alice/model.json` 是否存在,目录名是否正确(注意点号) |\n| API Key 未生效 | 检查环境变量名称是否与配置中 `${VAR_NAME}` 一致,确认已 `source ~/.bashrc` |\n| 权限错误 | 执行 `chmod 600 ~/.alice/model.json` 修复权限 |\n| JSON 格式错误 | 使用 `jq . ~/.alice/model.json` 验证 JSON 格式 |\n\n---\n\n如需进一步调整(如添加自定义 headers、代理配置、超时设置等),请告知具体需求,我可以帮您扩展配置结构。\n"}, "f067dcf4ddbb3b1be45693b752657ddd5f93eaf9": {"t": 1785755691.3153095, "n": 7537, "h": "94b701ba77c07ad7", "c": "明白了,配置文件路径为 `~/.alice/model.json`。以下是针对该路径的完整配置说明文档。\n\n---\n\n## model.json 配置说明文档\n\n### 1. 文件位置\n\n```\n~/.alice/model.json\n```\n\n即用户主目录下的 `.alice` 目录中:\n\n| 操作系统 | 完整路径 |\n|----------|----------|\n| Linux / macOS | `/home/[username]/.alice/model.json` |\n| macOS | `/Users/[username]/.alice/model.json` |\n| Windows | `C:\\Users\\[username]\\.alice\\model.json` |\n\n---\n\n### 2. 创建配置文件\n\n```bash\n# 创建配置目录\nmkdir -p ~/.alice\n\n# 创建配置文件\ntouch ~/.alice/model.json\n```\n\n---\n\n### 3. 完整配置模板\n\n将以下内容写入 `~/.alice/model.json`:\n\n```json\n{\n \"language_models\": {\n \"openai_compatible\": {\n \"my-provider\": {\n \"api_url\": \"https://api.myprovider.com/v1\",\n \"api_key\": \"${MY_PROVIDER_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"deepseek\",\n \"max_tokens\": 200000,\n \"max_output_tokens\": 32000,\n \"max_completion_tokens\": 200000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": true,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": true,\n \"chat_completions\": true\n }\n }\n ]\n }\n }\n }\n}\n```\n\n---\n\n### 4. 字段说明表\n\n| 层级 | 字段 | 类型 | 必填 | 说明 |\n|------|------|------|------|------|\n| 根 | `language_models` | object | ✅ | 语言模型配置根节点 |\n| 根 | `openai_compatible` | object | ✅ | OpenAI 兼容接口标识 |\n| 2 | `[provider_name]` | object | ✅ | 提供商自定义名称(如 `my-provider`) |\n| 3 | `api_url` | string | ✅ | API 基础 URL,如 `https://api.xxx.com/v1` |\n| 3 | `api_key` | string | ❌ | API 密钥,支持 `${ENV_VAR}` 环境变量引用 |\n| 3 | `available_models` | array | ✅ | 支持的模型列表 |\n| 4 | `name` | string | ✅ | 模型名称,调用时使用的标识符 |\n| 4 | `max_tokens` | integer | ✅ | 最大上下文总长度(输入 + 输出 token 数) |\n| 4 | `max_output_tokens` | integer | ✅ | 单次最大输出 token 数 |\n| 4 | `max_completion_tokens` | integer | ✅ | 补全接口最大 token 数 |\n| 5 | `capabilities.tools` | boolean | ✅ | 是否支持工具/函数调用 |\n| 5 | `capabilities.images` | boolean | ✅ | 是否支持图像输入(多模态) |\n| 5 | `capabilities.parallel_tool_calls` | boolean | ✅ | 是否支持并行工具调用 |\n| 5 | `capabilities.prompt_cache_key` | boolean | ✅ | 是否支持提示缓存键 |\n| 5 | `capabilities.chat_completions` | boolean | ✅ | 是否支持 `/chat/completions` 端点 |\n\n---\n\n### 5. 多提供商配置示例\n\n```json\n{\n \"language_models\": {\n \"openai_compatible\": {\n \"openai\": {\n \"api_url\": \"https://api.openai.com/v1\",\n \"api_key\": \"${OPENAI_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"gpt-4-turbo\",\n \"max_tokens\": 128000,\n \"max_output_tokens\": 4096,\n \"max_completion_tokens\": 128000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": true,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n },\n {\n \"name\": \"gpt-4o\",\n \"max_tokens\": 128000,\n \"max_output_tokens\": 16384,\n \"max_completion_tokens\": 128000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": true,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n }\n ]\n },\n \"deepseek\": {\n \"api_url\": \"https://api.deepseek.com/v1\",\n \"api_key\": \"${DEEPSEEK_API_KEY}\",\n \"available_models\": [\n {\n \"name\": \"deepseek-chat\",\n \"max_tokens\": 200000,\n \"max_output_tokens\": 32000,\n \"max_completion_tokens\": 200000,\n \"capabilities\": {\n \"tools\": true,\n \"images\": false,\n \"parallel_tool_calls\": true,\n \"prompt_cache_key\": true,\n \"chat_completions\": true\n }\n }\n ]\n },\n \"local\": {\n \"api_url\": \"http://localhost:8080/v1\",\n \"available_models\": [\n {\n \"name\": \"llama3\",\n \"max_tokens\": 8192,\n \"max_output_tokens\": 2048,\n \"max_completion_tokens\": 8192,\n \"capabilities\": {\n \"tools\": false,\n \"images\": false,\n \"parallel_tool_calls\": false,\n \"prompt_cache_key\": false,\n \"chat_completions\": true\n }\n }\n ]\n }\n }\n }\n}\n```\n\n---\n\n### 6. 环境变量配置\n\n在 `~/.bashrc`、`~/.zshrc` 或 `.env` 文件中设置:\n\n```bash\n# ~/.bashrc 或 ~/.zshrc\nexport MY_PROVIDER_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nexport DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n或创建 `~/.alice/.env`:\n\n```bash\n# ~/.alice/.env\nMY_PROVIDER_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nOPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nDEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n---\n\n### 7. 代码加载示例\n\n```python\nimport json\nimport os\nfrom pathlib import Path\n\ndef load_model_config():\n config_path = Path.home() / \".alice\" / \"model.json\"\n \n if not config_path.exists():\n raise FileNotFoundError(f\"Config file not found: {config_path}\")\n \n with open(config_path, 'r') as f:\n config = json.load(f)\n \n # 展开环境变量\n def expand_env(value):\n if isinstance(value, str) and value.startswith(\"${\") and value.endswith(\"}\"):\n env_var = value[2:-1]\n return os.environ.get(env_var, value)\n return value\n \n # 递归展开所有 api_key\n providers = config.get(\"language_models\", {}).get(\"openai_compatible\", {})\n for provider in providers.values():\n if \"api_key\" in provider:\n provider[\"api_key\"] = expand_env(provider[\"api_key\"])\n \n return config\n\n# 使用\nconfig = load_model_config()\nprint(config[\"language_models\"][\"openai_compatible\"][\"my-provider\"][\"api_url\"])\n```\n\n```javascript\n// Node.js\nconst fs = require('fs');\nconst path = require('path');\nconst os = require('os');\n\nfunction loadModelConfig() {\n const configPath = path.join(os.homedir(), '.alice', 'model.json');\n \n if (!fs.existsSync(configPath)) {\n throw new Error(`Config file not found: ${configPath}`);\n }\n \n const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n \n // 展开环境变量\n const expandEnv = (value) => {\n if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {\n const envVar = value.slice(2, -1);\n return process.env[envVar] || value;\n }\n return value;\n };\n \n const providers = config.language_models?.openai_compatible || {};\n for (const key of Object.keys(providers)) {\n if (providers[key].api_key) {\n providers[key].api_key = expandEnv(providers[key].api_key);\n }\n }\n \n return config;\n}\n\n// 使用\nconst config = loadModelConfig();\nconsole.log(config.language_models.openai_compatible['my-provider'].api_url);\n```\n\n---\n\n### 8. 配置校验规则\n\n| 校验项 | 规则 | 错误示例 |\n|--------|------|----------|\n| `max_tokens` | ≥ `max_output_tokens` | `max_tokens: 1000, max_output_tokens: 2000` ❌ |\n| `max_completion_tokens` | 建议等于 `max_tokens` | 相差较大时可能产生歧义 |\n| `api_url` | 必须以 `http://` 或 `https://` 开头 | `api.example.com/v1` ❌ |\n| `name` | 非空字符串 | `\"\"` ❌ |\n| `capabilities` | 所有布尔字段必须显式设置 | 缺失字段可能导致运行时错误 |\n\n---\n\n### 9. 配置文件权限建议\n\n```bash\n# 设置目录权限(仅用户可读写)\nchmod 700 ~/.alice\n\n# 设置文件权限(仅用户可读写)\nchmod 600 ~/.alice/model.json\n```\n\n---\n\n### 10. 常见问题\n\n| 问题 | 解决方案 |\n|------|----------|\n| 配置文件找不到 | 确认 `~/.alice/model.json` 是否存在,目录名是否正确(注意点号) |\n| API Key 未生效 | 检查环境变量名称是否与配置中 `${VAR_NAME}` 一致,确认已 `source ~/.bashrc` |\n| 权限错误 | 执行 `chmod 600 ~/.alice/model.json` 修复权限 |\n| JSON 格式错误 | 使用 `jq . ~/.alice/model.json` 验证 JSON 格式 |\n\n---\n\n如需进一步调整(如添加自定义 headers、代理配置、超时设置等),请告知具体需求,我可以帮您扩展配置结构。\n"}}}} |