fp-core 0.1.1.dev0__tar.gz → 0.1.3__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.
Files changed (66) hide show
  1. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/PKG-INFO +1 -1
  2. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/README.md +0 -0
  3. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/pyproject.toml +0 -0
  4. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/setup.cfg +0 -0
  5. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/__init__.py +1 -1
  6. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/__init__.py +3 -3
  7. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/back.py +0 -0
  8. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/clear.py +0 -0
  9. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/compact.py +0 -0
  10. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/exit_bang.py +0 -0
  11. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/exit_cmd.py +0 -0
  12. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/fork.py +0 -0
  13. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/help.py +0 -0
  14. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/history.py +0 -0
  15. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/memory_cmd.py +0 -0
  16. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/model.py +0 -0
  17. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/resume.py +0 -0
  18. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/commands/session.py +0 -0
  19. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/config.py +16 -11
  20. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/__init__.py +0 -0
  21. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/agent.py +3 -3
  22. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/conversation.py +0 -0
  23. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/io.py +0 -0
  24. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/lifecycle.py +0 -0
  25. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/llm_client.py +0 -0
  26. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/llm_service.py +0 -0
  27. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/prompt_builder.py +17 -6
  28. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/session.py +0 -0
  29. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/core/tool_executor.py +0 -0
  30. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/display.py +0 -0
  31. fp_core-0.1.3/src/fp_core/platform_utils.py +175 -0
  32. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/plugins/base/__init__.py +0 -0
  33. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/plugins/base/plugin.py +0 -0
  34. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/prompts/__init__.py +0 -0
  35. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/prompts/agent.md +13 -0
  36. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/prompts/agent.py +0 -0
  37. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/skills/__init__.py +0 -0
  38. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/skills/loader.py +4 -3
  39. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/skills/modify_skills.md +0 -0
  40. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/skills/python_syntax_check.md +0 -0
  41. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/skills/self_modification.md +0 -0
  42. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/skills/subagent.md +0 -0
  43. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/__init__.py +4 -3
  44. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/core.py +47 -9
  45. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/__init__.py +0 -0
  46. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/memory_read_plugin.py +0 -0
  47. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/memory_save_plugin.py +0 -0
  48. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/python_plugin.py +2 -1
  49. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/subagent_plugin.py +1 -1
  50. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/task_clear_plugin.py +0 -0
  51. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/task_create_plugin.py +0 -0
  52. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/task_list_plugin.py +0 -0
  53. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/task_update_plugin.py +0 -0
  54. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/web_fetch_plugin.py +0 -0
  55. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core/tools/plugins/web_search_plugin.py +0 -0
  56. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core.egg-info/PKG-INFO +1 -1
  57. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core.egg-info/SOURCES.txt +1 -0
  58. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core.egg-info/dependency_links.txt +0 -0
  59. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core.egg-info/requires.txt +0 -0
  60. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/src/fp_core.egg-info/top_level.txt +0 -0
  61. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/tests/__init__.py +0 -0
  62. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/tests/conftest.py +0 -0
  63. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/tests/test_config.py +0 -0
  64. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/tests/test_conversation.py +0 -0
  65. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/tests/test_session.py +0 -0
  66. {fp_core-0.1.1.dev0 → fp_core-0.1.3}/tests/test_skills.py +2 -2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fp-core
3
- Version: 0.1.1.dev0
3
+ Version: 0.1.3
4
4
  Summary: 五块卵石 Agent 引擎 - 基于生命周期钩子的插件化 Agent 核心
5
5
  Author: zpb
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  """fp-core - 五块卵石 Agent 引擎"""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.2"
4
4
  __license__ = "MIT"
5
5
  __author__ = "zpb"
6
6
 
@@ -11,6 +11,7 @@ import importlib.util
11
11
  import os
12
12
 
13
13
  from fp_core import display
14
+ from fp_core.platform_utils import get_data_dir
14
15
 
15
16
  # 缓存:命令名 → 模块对象
16
17
  _commands: dict[str, "CommandModule"] = {}
@@ -36,9 +37,8 @@ def _discover_commands():
36
37
  builtin_dir = os.path.dirname(os.path.abspath(__file__))
37
38
  _scan_dir(builtin_dir, "fp_core.commands")
