python-library-ai-agent 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_library_ai_agent-0.1.0/.cursor/skills/ai-agent-package-changelog/SKILL.md +67 -0
- python_library_ai_agent-0.1.0/.cursor/skills/ai-agent-package-design/SKILL.md +46 -0
- python_library_ai_agent-0.1.0/.cursor/skills/ai-agent-package-preload/SKILL.md +24 -0
- python_library_ai_agent-0.1.0/.gitignore +6 -0
- python_library_ai_agent-0.1.0/.vscode/settings.json +5 -0
- python_library_ai_agent-0.1.0/PKG-INFO +10 -0
- python_library_ai_agent-0.1.0/ai_agent/__init__.py +66 -0
- python_library_ai_agent-0.1.0/ai_agent/agent.py +122 -0
- python_library_ai_agent-0.1.0/ai_agent/app/__init__.py +10 -0
- python_library_ai_agent-0.1.0/ai_agent/app/_workspace.py +127 -0
- python_library_ai_agent-0.1.0/ai_agent/app/app.py +321 -0
- python_library_ai_agent-0.1.0/ai_agent/app/harness_io.py +109 -0
- python_library_ai_agent-0.1.0/ai_agent/app/output_format.py +77 -0
- python_library_ai_agent-0.1.0/ai_agent/app/packet.py +39 -0
- python_library_ai_agent-0.1.0/ai_agent/app/session.py +742 -0
- python_library_ai_agent-0.1.0/ai_agent/app/session_store.py +85 -0
- python_library_ai_agent-0.1.0/ai_agent/builtin_tools/__init__.py +18 -0
- python_library_ai_agent-0.1.0/ai_agent/builtin_tools/current_time.py +39 -0
- python_library_ai_agent-0.1.0/ai_agent/builtin_tools/pack.py +20 -0
- python_library_ai_agent-0.1.0/ai_agent/builtin_tools/prefix.py +11 -0
- python_library_ai_agent-0.1.0/ai_agent/context.py +151 -0
- python_library_ai_agent-0.1.0/ai_agent/harness/__init__.py +3 -0
- python_library_ai_agent-0.1.0/ai_agent/harness/current_time.py +25 -0
- python_library_ai_agent-0.1.0/ai_agent/harness/harness.py +324 -0
- python_library_ai_agent-0.1.0/ai_agent/harness/process.py +115 -0
- python_library_ai_agent-0.1.0/ai_agent/harness/prompts.py +38 -0
- python_library_ai_agent-0.1.0/ai_agent/harness/sandbox.py +139 -0
- python_library_ai_agent-0.1.0/ai_agent/json_extract.py +70 -0
- python_library_ai_agent-0.1.0/ai_agent/listener.py +172 -0
- python_library_ai_agent-0.1.0/ai_agent/llm.py +39 -0
- python_library_ai_agent-0.1.0/ai_agent/llm_openai.py +117 -0
- python_library_ai_agent-0.1.0/ai_agent/loop.py +124 -0
- python_library_ai_agent-0.1.0/ai_agent/mcp_config.py +54 -0
- python_library_ai_agent-0.1.0/ai_agent/mcp_loader.py +110 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/__init__.py +9 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/compression_work.py +71 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/compressor.py +339 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/config.py +40 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/context_builder.py +57 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/memory_system.py +561 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/models.py +76 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/snapshot_merge.py +158 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/store.py +107 -0
- python_library_ai_agent-0.1.0/ai_agent/memory/worker.py +227 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/__init__.py +15 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/complete.py +64 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/delivery.py +41 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/display.py +46 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/models.py +44 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/parse.py +39 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/planner.py +204 -0
- python_library_ai_agent-0.1.0/ai_agent/plan/runner.py +281 -0
- python_library_ai_agent-0.1.0/ai_agent/react_tool_turn.py +39 -0
- python_library_ai_agent-0.1.0/ai_agent/rule/__init__.py +3 -0
- python_library_ai_agent-0.1.0/ai_agent/rule/rules.py +36 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/__init__.py +5 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/builtin_registry.py +56 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/catalog.py +104 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/frontmatter.py +83 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/manager.py +486 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/models.py +31 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/roots.py +150 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/skill_kit.py +80 -0
- python_library_ai_agent-0.1.0/ai_agent/skill/tool_declarations.py +68 -0
- python_library_ai_agent-0.1.0/ai_agent/tools.py +123 -0
- python_library_ai_agent-0.1.0/examples/__init__.py +0 -0
- python_library_ai_agent-0.1.0/examples/_support/__init__.py +0 -0
- python_library_ai_agent-0.1.0/examples/_support/demo_sandbox.py +48 -0
- python_library_ai_agent-0.1.0/examples/_support/example_env.py +20 -0
- python_library_ai_agent-0.1.0/examples/_support/llm_config.py +86 -0
- python_library_ai_agent-0.1.0/examples/_support/mcp_json_config.py +54 -0
- python_library_ai_agent-0.1.0/examples/_support/print_listener.py +243 -0
- python_library_ai_agent-0.1.0/examples/_support/print_timing.py +23 -0
- python_library_ai_agent-0.1.0/examples/chat/.env.example +4 -0
- python_library_ai_agent-0.1.0/examples/chat/__main__.py +122 -0
- python_library_ai_agent-0.1.0/examples/chat/rules/assistant.md +11 -0
- python_library_ai_agent-0.1.0/examples/run/.env.example +3 -0
- python_library_ai_agent-0.1.0/examples/run/__main__.py +57 -0
- python_library_ai_agent-0.1.0/examples/run/rules/assistant.md +1 -0
- python_library_ai_agent-0.1.0/examples/search/.env.example +4 -0
- python_library_ai_agent-0.1.0/examples/search/__main__.py +179 -0
- python_library_ai_agent-0.1.0/examples/search/mcp.json.example +18 -0
- python_library_ai_agent-0.1.0/examples/search/rules/assistant.md +147 -0
- python_library_ai_agent-0.1.0/examples/search/scripts/gen_mcp_json.py +100 -0
- python_library_ai_agent-0.1.0/pyproject.toml +19 -0
- python_library_ai_agent-0.1.0/run.bat +28 -0
- python_library_ai_agent-0.1.0/test.bat +9 -0
- python_library_ai_agent-0.1.0/tests/script_llm.py +29 -0
- python_library_ai_agent-0.1.0/tests/test_ai_agent.py +45 -0
- python_library_ai_agent-0.1.0/tests/test_app.py +115 -0
- python_library_ai_agent-0.1.0/tests/test_builtin_tools.py +82 -0
- python_library_ai_agent-0.1.0/tests/test_context.py +41 -0
- python_library_ai_agent-0.1.0/tests/test_example_env.py +36 -0
- python_library_ai_agent-0.1.0/tests/test_harness.py +72 -0
- python_library_ai_agent-0.1.0/tests/test_json_extract.py +27 -0
- python_library_ai_agent-0.1.0/tests/test_listener.py +154 -0
- python_library_ai_agent-0.1.0/tests/test_llm_openai.py +97 -0
- python_library_ai_agent-0.1.0/tests/test_mcp_config.py +49 -0
- python_library_ai_agent-0.1.0/tests/test_mcp_json_config.py +54 -0
- python_library_ai_agent-0.1.0/tests/test_mcp_loader.py +22 -0
- python_library_ai_agent-0.1.0/tests/test_memory_system.py +238 -0
- python_library_ai_agent-0.1.0/tests/test_plan.py +406 -0
- python_library_ai_agent-0.1.0/tests/test_plan_delivery.py +104 -0
- python_library_ai_agent-0.1.0/tests/test_planning_prompt.py +19 -0
- python_library_ai_agent-0.1.0/tests/test_react_loop.py +198 -0
- python_library_ai_agent-0.1.0/tests/test_react_tool_turn.py +38 -0
- python_library_ai_agent-0.1.0/tests/test_rules.py +28 -0
- python_library_ai_agent-0.1.0/tests/test_run_packet.py +198 -0
- python_library_ai_agent-0.1.0/tests/test_skill_kit.py +109 -0
- python_library_ai_agent-0.1.0/tests/test_skill_manager.py +114 -0
- python_library_ai_agent-0.1.0/tests/test_tool_registry.py +40 -0
- python_library_ai_agent-0.1.0/update.bat +9 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-agent-package-changelog
|
|
3
|
+
description: >-
|
|
4
|
+
ai-agent 包要求与决议;矛盾以最新日期条目为准。当前口径见 ai-agent-package-design。
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ai-agent 包 · Changelog
|
|
8
|
+
|
|
9
|
+
(规则见 `~/.cursor/skills/agent-project-changelog/SKILL.md`。)
|
|
10
|
+
|
|
11
|
+
## 2026-06-03
|
|
12
|
+
|
|
13
|
+
- **决议**:**`ReactLoop`** 同回合若同时请求取时与其它工具,仅执行 **`builtin__current_time`** / **`harness__current_time`**,其它工具返回固定说明(**`react_tool_turn.py`**),迫使下回合据取时结果重写 query。搜索示例 **`rules/assistant.md`** 增补禁止同回合并行取时+搜索、过程句反例、无当日结果时的第二次 query 范例、终稿官方/媒体并列口径;**`step_intermediate_instruction`**、**`PLANNING_SYSTEM_PROMPT`** 同步。
|
|
14
|
+
- **决议**:搜索示例 **`rules/assistant.md`**、**`step_intermediate_instruction`**、**`PLANNING_SYSTEM_PROMPT`**、**`skills/chat-search-answer`** 二次收紧:首搜 query 须带时间窗口(附正反例);搜索步禁止英文过程句与首行 `---` 前缀;矛盾摘要事实不入要点;同会话可对照上轮要点减搜;官方动态与 brief 篇幅(简短/总结一下)口径;要点条数贴近用户所求 N 条。
|
|
15
|
+
- **决议**:搜索示例规则与 **`PLANNING_SYSTEM_PROMPT`**、**`run_output_instruction`** / **`step_intermediate_instruction`** 收紧:每轮搜索步须 **`builtin__current_time`**;规划 objective 不写死公历日期;搜索步产出首行锚定日期;窗口内已无结果时勿刷满 3 次搜索;终稿仅复述搜索步要点;JSON **`answer`** 禁止未转义 ASCII 双引号。**`skills/chat-search-answer`** 同步事实边界与日期口径。
|
|
16
|
+
|
|
17
|
+
## 2026-06-02
|
|
18
|
+
|
|
19
|
+
- **决议**:**`AgentApp.current_time_tool_enabled`**(默认 **`True`**)单独注册 **`builtin__current_time`**;**`harness_enabled=True`** 时从 Harness 工具表去掉 **`harness__current_time`**,避免与内置重复。搜索示例规则收紧:先取当前时间、搜索次数上限、中间步只写结论、终稿 JSON 须合法;**`PLANNING_SYSTEM_PROMPT`** 对搜索短答建议 2 步规划。
|
|
20
|
+
- **决议**:示例配置三分:**各示例目录 `.env`**(**`LLM_*`**,**`dotenv_values` 只读、不写 `os.environ`**);**`examples/search/mcp.json`** 仅 **`mcpServers`** 及各 server **`env`**(无 **`agentEnv`**);**`MCPToolLoader`** 子进程不继承宿主 **`os.environ`**。dev 依赖 **`python-dotenv`**。**`.env`** / **`mcp.json`** 由 monorepo 根与 **`packages/ai-agent/.gitignore`** 忽略(**`*.example`** 例外,可提交)。
|
|
21
|
+
- **决议**:规划与记忆用用户原文不再附带最终交付 JSON 说明(**`_compose_plan_user_message`**);**`run_output_instruction`** / **`step_intermediate_instruction`** 仅注入计划最后一步 / 非最后一步;**`complete_text(parse_from_answer_text_only=True)`** 供规划解析,避免思考草稿 JSON 覆盖正式计划;**`_finish_plan_run`** 向记忆写入解析后的 **`answer`**;**`PLANNING_SYSTEM_PROMPT`** 允许简单任务 1 步;示例 **`print_listener`** 不打印规划阶段思考流。
|
|
22
|
+
- **决议**:**`Agent`** / **`AgentApp`** 增加 **`thinking_enabled`**(默认 **`False`**);**`OpenAILLM`** 在 chat completions 请求中写入 **`enable_thinking`**;记忆压缩用模型不开启思考模式。
|
|
23
|
+
- **决议**:**`examples/chat`** 与 **`examples/search`** 拆分:**chat** 仅测记忆与会话隔离(无 MCP);**search** 承载 **`bocha_search`**、**`skill_roots`** 与 **`chat-search-answer`** 终稿脚本;**`gen_mcp_json.py`** 在 **search** 目录。
|
|
24
|
+
- **决议**:**`Harness`** 沙箱工具新增 **`harness__current_time`**(可选 **`timezone`**,IANA 名;省略则用本机本地时区);实现见 **`ai_agent/harness/current_time.py`**。
|
|
25
|
+
- **决议**:**`AgentListener`** 增补 **`on_plan_step_end`**、**`on_app_run_end`**;规划补全经 **`complete_text`** 流式触发 **`on_thinking_delta` / `on_output_delta`**(**`RunContext.phase`** 为 **`planning`**);逐步执行为 **`step`**;**`AgentApp.run`** 结束时 **`notify_app_run_end`** 交付 **`RunOutputPacket`**(含 **`answer`** 与 **`output_files`**)。示例 **`create_print_listener`** 按该顺序打印。
|
|
26
|
+
- **决议**:**`AgentApp.harness_enabled`** 默认 **`False`**,为 **`True`** 时才向模型注册 Harness 沙箱工具;**`RunInputPacket.clear`**(原 **`clear_harness`**)为 **`True`** 时清空 Harness 工作区、**`memory/`** 分层存储与无 Memory 时的 **`conversation.json`**;附带 **`input_files`** 且 Harness 未启用时 **`run`** 报错。
|
|
27
|
+
- **决议**:**`MemorySystem`** 主推理路径不得被压缩阻塞:Agent 每轮读 **`_agent_view`** 已发布快照;**`append` / `remember`** 仅短暂持锁并同步短期或重要条目到视图;压缩在 worker 线程锁外执行,提交后再 **`_publish_agent_view`**,下轮读到合并后的新快照。
|
|
28
|
+
- **决议**:中文注释与 docstring 禁用 **门面、基址、根名、应用级、运行期**(见用户根 **forbidden-doc-comment-vocabulary**);包内既有表述已按表改写。
|
|
29
|
+
- **决议**:移除 **`StepRequirement`**、**`step_requirements`** 与 **`plan/validator.py`**;规划仅依赖规则、技能与 **`PLANNING_SYSTEM_PROMPT`** 中的自然语言说明,不再注入或合并硬性步骤槽位。
|
|
30
|
+
- **决议**:**`AgentApp`** 移除 **`expose_skill_management_tools`**、**`auto_refresh_skills`**、**`builtin_registry`**、**`harness_prefix` / `skill_prefix`**、**`harness_subdir` / `memory_subdir`**;skill 管理工具在配置 **`skill_roots`** 后始终启用;子目录与工具前缀固化;删除 **`AgentContext.before_each_step`** 及步前自动 **`refresh_skills`**。
|
|
31
|
+
- **决议**:**`AgentApp`** 记忆容量与保留策略改为 **`memory_short_term_max_messages`** 等平铺参数;移除构造入参 **`memory_config`**;内部仍组装 **`MemoryConfig`** 传给 **`MemorySystem`**。
|
|
32
|
+
- **决议**:规划系统提示固化为 **`ai_agent/harness/prompts.py`**(**`PLANNING_SYSTEM_PROMPT`**);移除 **`plan/planning_system_prompt.md`**、**`planning_prompt_path`** 及 **`plan/planning_prompt.py`**。
|
|
33
|
+
- **决议**:聊天室搜索短答从 **tools/chat_search_answer** MCP 迁至 **`skills/chat-search-answer`**;**`examples/chat`** 以 **`skill_roots`** 挂载 skills 仓库,终稿步骤改按技能改写,不再注册 **`chat_search_answer__rewrite_for_chat`**。
|
|
34
|
+
- **决议**:**`AgentApp.run(RunInputPacket) -> RunOutputPacket`**:输入含用户名、会话 id、要求、附件路径、**`clear`**;先 **`run_with_plan`** 再解析 JSON 交付(**`answer`** + **`output_files`**);单次调用后 **`close_session`**;无 Memory 时 **`conversation.json`** 持久化;MCP 等经 **`shared_extra_tools`** 共享。
|
|
35
|
+
- **决议**:**`AgentApp`** 会话目录分层:**`sessions/<id>/harness/`** 为 Harness 工作区(**`harness_subdir`**),**`memory/`** 为记忆存储(**`memory_subdir`**);Harness 工具无法通过相对路径访问会话根上的 **`memory/`**。
|
|
36
|
+
- **决议**:移除 **`required_skill_refs`**、**`preload_skill_refs`**、**`skill_readonly`**;规则与 skill 职责分离,**`build_system_prompt()`** 仅拼规则。
|
|
37
|
+
- **决议**:skill 运行时只读:删除写入/读附属文件/**`load_skill`** 等管理工具;**`enable_skill`** 在单次 **`Agent.run`** 内注入 **`ephemeral_skill_context`**,运行结束 **`SkillManager.end_run()`** 还原启用状态与上下文。
|
|
38
|
+
- **决议**:新增 **`ai_agent/rule/`**(**`RuleSet`**、**`rule_paths`**):构造时从指定文本文件读入并固定作为系统提示,**不**暴露为工具;**`Agent.run(messages)`** 与 **`AgentSession.run` / `run_with_plan`** 移除运行时 **`system_prompt`** 参数;**`AgentApp`** 增加 **`rule_paths`**;示例 **`rules/assistant.md`**。
|
|
39
|
+
- **决议**:**SkillKit** 升级为动态能力层:**`SkillManager`** + 分层 **`ToolRegistry`**(base / management / skill / extra);**`enable_skill` / `disable_skill` / `refresh_skills`**;frontmatter **`tools:`** + **`BuiltinToolRegistry`**(仅 `builtin:` handler);**`AgentSession`** 增加 skill 会话 API 与必读拼入 system prompt。
|
|
40
|
+
- **决议**:新增 **`ai_agent/memory/`**(对外主类 **`MemorySystem`**、**`MemoryConfig`**、**`BuiltMemoryContext`**);单会话存储目录、四层记忆(短期 / 日期 / 长期 / 重要)、独立线程 + 专用便宜模型压缩;包根 **`__all__`** 导出;**`AgentSession.run(speaker=...)`** 与 **`AgentApp(memory_*=...)`** 可选集成。
|
|
41
|
+
|
|
42
|
+
## 2026-06-01
|
|
43
|
+
|
|
44
|
+
- **决议**:示例私密配置改由 **`examples/chat/mcp.json`**(已 **gitignore**)承载:**`agentEnv`** 供 **`Agent`**,各 MCP server **`env`** 仅注入对应子进程;不再由示例加载包根 **`.env`**;模板 **`mcp.json.example`**。
|
|
45
|
+
- **决议**:新增 **`ai_agent/app/`**(对外主类 **`AgentApp`**、**`AgentSession`**);总沙箱 + 按 **`session_id`** 子沙箱;**`skill_roots`** / **`skill_readonly`** 在 AgentApp 构造时配置;包根 **`__all__`** 导出,不改动 **`Agent` / `Harness`** 实现。
|
|
46
|
+
- **决议**:**`SkillKit(..., readonly=True)`** 与 **`Harness(..., skill_readonly=True)`** 仅向模型暴露读 skill 工具;写入类工具不注册,直接调用写入 API 报错。
|
|
47
|
+
- **决议**:**`examples/chat`** 固定单轮内置演示问题后退出,不再提供交互式多轮输入与 **`--once`** 开关。
|
|
48
|
+
- **决议**:示例目录改为 **`examples/run`** 与 **`examples/chat`**;前者表示最小运行,后者为带 **`current_time`** 与 **`bocha_search`** 的 MCP 单轮演示;**`run.bat`** 将 **`%*`** 转发给 **`python -m`**。
|
|
49
|
+
- **决议**:新增 **`ai_agent/skill/`**(对外主类 **`SkillKit(roots).build_tools()`**);扫描指定根下含 **`SKILL.md`** 的技能目录,提供 list/load/元数据/写入/新建等工具;**`Harness(..., skill_roots=...)`** 可选挂载 skill 区,**`build_skill_tools()`** / **`build_all_tools()`** 与沙箱 **`build_tools()`** 分离。
|
|
50
|
+
- **决议**:**Harness** 迁入 **`ai_agent/harness/`**(对外主类 **`Harness(workspace).build_tools()`**);删除 **`tools/harness`** MCP 小仓库;隔离工作区由调用方指定目录,不经环境变量。
|
|
51
|
+
- **决议**:移除 **`python-library-configlib`** 依赖;MCP 改为 **`McpConfig`**(Pydantic)入参 + **`MCPToolLoader.load`**;配置文件解析由调用方负责。
|
|
52
|
+
- **决议**:新增 **`MCPToolLoader`** 与 **`discover_mcp_config_paths`**;运行时依赖 **`mcp`**;不再维护独立本地 `tool.py` 封装层,工具以 MCP 配置加载为主。
|
|
53
|
+
|
|
54
|
+
## 2026-05-31
|
|
55
|
+
|
|
56
|
+
- **决议**:在 **`packages/ai-agent/`** 建立 **python-library-ai-agent** 包骨架(**`pyproject.toml`**、**`ai_agent/`**、**`tests/`**、**`update.bat`** / **`test.bat`**)。
|
|
57
|
+
- **决议**:建立 **ai-agent-package-preload**、**ai-agent-package-design**、**ai-agent-package-changelog** 三件套;**python-library-design-notes** 子包清单表 **ai-agent** 标为「有」。
|
|
58
|
+
- **决议**:落地 ReAct 运行核模块划分(**`agent` / `run` / `context` / `llm` / `tools` / `loop` / `stream`**);对外主类 **`Agent`**,记录单元 **`AgentRun`**;首版零第三方运行时依赖;**不**单独维护 **`errors`** 模块。
|
|
59
|
+
- **决议**:增加 **`examples/`**,每子目录一个可 `python -m examples.<name>` 运行的用例;共享 **`examples/_support/`**(脚本化 LLM、演示工具)。
|
|
60
|
+
- **决议**:**`examples/`** 收敛为 **`examples/chat`** 单用例;**`.env`** 装载 `LLM_*` 私密配置,**`httpx`** + **`python-dotenv`** 仅 **`[dev]`** 可选依赖;OpenAI 兼容流式客户端在 **`examples/_support/openai_llm.py`**。
|
|
61
|
+
- **决议(2026-06-01)**:API 收口为 **`Agent(llm)`** + **`run(system_prompt, messages)` → `AgentContext`**;**`Tool`** / **`ReactLoop`** 在库内;删除 **`run.py`**、**`stream.py`**、**`examples/_support`**。
|
|
62
|
+
- **决议(2026-06-01)**:**`LLMClient`** + **`OpenAILLM` / `LangChainLLM` / `LiteLLMLLM`** 适配器;**`api_key` 在外部 SDK 客户端配置**;可选 extra **`[openai]`** 等;移除内置 **`httpx`** 直连。
|
|
63
|
+
- **决议(2026-06-01)**:仅保留 **OpenAI**;**`openai`** 升为**基本依赖**;**`Agent(api_key, model, ...)`** 内部建客户端;删除 LangChain / LiteLLM 模块与可选 extra。
|
|
64
|
+
- **决议(2026-06-01)**:删除 **`Agent.from_env()`**;`LLM_*` 环境变量仅由 **`examples/_support/llm_config.py`** 读取。
|
|
65
|
+
- **决议(2026-06-01)**:删除库内 **`for_test`**、**`ScriptLLM`**;测试替身见 **`tests/script_llm.py`**。
|
|
66
|
+
- **决议(2026-05-31)**:对外仅 **`Agent.run`**(`async for` 流式 **`RunContext`**);**`Agent.context`** 为 **`AgentContext`**(`llm` / `tools` / `max_steps`);无 **`stream`**、无 **`cancel`**。
|
|
67
|
+
- **决议(2026-05-31)**:**`Agent`** 构造 **`base_url`** 必填;**`AsyncOpenAI`** 直接传参,不用 **`client_kwargs`** 字典。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-agent-package-design
|
|
3
|
+
description: >-
|
|
4
|
+
ai-agent 包(python_library/packages/ai-agent)设计约定;变更见 ai-agent-package-changelog。
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ai-agent 包 · 设计笔记
|
|
8
|
+
|
|
9
|
+
> 变更记录见 `.cursor/skills/ai-agent-package-changelog/SKILL.md`;矛盾以 changelog 最新条目为准。
|
|
10
|
+
|
|
11
|
+
## 设计意图
|
|
12
|
+
|
|
13
|
+
- **可复用 Agent 基建**:在 monorepo 内沉淀 LLM / 工具调用 / 多步编排等**与具体家居或业务无关**的能力,供 **home-agent** 及其它应用 `pip` 依赖。
|
|
14
|
+
- **应用与库分离**:设备、自动化 YAML、局域网发现等留在应用仓库;本包只收跨项目可复用的 Agent 片段。
|
|
15
|
+
- **对外形态**:新增能力时优先用**高聚合总类**(见用户根 **cohesive-main-class-api**),经 **`ai_agent` 包根** 的 **`__all__`** 导出,避免碎片函数与包级全局单例。
|
|
16
|
+
|
|
17
|
+
## 硬性要求
|
|
18
|
+
|
|
19
|
+
- **PyPI 名**:`python-library-ai-agent`;**导入包名**:`ai_agent`(目录 **`ai_agent/`**)。
|
|
20
|
+
- **无 callback 分层**:本包默认**不**采用 `api` / `impl`;若未来引入 callback,须在 changelog 决议后更新预加载(加入 **callback-api-impl-layers**)与设计笔记。
|
|
21
|
+
- **中文用户向文案**:注释与 docstring 遵循 **python-doc-comments**;禁用词仅查用户根 **forbidden-doc-comment-vocabulary**。
|
|
22
|
+
- **依赖**:新增第三方依赖须在 **`pyproject.toml`** 声明,并在 changelog 记一笔;避免把应用专属依赖(如单厂商 SDK)拉进本包除非已决议为通用能力。
|
|
23
|
+
|
|
24
|
+
## 与 monorepo 的关系
|
|
25
|
+
|
|
26
|
+
- 中文禁用词:**forbidden-doc-comment-vocabulary**(用户根);**勿**复制包级禁用词表。
|
|
27
|
+
- monorepo 整库约定见 **`python-library-design-notes`**;本包清单表项为 **ai-agent**。
|
|
28
|
+
|
|
29
|
+
## ReAct 运行核(2026-05-31,2026-06 收口)
|
|
30
|
+
|
|
31
|
+
- **对外主类**:`Agent`(`agent.py`)构造时由**调用方**传入 **`api_key`**、**`model`**、**`base_url`**(必填)及可选 **`rule_paths`** 等,内部 **`AsyncOpenAI(api_key=..., base_url=...)`**;库内**不**读取环境变量或 `.env`。系统提示由 **`RuleSet`**(`ai_agent/rule/`)在构造时从指定文本文件读入;对外 **`run(messages)`**,不再接受运行时 **`system_prompt`** 字符串。示例在 **`examples/_support/llm_config.py`** 用 **`dotenv_values`** 读各示例目录 **`.env`** 的 `LLM_*`(**不**写入 **`os.environ`**);**`examples/search`** 另用 **`mcp.json`**(仅 **`mcpServers`** 及各 server **`env`**);**`MCPToolLoader`** 子进程**不**继承宿主 **`os.environ`**。
|
|
32
|
+
- **语言模型**:仅 **OpenAI 官方 SDK**(`openai` 为**基本依赖**);`llm_openai.OpenAILLM` 为库内实现,不对外要求自行适配。
|
|
33
|
+
- **Rule**:**`RuleSet`** / **`rule_paths`** — 调用方指定 Markdown 等文本文件路径,启动时读入并固定拼入系统提示;**不**注册为工具(与 skill 子工具区分)。
|
|
34
|
+
- **上下文**:**`AgentContext`** 装 **`llm`**、**`tools`**、**`max_steps`**(`Agent.context`);单轮 **`RunContext`** 含 **`system_prompt`**(由规则与必读 skill 等拼装)、**`messages`**、**`tool_invocations`**、**`thinking`**、**`output`**、**`status`**。
|
|
35
|
+
- **工具**:`Tool` + 库内 `ToolRegistry`(`tools.py`);MCP 经 **`McpConfig`** / **`McpStdioServerConfig`**(`mcp_config.py`,Pydantic)与 **`MCPToolLoader.load`**(`mcp_loader.py`)起 stdio server、保持 **`ClientSession`** 并包装为 `Tool`;**不**在库内读配置文件;调用方在 `Agent.run` 结束后 **`await loader.close()`**。对外工具名为 **`{server}__{tool}`**。
|
|
36
|
+
- **Harness**(`ai_agent/harness/`,与 Agent 核分离):调用方构造 **`Harness(workspace)`** 并 **`build_tools()`** 得到沙箱 `list[Tool]`;可选 **`skill_roots`** 划定只读 skill 区,经 **`build_skill_tools()`** 或 **`build_all_tools()`** 注入。与 MCP 工具一并传入 **`Agent(..., tools=...)`**。沙箱与 skill 路径均不得越界;工具返回值与说明**不向模型暴露宿主机绝对路径**。
|
|
37
|
+
- **应用级内置工具**(`ai_agent/builtin_tools/`):与 Harness 无关的基础能力,经 **`AgentApp.current_time_tool_enabled`**(默认 **`True`**)注册 **`builtin__current_time`**;**`harness_enabled=True`** 时不再注册 **`harness__current_time`**,避免重复。实现复用 **`harness/current_time.get_current_time`**。
|
|
38
|
+
- **Rule 与 Skill 分工**:**`rule_paths`** 固定拼入系统提示;skill **不**再使用 **`required_skill_refs` / `preload_skill_refs`**。skill 仓库运行时只读(无写入/附属文件读取类管理工具),须在开发阶段维护 **`SKILL.md`**。
|
|
39
|
+
- **Skill 动态能力层**(`ai_agent/skill/`):**`SkillManager`** + **`SkillKit`**;工具表分层:**基础 → skill 管理 → 已启用 skill 子工具 → extra**。管理工具含 list / enable / disable / refresh / get_metadata / roots_info(**`AgentApp`** 配置 **`skill_roots`** 后始终注册);**`BuiltinToolRegistry`** 在 **`SkillManager`** 内创建,宿主可在装配前向 **`builtin_registry`** 注册 **`builtin:`** 工具。**`enable_skill`** 暴露子工具并在**单次 ReAct 运行**内将正文注入 **`RunContext.ephemeral_skill_context`**,**`end_run`** 后还原。子工具须在 frontmatter 声明 **`tools:`**,handler 仅 **`builtin:名称`**。
|
|
40
|
+
- **规划提示**:内置 **`ai_agent/harness/prompts.py`** 的 **`PLANNING_SYSTEM_PROMPT`**;**`PlanPlanner`** 固定使用,不接受外部路径或运行时覆盖。
|
|
41
|
+
- **AgentApp / AgentSession**:**`rule_paths`**、**`skill_roots`**;**`build_system_prompt()`** 仅合并规则;会话 **`run` / `run_with_plan`** 不接收运行时 **`system_prompt`** 字符串。
|
|
42
|
+
- **AgentApp**(`ai_agent/app/`,集成最上层):对外主类 **`AgentApp(sandbox, ...)`**;**`harness_enabled`** 默认 **`False`**,为 **`True`** 时注册 Harness 沙箱工具;总沙箱下 **`sessions/<session_id>/`** 为会话根;其下固定 **`harness/`** 与 **`memory/`** 子目录;工具对外名前缀 **`harness__` / `skill__`** 在库内固化;**`open_session`** 装配 **`AgentSession`**;**`shared_extra_tools` / `set_shared_extra_tools`** 供 MCP 等在 AgentApp 构造时一次加载、多会话共享;**`run(RunInputPacket) -> RunOutputPacket`** 为阻塞式运行入口(先规划再串行执行),单次调用结束 **`close_session`** 释放内存,对话无 Memory 时落盘 **`conversation.json`**;**`RunInputPacket.clear`** 清空 Harness、**`memory/`** 与无 Memory 时的对话持久化;事件经 **`AgentListener`**(规划/步骤/流式/工具/**`on_app_run_end`** 交付 **`RunOutputPacket`**);**`RunContext.phase`** 区分规划、计划步与直连 ReAct。示例打印见 **`examples/_support/print_listener.py`**。输入/输出数据包见 **`RunInputPacket` / `RunOutputPacket`**。
|
|
43
|
+
- **MemorySystem**(`ai_agent/memory/`,与会话 Agent 分离):对外主类 **`MemorySystem(storage_dir, config=MemoryConfig(...))`**;存储目录在会话根下的 **`memory/`**(与 Harness 沙箱并列);**`AgentApp`** 以 **`memory_api_key` / `memory_model` / `memory_base_url`** 与容量类 **`memory_*`** 平铺参数配置,构造时组装 **`MemoryConfig`**;**`AgentSession.run(..., speaker=...)`** 可选绑定。Agent 每轮读**已发布视图**,压缩在独立线程锁外执行,完成后发布新视图,**不阻塞**主 **`Agent.run`**。
|
|
44
|
+
- **循环**:`ReactLoop`(`loop.py`)驱动多步 ReAct;无独立 `errors` / `stream` / `run` 记录模块。
|
|
45
|
+
- **运行时依赖**:**`openai`**、**`mcp`**、**`pydantic`**;示例与测试 dev 含 **`python-dotenv`**。
|
|
46
|
+
- **示例命名**:**`examples/run`** 是最小单轮运行(**`.env`**);**`examples/chat`** 经 **`AgentApp.run`** 演示分层记忆与会话隔离(**`.env`**,无 MCP);**`examples/search`** 演示 **`bocha_search`** MCP 与 **`skills/chat-search-answer`** 终稿(**`.env`** + **`mcp.json`**;规则与技能自然语言引导,无硬性步骤槽位)。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-agent-package-preload
|
|
3
|
+
description: >-
|
|
4
|
+
ai-agent 包会话预加载:编辑 packages/ai-agent 前先 Read 的 skill 顺序。
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ai-agent 包 · 预加载
|
|
8
|
+
|
|
9
|
+
## 初始化加载(Session preload)
|
|
10
|
+
|
|
11
|
+
在 **`packages/ai-agent/`** 内改代码或讨论本包边界时,按序 **Read**:
|
|
12
|
+
|
|
13
|
+
1. `~/.cursor/skills/project-skill-manifest-policy/SKILL.md`
|
|
14
|
+
2. `../../../.cursor/skills/python-library-design-notes/SKILL.md`(monorepo 根;单包工作区则跳过)
|
|
15
|
+
3. `.cursor/skills/ai-agent-package-design/SKILL.md`
|
|
16
|
+
4. `.cursor/skills/ai-agent-package-changelog/SKILL.md`
|
|
17
|
+
5. `~/.cursor/skills/python-doc-comments/SKILL.md`
|
|
18
|
+
6. `~/.cursor/skills/python-no-fluent-chaining/SKILL.md`
|
|
19
|
+
7. `~/.cursor/skills/forbidden-doc-comment-vocabulary/SKILL.md`
|
|
20
|
+
8. `~/.cursor/skills/cohesive-main-class-api/SKILL.md`
|
|
21
|
+
|
|
22
|
+
## 用过的 skill(追加记录)
|
|
23
|
+
|
|
24
|
+
- python-doc-comments、python-no-fluent-chaining、forbidden-doc-comment-vocabulary、agent-codegen-self-review(示例拆分)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-library-ai-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Requires-Python: >=3.10
|
|
5
|
+
Requires-Dist: mcp>=1.0
|
|
6
|
+
Requires-Dist: openai>=1.0
|
|
7
|
+
Requires-Dist: pydantic>=2.0
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from ai_agent.agent import Agent
|
|
2
|
+
from ai_agent.app import AgentApp, AgentSession, RunInputPacket, RunOutputPacket
|
|
3
|
+
from ai_agent.harness import Harness
|
|
4
|
+
from ai_agent.memory import BuiltMemoryContext, MemoryConfig, MemorySystem
|
|
5
|
+
from ai_agent.skill import BuiltinToolRegistry, SkillKit, SkillManager
|
|
6
|
+
from ai_agent.context import (
|
|
7
|
+
AgentContext,
|
|
8
|
+
ChatMessage,
|
|
9
|
+
RunContext,
|
|
10
|
+
RunPhase,
|
|
11
|
+
RunPhaseKind,
|
|
12
|
+
RunStatus,
|
|
13
|
+
ToolInvocation,
|
|
14
|
+
)
|
|
15
|
+
from ai_agent.listener import AgentListener
|
|
16
|
+
from ai_agent.mcp_config import McpConfig, McpStdioServerConfig, parse_mcp_config
|
|
17
|
+
from ai_agent.mcp_loader import MCPToolLoader
|
|
18
|
+
from ai_agent.plan import (
|
|
19
|
+
Plan,
|
|
20
|
+
PlanParseError,
|
|
21
|
+
parse_plan_text,
|
|
22
|
+
PlanPlanner,
|
|
23
|
+
PlanRunResult,
|
|
24
|
+
PlanRunner,
|
|
25
|
+
PlanStep,
|
|
26
|
+
PlanStepFailedError,
|
|
27
|
+
)
|
|
28
|
+
from ai_agent.rule import RuleSet
|
|
29
|
+
from ai_agent.tools import Tool
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"Agent",
|
|
33
|
+
"AgentApp",
|
|
34
|
+
"AgentSession",
|
|
35
|
+
"RunInputPacket",
|
|
36
|
+
"RunOutputPacket",
|
|
37
|
+
"BuiltMemoryContext",
|
|
38
|
+
"Harness",
|
|
39
|
+
"MemoryConfig",
|
|
40
|
+
"MemorySystem",
|
|
41
|
+
"BuiltinToolRegistry",
|
|
42
|
+
"SkillKit",
|
|
43
|
+
"SkillManager",
|
|
44
|
+
"AgentContext",
|
|
45
|
+
"ChatMessage",
|
|
46
|
+
"AgentListener",
|
|
47
|
+
"McpConfig",
|
|
48
|
+
"McpStdioServerConfig",
|
|
49
|
+
"MCPToolLoader",
|
|
50
|
+
"RunContext",
|
|
51
|
+
"RunPhase",
|
|
52
|
+
"RunPhaseKind",
|
|
53
|
+
"RunStatus",
|
|
54
|
+
"Tool",
|
|
55
|
+
"ToolInvocation",
|
|
56
|
+
"parse_mcp_config",
|
|
57
|
+
"RuleSet",
|
|
58
|
+
"Plan",
|
|
59
|
+
"PlanParseError",
|
|
60
|
+
"parse_plan_text",
|
|
61
|
+
"PlanPlanner",
|
|
62
|
+
"PlanRunResult",
|
|
63
|
+
"PlanRunner",
|
|
64
|
+
"PlanStep",
|
|
65
|
+
"PlanStepFailedError",
|
|
66
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from openai import AsyncOpenAI
|
|
7
|
+
|
|
8
|
+
from ai_agent.context import (
|
|
9
|
+
AgentContext,
|
|
10
|
+
ChatMessage,
|
|
11
|
+
RunContext,
|
|
12
|
+
RunPhase,
|
|
13
|
+
RunPhaseKind,
|
|
14
|
+
)
|
|
15
|
+
from ai_agent.listener import AgentListener, normalize_listeners
|
|
16
|
+
from ai_agent.llm import LLMClient
|
|
17
|
+
from ai_agent.llm_openai import OpenAILLM
|
|
18
|
+
from ai_agent.loop import ReactLoop
|
|
19
|
+
from ai_agent.rule import RuleSet
|
|
20
|
+
from ai_agent.tools import Tool, ToolRegistry
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Agent:
|
|
24
|
+
"""
|
|
25
|
+
ReAct 运行入口:构造时组装 ``AgentContext`` 并驱动多步循环。
|
|
26
|
+
|
|
27
|
+
系统提示由构造时的 ``rule_paths`` 指定文件读入;``run`` 只接收消息列表。
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
api_key: str,
|
|
34
|
+
model: str,
|
|
35
|
+
base_url: str,
|
|
36
|
+
temperature: float | None = None,
|
|
37
|
+
max_tokens: int | None = None,
|
|
38
|
+
thinking_enabled: bool = False,
|
|
39
|
+
tools: list[Tool] | ToolRegistry | None = None,
|
|
40
|
+
rule_paths: list[str] | list[Path] | tuple[str, ...] | tuple[Path, ...] | None = None,
|
|
41
|
+
max_steps: int = 20,
|
|
42
|
+
listeners: AgentListener | Iterable[AgentListener] | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
if not api_key.strip():
|
|
45
|
+
raise ValueError("api_key 不能为空")
|
|
46
|
+
if not model.strip():
|
|
47
|
+
raise ValueError("model 不能为空")
|
|
48
|
+
if not base_url.strip():
|
|
49
|
+
raise ValueError("base_url 不能为空")
|
|
50
|
+
|
|
51
|
+
api_key = api_key.strip()
|
|
52
|
+
model = model.strip()
|
|
53
|
+
base_url = base_url.strip().rstrip("/")
|
|
54
|
+
|
|
55
|
+
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
|
|
56
|
+
llm: LLMClient = OpenAILLM(
|
|
57
|
+
client,
|
|
58
|
+
model=model,
|
|
59
|
+
base_url=base_url,
|
|
60
|
+
temperature=temperature,
|
|
61
|
+
max_tokens=max_tokens,
|
|
62
|
+
thinking_enabled=thinking_enabled,
|
|
63
|
+
)
|
|
64
|
+
if isinstance(tools, ToolRegistry):
|
|
65
|
+
registry = tools
|
|
66
|
+
else:
|
|
67
|
+
registry = ToolRegistry(tools)
|
|
68
|
+
self.context = AgentContext(
|
|
69
|
+
llm=llm,
|
|
70
|
+
tools=registry,
|
|
71
|
+
max_steps=max_steps,
|
|
72
|
+
listeners=normalize_listeners(listeners),
|
|
73
|
+
)
|
|
74
|
+
self._rules = RuleSet(rule_paths)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def rules(self) -> RuleSet:
|
|
78
|
+
"""构造时绑定的规则集。"""
|
|
79
|
+
return self._rules
|
|
80
|
+
|
|
81
|
+
async def run(
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
messages: list[ChatMessage],
|
|
85
|
+
) -> str:
|
|
86
|
+
"""
|
|
87
|
+
运行一轮 ReAct 循环,返回最终回答文本。
|
|
88
|
+
"""
|
|
89
|
+
return await self.run_with_system(
|
|
90
|
+
system_prompt=self._rules.build_system_prompt(),
|
|
91
|
+
messages=messages,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def run_with_system(
|
|
95
|
+
self,
|
|
96
|
+
*,
|
|
97
|
+
system_prompt: str,
|
|
98
|
+
messages: list[ChatMessage],
|
|
99
|
+
phase: RunPhase | None = None,
|
|
100
|
+
) -> str:
|
|
101
|
+
"""
|
|
102
|
+
使用给定系统提示运行一轮(供会话层叠加计划步说明等)。
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
system_prompt: 本轮送入模型的完整系统提示
|
|
106
|
+
messages: 用户与助手历史及本轮输入
|
|
107
|
+
"""
|
|
108
|
+
run = RunContext(
|
|
109
|
+
system_prompt=system_prompt,
|
|
110
|
+
messages=list(messages),
|
|
111
|
+
phase=phase or RunPhase(kind=RunPhaseKind.DIRECT),
|
|
112
|
+
)
|
|
113
|
+
if self.context.on_run_begin is not None:
|
|
114
|
+
self.context.on_run_begin(run)
|
|
115
|
+
try:
|
|
116
|
+
loop = ReactLoop(self.context)
|
|
117
|
+
async for updated in loop.run(run):
|
|
118
|
+
run = updated
|
|
119
|
+
return run.output
|
|
120
|
+
finally:
|
|
121
|
+
if self.context.on_run_end is not None:
|
|
122
|
+
self.context.on_run_end(run)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
_SESSIONS_DIR = "sessions"
|
|
7
|
+
SESSION_HARNESS_SUBDIR = "harness"
|
|
8
|
+
SESSION_MEMORY_SUBDIR = "memory"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve_app_sandbox_root(sandbox: Path | str) -> Path:
|
|
12
|
+
"""
|
|
13
|
+
解析并创建总沙箱根目录。
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
sandbox: 总沙箱路径
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
规范化后的绝对路径
|
|
20
|
+
"""
|
|
21
|
+
root = Path(sandbox).expanduser().resolve()
|
|
22
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
if not root.is_dir():
|
|
24
|
+
raise ValueError(f"沙箱根须为目录: {root}")
|
|
25
|
+
return root
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_session_id(session_id: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
校验会话标识,拒绝路径穿越与分隔符。
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
session_id: 调用方提供的会话 id
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
去首尾空白后的 id
|
|
37
|
+
"""
|
|
38
|
+
cleaned = session_id.strip()
|
|
39
|
+
if not cleaned:
|
|
40
|
+
raise ValueError("session_id 不能为空")
|
|
41
|
+
if cleaned in (".", ".."):
|
|
42
|
+
raise ValueError(f"非法 session_id: {session_id}")
|
|
43
|
+
for char in ("/", "\\", "\0"):
|
|
44
|
+
if char in cleaned:
|
|
45
|
+
raise ValueError(f"session_id 不能包含路径分隔符: {session_id}")
|
|
46
|
+
return cleaned
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def session_workspace(sandbox_root: Path, session_id: str) -> Path:
|
|
50
|
+
"""
|
|
51
|
+
在总沙箱下为某会话分配子工作区目录。
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
sandbox_root: 总沙箱根(已 resolve)
|
|
55
|
+
session_id: 会话 id
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
该会话专属子目录(已创建)
|
|
59
|
+
"""
|
|
60
|
+
label = validate_session_id(session_id)
|
|
61
|
+
target = (sandbox_root / _SESSIONS_DIR / label).resolve()
|
|
62
|
+
try:
|
|
63
|
+
target.relative_to(sandbox_root)
|
|
64
|
+
except ValueError as exc:
|
|
65
|
+
raise ValueError(f"会话工作区越出总沙箱: {session_id}") from exc
|
|
66
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
if not target.is_dir():
|
|
68
|
+
raise ValueError(f"无法创建会话工作区: {session_id}")
|
|
69
|
+
return target
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _validate_session_subdir_name(name: str, *, label: str) -> str:
|
|
73
|
+
cleaned = name.strip()
|
|
74
|
+
if not cleaned:
|
|
75
|
+
raise ValueError(f"{label} 不能为空")
|
|
76
|
+
if cleaned in (".", ".."):
|
|
77
|
+
raise ValueError(f"非法 {label}: {name}")
|
|
78
|
+
for char in ("/", "\\", "\0"):
|
|
79
|
+
if char in cleaned:
|
|
80
|
+
raise ValueError(f"{label} 不能包含路径分隔符: {name}")
|
|
81
|
+
return cleaned
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def session_harness_workspace(session_root: Path) -> Path:
|
|
85
|
+
"""
|
|
86
|
+
在会话工作区下分配 Harness 隔离子目录。
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
session_root: 会话根目录(已 resolve)
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Harness 工作区路径(已创建)
|
|
93
|
+
"""
|
|
94
|
+
root = session_root.resolve()
|
|
95
|
+
sub = _validate_session_subdir_name(
|
|
96
|
+
SESSION_HARNESS_SUBDIR,
|
|
97
|
+
label="harness 子目录",
|
|
98
|
+
)
|
|
99
|
+
target = (root / sub).resolve()
|
|
100
|
+
try:
|
|
101
|
+
target.relative_to(root)
|
|
102
|
+
except ValueError as exc:
|
|
103
|
+
raise ValueError("Harness 子目录越出会话工作区") from exc
|
|
104
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
if not target.is_dir():
|
|
106
|
+
raise ValueError("无法创建 Harness 工作区")
|
|
107
|
+
return target
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def reset_session_subdirs(session_root: Path) -> None:
|
|
111
|
+
"""
|
|
112
|
+
清空会话内 Harness 与 memory 子目录并重建。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
session_root: 会话根目录
|
|
116
|
+
"""
|
|
117
|
+
root = session_root.resolve()
|
|
118
|
+
for sub_name in (SESSION_HARNESS_SUBDIR, SESSION_MEMORY_SUBDIR):
|
|
119
|
+
sub = _validate_session_subdir_name(sub_name, label="子目录")
|
|
120
|
+
target = (root / sub).resolve()
|
|
121
|
+
try:
|
|
122
|
+
target.relative_to(root)
|
|
123
|
+
except ValueError as exc:
|
|
124
|
+
raise ValueError(f"子目录越出会话工作区: {sub_name}") from exc
|
|
125
|
+
if target.is_dir():
|
|
126
|
+
shutil.rmtree(target)
|
|
127
|
+
target.mkdir(parents=True, exist_ok=True)
|