Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chaineasy-ai_alice-agent.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
745 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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 &lt;key&gt;} — 获取单个配置项\n * <li>{@code alice config set &lt;key&gt; &lt;value&gt;} — 设置配置项(当前仅打印模拟)\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"}}, "docs/config/example.yaml": {"8ebb2c40d3906a86e5a1eaa8ea381b21d0df9bd9": {"t": 1786068212.9364786, "n": 4236, "h": "f9e2ca001a0ee1a7", "c": "# Alice Agent 配置键参考\n#\n# Alice Agent 使用两个配置文件:\n# ~/.alice/config.json — 系统配置(由 AliceConfigStore 管理)\n# ~/.alice/model.json — 模型提供商配置(由 ModelConfigLoader 管理)\n#\n# 本 YAML 仅为 ~/.alice/config.json 的配置键参考文档。\n# 模型/提供商相关配置(openai、anthropic、deepseek 等)请参见 model.json:\n# https://github.com/cland/alice-agent/docs/config/README.md#2-model-config-alicemodeljson\n#\n# 配置读取优先级:环境变量 > ~/.alice/config.json > 内建默认值\n#\n# CLI 使用点分隔键名(如 default.timeout),\n# JSON 存储使用下划线键名(如 default_timeout),\n# 由 AliceConfigStore 自动转换。\n\n# ==================== System Config ====================\n\ndefault.timeout:\n json_key: default_timeout\n env: DEFAULT_TIMEOUT\n default: 180\n description: \"默认任务超时时长(秒)\"\n\ndefault.verbose:\n json_key: default_verbose\n env: DEFAULT_VERBOSE\n default: false\n description: \"默认是否启用详细输出模式\"\n\nmax_iterations:\n json_key: max_iterations\n env: MAX_ITERATIONS\n default: 10\n description: \"最大 PPAO 迭代次数\"\n\naction_timeout_ms:\n json_key: action_timeout_ms\n env: ACTION_TIMEOUT_MS\n default: 30000\n description: \"Action 执行超时(毫秒)\"\n\nmax_micro_depth:\n json_key: max_micro_depth\n env: MAX_MICRO_DEPTH\n default: 30\n description: \"Micro-ReAct 最大递归深度(熔断阈值),高于 Macro 迭代以支持多步骤工具链\"\n\n# ==================== Model Override Config ====================\n# 以下键为 config.json 中可选的模型覆盖配置。\n# 主要模型配置位于 ~/.alice/model.json 的 language_models.openai_compatible 结构下,\n# config.json 中的值优先级低于 model.json 的 default_model 字段。\n\ndefault.model:\n json_key: default_model\n env: DEFAULT_MODEL\n default: \"gpt-4o-mini\"\n description: \"默认 LLM 模型 ID(可选覆盖,主配置见 model.json 中的 default_model)\"\n\nagent.max_iterations:\n json_key: max_iterations\n env: MAX_ITERATIONS\n default: 10\n description: \"最大迭代次数(同 max_iterations,可选覆盖)\"\n\nagent.max_micro_depth:\n json_key: max_micro_depth\n env: MAX_MICRO_DEPTH\n default: 30\n description: \"Micro-ReAct 最大递归深度(同 max_micro_depth,可选覆盖)\"\n\n# ==================== Provider Config (已迁移至 model.json) ====================\n#\n# 以下 openai.* / anthropic.* 键为旧版扁平配置路径,\n# 在 AliceConfigStore 中仍然可以 get/set,但不再被模型初始化流程使用。\n#\n# 所有提供商配置(API Key、Base URL、可用模型列表)已迁移至:\n# ~/.alice/model.json → language_models.openai_compatible.{provider_name}\n#\n# 环境变量仍作为最终的 fallback 用于模型初始化:\n# OPENAI_API_KEY — OpenAI API 密钥\n# ANTHROPIC_API_KEY — Anthropic API 密钥\n# DEEPSEEK_API_KEY — DeepSeek API 密钥\n#\n# 示例 model.json 结构(新格式):\n# {\n# \"default_model\": { \"provider\":\"deepseek\", \"model\":\"deepseek-v4-flash\",\n# \"enable_thinking\":true, \"reasoning_effort\":\"high\" },\n# \"planner\": {\n# \"instruction_model_id\": \"deepseek-v4-flash\",\n# \"reasoning_model_id\": \"deepseek-v4-flash\",\n# \"instruction\": { \"enable_thinking\":false, \"reasoning_effort\":\"low\" },\n# \"reasoning\": { \"enable_thinking\":true, \"reasoning_effort\":\"high\" }\n# },\n# \"providers\": {\n# \"deepseek\": {\n# \"base_url\": \"https://api.deepseek.com/v1\",\n# \"api_key\": \"${DEEPSEEK_API_KEY}\",\n# \"available_models\": [\n# { \"name\":\"deepseek-v4-flash\", \"model\":\"deepseek-v4-flash\",\n# \"max_tokens\":131072, \"max_output_tokens\":32000,\n# \"capabilities\": { \"tools\":true, \"images\":false, ... } }\n# ]\n# }\n# }\n# }\n#\n# 以下条目保留仅用于向后兼容的 get/set 支持:\n# ===========================================================================\n\n# openai.api_key:\n# json_key: openai_api_key\n# env: OPENAI_API_KEY\n# default: ~\n# description: \"[已迁移] OpenAI API 密钥。请在 model.json 中配置。\"\n\n# openai.model:\n# json_key: openai_model\n# env: OPENAI_MODEL\n# default: ~\n# description: \"[已迁移] OpenAI provider 使用的模型。请在 model.json 的 available_models 中配置。\"\n\n# openai.base_url:\n# json_key: openai_base_url\n# env: OPENAI_BASE_URL\n# default: \"https://api.openai.com/v1\"\n# description: \"[已迁移] OpenAI API 端点 URL。请在 model.json 中配置。\"\n\n# anthropic.api_key:\n# json_key: anthropic_api_key\n# env: ANTHROPIC_API_KEY\n# default: ~\n# description: \"[已迁移] Anthropic API 密钥。请在 model.json 中配置。\"\n\n# anthropic.model:\n# json_key: anthropic_model\n# env: ANTHROPIC_MODEL\n# default: ~\n# description: \"[已迁移] Anthropic provider 使用的模型。请在 model.json 的 available_models 中配置。\"\n\n# anthropic.base_url:\n# json_key: anthropic_base_url\n# env: ANTHROPIC_BASE_URL\n# default: \"https://api.anthropic.com/v1\"\n# description: \"[已迁移] Anthropic API 端点 URL。请在 model.json 中配置。\"\n"}}, "e2e/smoke/test_smoke_case_2.py": {"8ebb2c40d3906a86e5a1eaa8ea381b21d0df9bd9": {"t": 1786068241.441999, "n": 4255, "h": "badc81b3b11b129c", "c": "#!/usr/bin/env python3\n\"\"\"\nPMTEV Smoke Test — Case 2: 多工具协同与跨文件依赖修复测试\n\n校验 Agent 任务规划(Plan)能力,可跨文件全局检索变量、同步修改多处关联代码。\n\n真实验证:Agent 执行后,检查 config.py 是否将 TIMEOUT_MS 替换为 TIMEOUT_SEC,\n且 client.py 中的所有引用也同步更新,单位换算正确。\n\nPMTEV: Plan (cross-file refactoring) + Tool (edit multiple)\n\nSpec reference: docs/Agent 冒烟测试用例规范文档.md §Case 2\n\nUsage:\n python -m e2e.smoke.test_smoke_case_2\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nimport unittest\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).resolve().parents[2]))\nfrom e2e.smoke.cases import CASE_2\nfrom e2e.smoke.config import PROJECT_MODEL, DEEPSEEK_API_KEY, WORKSPACE_DIR\nfrom e2e.helpers import GRADLEW, PROJECT_ROOT\n\n\nclass TestSmokeCase2(unittest.TestCase):\n \"\"\"Case 2: TIMEOUT_MS → TIMEOUT_SEC 跨文件重命名 + 单位换算\"\"\"\n\n maxDiff = None\n\n WORKSPACE = WORKSPACE_DIR / \"smoke__case-2\"\n FIXTURE_DIR = Path(__file__).resolve().parent / \"fixtures\" / \"cross_file_config\"\n\n @classmethod\n def setUpClass(cls):\n if cls.WORKSPACE.exists():\n import shutil\n shutil.rmtree(cls.WORKSPACE)\n dst = cls.WORKSPACE\n dst.mkdir(parents=True, exist_ok=True)\n import shutil\n for f in cls.FIXTURE_DIR.iterdir():\n if f.is_dir():\n continue\n shutil.copy2(f, dst / f.name)\n\n def _run_agent(self):\n \"\"\"Run Alice Agent with the case prompt.\"\"\"\n prompt_flat = self.case.problem_description.replace(\"\\n\", \" \").strip()\n cmd = [\n str(GRADLEW),\n \":alice-bootstrap:run\",\n \"--rerun-tasks\",\n \"--args\",\n f'run \"{prompt_flat}\" --model {PROJECT_MODEL} --verbose',\n ]\n env = os.environ.copy()\n if DEEPSEEK_API_KEY:\n env[\"DEEPSEEK_API_KEY\"] = DEEPSEEK_API_KEY\n result = subprocess.run(\n cmd, cwd=PROJECT_ROOT, capture_output=True, text=True,\n timeout=self.case.timeout_seconds, env=env,\n )\n return result.returncode, result.stdout + result.stderr\n\n def setUp(self):\n self.case = CASE_2\n # Agent 的工作目录是项目根目录,path 解析相对于项目根\n # 所以 Agent 会修改原始 fixture 文件(而非 temp 副本)\n self.config_file = Path(__file__).resolve().parent / \"fixtures\" / \"cross_file_config\" / \"config.py\"\n self.client_file = Path(__file__).resolve().parent / \"fixtures\" / \"cross_file_config\" / \"client.py\"\n\n @classmethod\n def tearDownClass(cls):\n # 恢复原始 fixture\n import subprocess\n for f in [\"config.py\", \"client.py\"]:\n fixture = cls.FIXTURE_DIR / f\n subprocess.run(\n [\"git\", \"checkout\", \"--\", str(fixture)],\n cwd=PROJECT_ROOT, capture_output=True)\n\n def test_timeout_renamed_across_files(self):\n \"\"\"config.py 和 client.py 都必须将 TIMEOUT_MS 替换为 TIMEOUT_SEC\"\"\"\n code, output = self._run_agent()\n self.assertEqual(code, 0, f\"Agent 退出码非0\\n---\\n{output[-500:]}\")\n\n # 验证 config.py\n config_content = self.config_file.read_text(encoding=\"utf-8\")\n self.assertIn(\n \"TIMEOUT_SEC\",\n config_content,\n f\"config.py 未包含 TIMEOUT_SEC\\n当前内容:\\n{config_content}\",\n )\n self.assertNotIn(\n \"TIMEOUT_MS\",\n config_content,\n f\"config.py 仍包含旧字段 TIMEOUT_MS\\n当前内容:\\n{config_content}\",\n )\n\n # 验证 client.py\n client_content = self.client_file.read_text(encoding=\"utf-8\")\n self.assertIn(\n \"TIMEOUT_SEC\",\n client_content,\n f\"client.py 未引用 TIMEOUT_SEC\\n当前内容:\\n{client_content}\",\n )\n self.assertNotIn(\n \"TIMEOUT_MS\",\n client_content,\n f\"client.py 仍引用旧字段 TIMEOUT_MS\\n当前内容:\\n{client_content}\",\n )\n\n def test_unit_conversion_correct(self):\n \"\"\"单位换算正确:5秒 = 5000毫秒\"\"\"\n code, output = self._run_agent()\n self.assertEqual(code, 0, f\"Agent 退出码非0\\n---\\n{output[-500:]}\")\n\n config_content = self.config_file.read_text(encoding=\"utf-8\")\n\n # 原 TIMEOUT_MS = 5000 → 换算后 TIMEOUT_SEC = 5\n self.assertIn(\n \"5\", config_content,\n f\"未找到正确的单位换算: TIMEOUT_SEC 应=5\\n当前内容:\\n{config_content}\",\n )\n\n\nif __name__ == \"__main__\":\n unittest.main(verbosity=2)\n"}}}}