38
39
 
39
- # 用户命令目录
40
- _xdg_data = os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
41
- user_dir = os.path.join(_xdg_data, "fp", "commands")
40
+ # 用户命令目录(跨平台:Linux ~/.local/share/fp/commands, Windows %LOCALAPPDATA%/fp/commands)
41
+ user_dir = os.path.join(get_data_dir(), "commands")
42
42
  _scan_dir(user_dir) # 直接 import 路径,通过 sys.path 解析
43
43
 
44
44
 
@@ -8,9 +8,10 @@ import os
8
8
  import sys
9
9
  from typing import Any
10
10
 
11
- # 用户配置(XDG 标准)
12
- _XDG_CONFIG_HOME = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config"))
13
- USER_CONFIG_PATH = os.path.join(_XDG_CONFIG_HOME, "fp", "config.json")
11
+ from fp_core.platform_utils import get_config_dir, get_data_dir, is_windows
12
+
13
+ # 用户配置(跨平台:Linux XDG 标准 / Windows %APPDATA%)
14
+ USER_CONFIG_PATH = os.path.join(get_config_dir(), "config.json")
14
15
 
15
16
 
16
17
  def _load_json_config() -> dict:
@@ -147,15 +148,14 @@ MEMORY_MAX_HISTORY: int = _value("MEMORY_MAX_HISTORY", 100)
147
148
 
148
149
 
149
150
  # ═══════════════════════════════════════════════════════════════
150
- # 路径配置(XDG 标准)
151
+ # 路径配置(跨平台:Linux XDG 标准 / Windows %APPDATA%)
151
152
  # ═══════════════════════════════════════════════════════════════
152
153
 
153
- _XDG_DATA_HOME = os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
154
- _XDG_DATA_FP = os.path.join(_XDG_DATA_HOME, "fp")
154
+ _FP_DATA_DIR = get_data_dir()
155
155
 
156
- SESSIONS_DIR = os.path.join(_XDG_DATA_FP, "sessions")
157
- MEMORY_DIR = os.path.join(_XDG_DATA_FP, "memory")
158
- TASKS_FILE = os.path.join(_XDG_DATA_FP, "tasks.json")
156
+ SESSIONS_DIR = os.path.join(_FP_DATA_DIR, "sessions")
157
+ MEMORY_DIR = os.path.join(_FP_DATA_DIR, "memory")
158
+ TASKS_FILE = os.path.join(_FP_DATA_DIR, "tasks.json")
159
159
  PROMPTS_DIR = os.path.join(os.path.dirname(__file__), "prompts")
160
160
  SKILLS_DIR = os.path.join(os.path.dirname(__file__), "skills")
161
161
 
@@ -200,7 +200,12 @@ def color_supported() -> bool:
200
200
  return True
201
201
  if os.environ.get("NO_COLOR"):
202
202
  return False
203
- import sys
203
+
204
+ if is_windows():
205
+ # Windows 上委托 platform_utils 做更精细的检测
206
+ from fp_core.platform_utils import ansi_supported
207
+
208
+ return ansi_supported()
204
209
 
205
210
  return sys.stdout.isatty()
206
211
 
@@ -301,7 +306,7 @@ def get_default_config() -> dict:
301
306
 
302
307
 
303
308
  def init_config(path: str | None = None):
304
- """初始化用户配置文件(写入 ~/.config/fp/config.json)"""
309
+ """初始化用户配置文件(跨平台:Linux ~/.config/fp/,Windows %APPDATA%/fp/)"""
305
310
  config_path = path or USER_CONFIG_PATH
306
311
  if os.path.exists(config_path):
307
312
  print(f"[Config] {config_path} already exists")
@@ -27,6 +27,7 @@ from fp_core.core.lifecycle import HookContext, LifecycleHook, LifecycleManager
27
27
  from fp_core.core.llm_service import LLMConfig, LLMService
28
28
  from fp_core.core.prompt_builder import PromptBuilder
29
29
  from fp_core.core.tool_executor import ToolExecutor
30
+ from fp_core.platform_utils import get_data_dir
30
31
  from fp_core.plugins.base.plugin import PluginRegistry
31
32
 
32
33
  # ── 上下文 local IO 通道(防并发竞态) ─────────────────
@@ -143,9 +144,8 @@ class Agent:
143
144
  plugin_dir=os.path.normpath(_builtin_plugin_dir),
144
145
  )
145
146
 
146
- # 用户插件目录(XDG,同名覆盖)
147
- _xdg_data = os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
148
- _user_plugin_dir = os.path.join(_xdg_data, "fp", "plugins")
147
+ # 用户插件目录(跨平台,同名覆盖)
148
+ _user_plugin_dir = os.path.join(get_data_dir(), "plugins")
149
149
  if os.path.isdir(_user_plugin_dir):
150
150
  self.plugins.scan(_user_plugin_dir)
151
151
 
@@ -11,6 +11,8 @@ import os
11
11
  from datetime import datetime
12
12
  from typing import Any
13
13
 
14
+ from fp_core.platform_utils import check_git_bash, is_windows, platform
15
+
14
16
 
15
17
  class PromptBuilder:
16
18
  """系统提示词构建器"""
@@ -55,12 +57,21 @@ class PromptBuilder:
55
57
  except Exception:
56
58
  current_user = "unknown"
57
59
 
58
- state_info = f"""
59
- ## 当前时间,路径等状态信息
60
- 当前时间: {current_time}
61
- 当前路径: {current_path}
62
- 当前用户: {current_user}
63
- """
60
+ # ── 运行时环境信息 ──
61
+ runtime_parts = [
62
+ f"当前时间: {current_time}",
63
+ f"当前路径: {current_path}",
64
+ f"当前用户: {current_user}",
65
+ f"当前平台: {platform()}",
66
+ ]
67
+ if is_windows():
68
+ git_ok, git_msg = check_git_bash()
69
+ if git_ok:
70
+ runtime_parts.append("Git Bash: 可用 ✓(写 Unix 命令:ls/grep/awk)")
71
+ else:
72
+ runtime_parts.append("Git Bash: 不可用 ✗(使用 cmd.exe 回退,写 Windows 命令:dir/type/findstr)")
73
+ runtime_info = "\n".join(runtime_parts)
74
+ state_info = f"\n## 当前时间,路径等状态信息\n{runtime_info}\n"
64
75
  parts.append(state_info)
65
76
 
66
77
  # 长期记忆索引(name + type 摘要,不含正文)
@@ -0,0 +1,175 @@
1
+ """
2
+ platform_utils.py - 跨平台工具集
3
+
4
+ 单一真相源(Single Source of Truth),所有模块都通过此模块获取:
5
+ - 平台类型(linux / windows / darwin)
6
+ - 数据目录(XDG_DATA_HOME ~/.local/share <-> %APPDATA%)
7
+ - 配置目录(XDG_CONFIG_HOME ~/.config <-> %APPDATA%)
8
+ - Git Bash 定位(Windows 专用)
9
+ """
10
+
11
+ import os
12
+ import shutil
13
+ import sys
14
+
15
+ # ═══════════════════════════════════════════════════════════════
16
+ # 平台检测
17
+ # ═══════════════════════════════════════════════════════════════
18
+
19
+
20
+ def platform() -> str:
21
+ """返回标准平台名:linux / windows / darwin"""
22
+ if sys.platform == "win32":
23
+ return "windows"
24
+ if sys.platform == "darwin":
25
+ return "darwin"
26
+ return "linux"
27
+
28
+
29
+ def is_windows() -> bool:
30
+ """是否为 Windows 系统"""
31
+ return sys.platform == "win32"
32
+
33
+
34
+ def is_linux() -> bool:
35
+ """是否为 Linux 系统"""
36
+ return sys.platform == "linux"
37
+
38
+
39
+ # ═══════════════════════════════════════════════════════════════
40
+ # 路径工具(跨平台 XDG 等价物)
41
+ # ═══════════════════════════════════════════════════════════════
42
+
43
+ # 参考规范:
44
+ # Linux: XDG_CONFIG_HOME (默认 ~/.config)
45
+ # XDG_DATA_HOME (默认 ~/.local/share)
46
+ # Windows: APPDATA (C:/Users/<user>/AppData/Roaming)
47
+ # LOCALAPPDATA (C:/Users/<user>/AppData/Local)
48
+ # macOS: ~/Library/Application Support
49
+
50
+
51
+ def get_config_dir() -> str:
52
+ """获取跨平台用户配置目录
53
+
54
+ Linux: $XDG_CONFIG_HOME/fp -> ~/.config/fp
55
+ Windows: %APPDATA%/fp -> C:/Users/<user>/AppData/Roaming/fp
56
+ macOS: ~/Library/Application Support/fp
57
+ """
58
+ if is_windows():
59
+ base = os.environ.get("APPDATA", os.path.join(os.path.expanduser("~"), "AppData", "Roaming"))
60
+ elif sys.platform == "darwin":
61
+ base = os.path.join(os.path.expanduser("~"), "Library", "Application Support")
62
+ else:
63
+ base = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config"))
64
+ return os.path.join(base, "fp")
65
+
66
+
67
+ def get_data_dir() -> str:
68
+ """获取跨平台用户数据目录
69
+
70
+ Linux: $XDG_DATA_HOME/fp -> ~/.local/share/fp
71
+ Windows: %LOCALAPPDATA%/fp -> C:/Users/<user>/AppData/Local/fp
72
+ macOS: ~/Library/Application Support/fp
73
+ """
74
+ if is_windows():
75
+ base = os.environ.get("LOCALAPPDATA", os.path.join(os.path.expanduser("~"), "AppData", "Local"))
76
+ elif sys.platform == "darwin":
77
+ base = os.path.join(os.path.expanduser("~"), "Library", "Application Support")
78
+ else:
79
+ base = os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
80
+ return os.path.join(base, "fp")
81
+
82
+
83
+ # ═══════════════════════════════════════════════════════════════
84
+ # Git Bash 定位(Windows)
85
+ # ═══════════════════════════════════════════════════════════════
86
+
87
+
88
+ def find_bash() -> str | None:
89
+ """在 Windows 上找到 Git Bash 的 bash.exe,找不到返回 None
90
+
91
+ 搜索路径优先级:
92
+ 1. PATH 中的 bash(Git Bash 安装时默认添加)
93
+ 2. 常见安装路径
94
+ 3. Git for Windows SDK 路径
95
+
96
+ Linux/macOS 上始终返回 "bash"(系统自带)。
97
+ """
98
+ if not is_windows():
99
+ return "bash" # Unix 系统直接用系统 bash
100
+
101
+ # 1. PATH 中查找
102
+ resolved = shutil.which("bash")
103
+ if resolved:
104
+ return resolved
105
+
106
+ # 2. 常见安装路径
107
+ common_paths = [
108
+ os.path.join("C:", os.sep, "Program Files", "Git", "bin", "bash.exe"),
109
+ os.path.join("C:", os.sep, "Program Files (x86)", "Git", "bin", "bash.exe"),
110
+ os.path.join(os.path.expanduser("~"), "AppData", "Local", "Programs", "Git", "bin", "bash.exe"),
111
+ # MSYS2
112
+ os.path.join("C:", os.sep, "msys64", "usr", "bin", "bash.exe"),
113
+ os.path.join("C:", os.sep, "msys32", "usr", "bin", "bash.exe"),
114
+ ]
115
+ for path in common_paths:
116
+ if os.path.isfile(path):
117
+ return path
118
+
119
+ return None
120
+
121
+
122
+ def check_git_bash() -> tuple[bool, str]:
123
+ """检查 Git Bash 是否可用(Windows 专用),返回 (可用?, 消息)
124
+
125
+ Linux/macOS 上始终返回 (True, "系统自带 bash")。
126
+ """
127
+ if not is_windows():
128
+ return True, "系统自带 bash"
129
+
130
+ bash_path = find_bash()
131
+ if bash_path:
132
+ return True, f"已找到 Git Bash: {bash_path}"
133
+ return False, "未找到 Git Bash。请安装 Git for Windows (https://git-scm.com) 并确保将 Git Bash 添加到 PATH"
134
+
135
+
136
+ # ═══════════════════════════════════════════════════════════════
137
+ # ANSI 颜色支持检测
138
+ # ═══════════════════════════════════════════════════════════════
139
+
140
+
141
+ def ansi_supported() -> bool:
142
+ """检测终端是否支持 ANSI 转义码
143
+
144
+ Linux/macOS: 始终 True(终端原生支持)
145
+ Windows: Windows 10+ 的 Windows Terminal / VS Code 集成终端 支持;
146
+ cmd.exe 仅在 10+ 版本 + ENABLE_VIRTUAL_TERMINAL_PROCESSING 启用时支持
147
+ """
148
+ # 强制开关优先
149
+ if os.environ.get("FORCE_COLOR"):
150
+ return True
151
+ if os.environ.get("NO_COLOR"):
152
+ return False
153
+
154
+ # 非交互式终端不支持
155
+ if not sys.stdout.isatty():
156
+ return False
157
+
158
+ if is_windows():
159
+ # Windows 10 build 14393+ 通过虚拟终端处理支持 ANSI
160
+ # 但我们保守检测:如果在 Windows Terminal 或 VS Code 终端中,返回 True
161
+ term = os.environ.get("TERM_PROGRAM", "").lower()
162
+ if term in ("vscode", "microsoft-terminal", "windows-terminal"):
163
+ return True
164
+ # 检查 ConEmu / Cmder
165
+ if os.environ.get("CONEMUANSI") == "ON":
166
+ return True
167
+ # 兜底:使用 colorama 的 stream 检测
168
+ try:
169
+ import colorama.ansitowin32 # type: ignore[import-untyped]
170
+
171
+ return colorama.ansitowin32.is_ansi_enabled()
172
+ except ImportError:
173
+ return False
174
+
175
+ return True
@@ -45,3 +45,16 @@
45
45
  - 开始执行任务时,请用 task_update 将状态改为 in_progress
46
46
  - 任务完成后,请用 task_update 将状态改为 completed
47
47
  - 在回复中引用任务时带上 #ID 方便用户追踪
48
+
49
+ 【跨平台兼容】
50
+ - 我同时支持 Linux 和 Windows。
51
+ - **bash 工具**:
52
+ - Linux/macOS:直接由系统 shell 执行,写 Unix 命令(`ls`/`grep`/`cat`)
53
+ - Windows + Git Bash 可用:自动路由到 Git Bash 的 `bash.exe`,写 Unix 命令(`ls`/`grep`/`awk`)
54
+ - Windows + 无 Git Bash:降级到 `cmd.exe`,**写 Windows 命令**(`dir`/`type`/`findstr`)
55
+ - 运行时环境信息(平台 + Git Bash 状态)会自动注入到提示词的状态信息区,注意查看。
56
+ - **路径兼容**:配置/会话/记忆等数据目录自动适配——
57
+ - Linux: `~/.config/fp/` + `~/.local/share/fp/`
58
+ - Windows: `%APPDATA%/fp/` + `%LOCALAPPDATA%/fp/`
59
+ - **KDE 专属技能**(KDE Connect 通知/壁纸等)仅在 Linux/KDE 桌面环境可用,Windows 上调用会返回不可用提示。
60
+ - **ANSI 颜色**:Windows Terminal / VS Code 终端原生支持;旧版 cmd.exe 可能禁用颜色输出。
@@ -7,11 +7,12 @@ import os
7
7
  import re
8
8
  from dataclasses import dataclass, field
9
9
 
10
+ from fp_core.platform_utils import get_data_dir
11
+
10
12
  BUILTIN_SKILLS_DIR = os.path.dirname(os.path.abspath(__file__))
11
13
 
12
- # 用户技能目录(XDG 标准,可写,同名技能覆盖内置)
13
- _XDG_DATA_HOME = os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
14
- USER_SKILLS_DIR = os.path.join(_XDG_DATA_HOME, "fp", "skills")
14
+ # 用户技能目录(跨平台:Linux ~/.local/share/fp/skills, Windows %LOCALAPPDATA%/fp/skills, 同名技能覆盖内置)
15
+ USER_SKILLS_DIR = os.path.join(get_data_dir(), "skills")
15
16
 
16
17
  # 模板变量:agent 主入口文件路径(用于自修改等技能)
17
18
  AGENT_ENTRY = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "cli.py")
@@ -18,6 +18,8 @@ import os
18
18
  from collections.abc import Callable
19
19
  from typing import Any
20
20
 
21
+ from fp_core.platform_utils import get_data_dir
22
+
21
23
 
22
24
  class ToolRegistry:
23
25
  """工具注册表,管理所有核心工具和插件"""
@@ -41,9 +43,8 @@ class ToolRegistry:
41
43
  builtin_dir = os.path.join(os.path.dirname(__file__), "plugins")
42
44
  self._load_from_dir(builtin_dir, "fp_core.tools.plugins")
43
45
 
44
- # 用户工具目录
45
- _xdg_data = os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
46
- user_dir = os.path.join(_xdg_data, "fp", "tools", "plugins")
46
+ # 用户工具目录(跨平台:Linux ~/.local/share/fp/tools/plugins, Windows %LOCALAPPDATA%/fp/tools/plugins)
47
+ user_dir = os.path.join(get_data_dir(), "tools", "plugins")
47
48
  self._load_from_dir(user_dir)
48
49
 
49
50
  def _load_from_dir(self, directory: str, package_prefix: str | None = None):
@@ -2,7 +2,7 @@
2
2
  核心工具模块 — 不可插件化的基础工具(全异步版本)
3
3
 
4
4
  这些工具必须保持直接绑定,作为系统的基础设施:
5
- - bash: Shell 命令执行(使用 asyncio.create_subprocess_shell
5
+ - bash: Shell 命令执行(跨平台:Linux 用 bash,Windows 用 Git Bash
6
6
  - read_file: 文件读取
7
7
  - write_file: 文件写入
8
8
  - edit_file: 精确文本替换
@@ -12,6 +12,8 @@ import asyncio
12
12
  import os
13
13
  from typing import Any
14
14
 
15
+ from fp_core.platform_utils import find_bash, is_windows
16
+
15
17
  # ── 核心工具定义(OpenAI function calling schema) ──────────────────────
16
18
 
17
19
  CORE_TOOL_DEFINITIONS = [
@@ -19,7 +21,11 @@ CORE_TOOL_DEFINITIONS = [
19
21
  "type": "function",
20
22
  "function": {
21
23
  "name": "bash",
22
- "description": "执行一条 shell 命令并获取输出。支持任意 bash 语法,可管道/重定向。超时 300 秒。",
24
+ "description": (
25
+ "执行 shell 命令,支持管道/重定向。"
26
+ "跨平台:Linux 原生执行,Windows 自动路由到 Git Bash 或降级 cmd.exe。"
27
+ "超时 300 秒。"
28
+ ),
23
29
  "parameters": {
24
30
  "type": "object",
25
31
  "properties": {
@@ -85,16 +91,45 @@ def get_core_definitions() -> list:
85
91
 
86
92
 
87
93
  async def _execute_bash(command: str) -> str:
88
- """异步执行 shell 命令"""
94
+ """异步执行 shell 命令
95
+
96
+ 跨平台策略:
97
+ - Linux/macOS: 使用 asyncio.create_subprocess_shell,由系统 shell 执行
98
+ - Windows + Git Bash: 使用 bash.exe -c,Unix 命令(ls/grep/awk)可用
99
+ - Windows + 无 Git Bash: 降级到 cmd.exe /c,Windows 命令(dir/type/findstr)可用
100
+ """
89
101
  if not command:
90
102
  raise ValueError("bash 工具需要 command 参数")
91
103
 
104
+ # ── 跨平台路由:选择正确的 shell 执行方式 ──
105
+ cmd_prefix = "" # 输出前缀,cmd.exe 回退时标记
92
106
  try:
93
- proc = await asyncio.create_subprocess_shell(
94
- command,
95
- stdout=asyncio.subprocess.PIPE,
96
- stderr=asyncio.subprocess.PIPE,
97
- )
107
+ if is_windows():
108
+ bash_path = find_bash()
109
+ if bash_path:
110
+ # Git Bash 可用 → 走 bash.exe,Unix 命令
111
+ proc = await asyncio.create_subprocess_exec(
112
+ bash_path,
113
+ "-c",
114
+ command,
115
+ stdout=asyncio.subprocess.PIPE,
116
+ stderr=asyncio.subprocess.PIPE,
117
+ )
118
+ else:
119
+ # Git Bash 不可用 → 降级到 cmd.exe,Windows 命令
120
+ proc = await asyncio.create_subprocess_shell(
121
+ command,
122
+ stdout=asyncio.subprocess.PIPE,
123
+ stderr=asyncio.subprocess.PIPE,
124
+ )
125
+ cmd_prefix = "[cmd.exe 回退] "
126
+ else:
127
+ # Linux/macOS → 系统 shell
128
+ proc = await asyncio.create_subprocess_shell(
129
+ command,
130
+ stdout=asyncio.subprocess.PIPE,
131
+ stderr=asyncio.subprocess.PIPE,
132
+ )
98
133
 
99
134
  try:
100
135
  stdout, stderr = await asyncio.wait_for(
@@ -106,7 +141,6 @@ async def _execute_bash(command: str) -> str:
106
141
  await proc.wait()
107
142
  return "错误:命令执行超时(300秒)"
108
143
  except (KeyboardInterrupt, asyncio.CancelledError):
109
- # 中断时 kill 子进程并重新抛出
110
144
  proc.kill()
111
145
  await proc.wait()
112
146
  raise
@@ -121,6 +155,10 @@ async def _execute_bash(command: str) -> str:
121
155
  if len(output) > 10000:
122
156
  output = output[:10000] + f"\n...(已截断,原文 {len(output)} 字符)"
123
157
 
158
+ # cmd.exe 回退模式下加前缀告知 LLM
159
+ if cmd_prefix and output.strip():
160
+ output = cmd_prefix + output.lstrip()
161
+
124
162
  return output
125
163
  except Exception as e:
126
164
  return f"错误:{e}"
@@ -7,6 +7,7 @@ Python 插件 — 执行 Python 代码(异步版本)
7
7
  import asyncio
8
8
  import contextlib
9
9
  import os
10
+ import sys
10
11
  import tempfile
11
12
  from typing import Any
12
13
 
@@ -63,7 +64,7 @@ async def execute(params: dict[str, Any]) -> str:
63
64
 
64
65
  # 异步执行
65
66
  proc = await asyncio.create_subprocess_exec(
66
- "python3",
67
+ sys.executable,
67
68
  tmp_path,
68
69
  stdout=asyncio.subprocess.PIPE,
69
70
  stderr=asyncio.subprocess.PIPE,
@@ -181,7 +181,7 @@ async def execute(params: dict[str, Any]) -> str:
181
181
 
182
182
  # ═══════════════════════════════════════════════════════════
183
183
  # 定位入口 — 通过 python -m fp_cli.main 启动子进程
184
- # (不依赖文件系统路径,fp-cli 必须已安装)
184
+ # (不依赖文件系统路径,fp-terminal 必须已安装)
185
185
  # ═══════════════════════════════════════════════════════════
186
186
  entry = [sys.executable, "-m", "fp_cli.main", "-m"]
187
187
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fp-core
3
- Version: 0.1.1.dev0
3
+ Version: 0.1.3
4
4
  Summary: 五块卵石 Agent 引擎 - 基于生命周期钩子的插件化 Agent 核心
5
5
  Author: zpb
6
6
  License-Expression: MIT
@@ -3,6 +3,7 @@ pyproject.toml
3
3
  src/fp_core/__init__.py
4
4
  src/fp_core/config.py
5
5
  src/fp_core/display.py
6
+ src/fp_core/platform_utils.py
6
7
  src/fp_core.egg-info/PKG-INFO
7
8
  src/fp_core.egg-info/SOURCES.txt
8
9
  src/fp_core.egg-info/dependency_links.txt
File without changes
File without changes
@@ -106,9 +106,9 @@ class TestSkillLoader:
106
106
  )
107
107
 
108
108
  # 创建同名用户技能
109
- import fp_core.config as cfg
109
+ from fp_core.skills.loader import USER_SKILLS_DIR
110
110
 
111
- user_dir = os.path.join(cfg._XDG_DATA_HOME, "fp", "skills")
111
+ user_dir = USER_SKILLS_DIR
112
112
  os.makedirs(user_dir, exist_ok=True)
113
113
  user_file = os.path.join(user_dir, f"{builtin_name}.md")
114
114
  with open(user_file, "w", encoding="utf-8") as f: