fp-core 0.1.0__py3-none-any.whl

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 (49) hide show
  1. fp_core/__init__.py +21 -0
  2. fp_core/commands/__init__.py +132 -0
  3. fp_core/commands/back.py +73 -0
  4. fp_core/commands/clear.py +14 -0
  5. fp_core/commands/compact.py +10 -0
  6. fp_core/commands/exit_bang.py +27 -0
  7. fp_core/commands/exit_cmd.py +9 -0
  8. fp_core/commands/fork.py +16 -0
  9. fp_core/commands/help.py +25 -0
  10. fp_core/commands/history.py +32 -0
  11. fp_core/commands/memory_cmd.py +30 -0
  12. fp_core/commands/model.py +24 -0
  13. fp_core/commands/resume.py +166 -0
  14. fp_core/commands/session.py +13 -0
  15. fp_core/config.py +329 -0
  16. fp_core/core/__init__.py +0 -0
  17. fp_core/core/agent.py +762 -0
  18. fp_core/core/conversation.py +355 -0
  19. fp_core/core/io.py +180 -0
  20. fp_core/core/lifecycle.py +342 -0
  21. fp_core/core/llm_client.py +228 -0
  22. fp_core/core/llm_service.py +109 -0
  23. fp_core/core/prompt_builder.py +123 -0
  24. fp_core/core/session.py +369 -0
  25. fp_core/core/tool_executor.py +59 -0
  26. fp_core/display.py +366 -0
  27. fp_core/plugins/base/__init__.py +0 -0
  28. fp_core/plugins/base/plugin.py +273 -0
  29. fp_core/prompts/__init__.py +5 -0
  30. fp_core/prompts/agent.py +29 -0
  31. fp_core/skills/__init__.py +5 -0
  32. fp_core/skills/loader.py +209 -0
  33. fp_core/tools/__init__.py +216 -0
  34. fp_core/tools/core.py +239 -0
  35. fp_core/tools/plugins/__init__.py +0 -0
  36. fp_core/tools/plugins/memory_read_plugin.py +138 -0
  37. fp_core/tools/plugins/memory_save_plugin.py +88 -0
  38. fp_core/tools/plugins/python_plugin.py +106 -0
  39. fp_core/tools/plugins/subagent_plugin.py +320 -0
  40. fp_core/tools/plugins/task_clear_plugin.py +59 -0
  41. fp_core/tools/plugins/task_create_plugin.py +73 -0
  42. fp_core/tools/plugins/task_list_plugin.py +68 -0
  43. fp_core/tools/plugins/task_update_plugin.py +78 -0
  44. fp_core/tools/plugins/web_fetch_plugin.py +71 -0
  45. fp_core/tools/plugins/web_search_plugin.py +255 -0
  46. fp_core-0.1.0.dist-info/METADATA +188 -0
  47. fp_core-0.1.0.dist-info/RECORD +49 -0
  48. fp_core-0.1.0.dist-info/WHEEL +5 -0
  49. fp_core-0.1.0.dist-info/top_level.txt +1 -0
fp_core/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """fp-core - 五块卵石 Agent 引擎"""
2
+
3
+ __version__ = "0.1.0"
4
+ __license__ = "MIT"
5
+ __author__ = "zpb"
6
+
7
+ from fp_core.core.agent import Agent, Message, Response
8
+ from fp_core.core.lifecycle import HookContext, LifecycleHook, LifecycleManager
9
+ from fp_core.plugins.base.plugin import Plugin, PluginConfig, PluginRegistry
10
+
11
+ __all__ = [
12
+ "Agent",
13
+ "Message",
14
+ "Response",
15
+ "LifecycleManager",
16
+ "LifecycleHook",
17
+ "HookContext",
18
+ "Plugin",
19
+ "PluginConfig",
20
+ "PluginRegistry",
21
+ ]
@@ -0,0 +1,132 @@
1
+ """
2
+ commands/__init__.py — 命令注册表与自动发现
3
+
4
+ 自动扫描 commands/ 目录下所有 .py 文件(排除 __init__.py),
5
+ 导入每个模块并检查 name/execute 接口,构建命令名→模块的映射(含别名)。
6
+ """
7
+
8
+ import asyncio
9
+ import importlib
10
+ import importlib.util
11
+ import os
12
+
13
+ from fp_core import display
14
+
15
+ # 缓存:命令名 → 模块对象
16
+ _commands: dict[str, "CommandModule"] = {}
17
+
18
+
19
+ # 类型标注
20
+ class CommandModule:
21
+ name: str
22
+ aliases: list[str]
23
+ description: str
24
+
25
+ # execute 返回 (已处理, 输出文本);
26
+ # 兼容旧版:也可只返回 bool(自动转为 ("", False/True))
27
+ # 同步或异步均可,由 execute() 自动适配
28
+ async def execute(self, arg: str) -> tuple[bool, str]: ...
29
+
30
+
31
+ def _discover_commands():
32
+ """扫描并注册所有命令模块(内置 → 用户,同名覆盖)"""
33
+ global _commands
34
+ _commands = {}
35
+
36
+ builtin_dir = os.path.dirname(os.path.abspath(__file__))
37
+ _scan_dir(builtin_dir, "fp_core.commands")
38
+
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")
42
+ _scan_dir(user_dir) # 直接 import 路径,通过 sys.path 解析
43
+
44
+
45
+ def _scan_dir(directory: str, package_prefix: str | None = None):
46
+ """扫描单个目录下的命令文件"""
47
+ if not os.path.isdir(directory):
48
+ return
49
+
50
+ for fname in sorted(os.listdir(directory)):
51
+ if not fname.endswith(".py") or fname == "__init__.py":
52
+ continue
53
+
54
+ mod_name = fname[:-3]
55
+ try:
56
+ if package_prefix:
57
+ mod = importlib.import_module(f"{package_prefix}.{mod_name}")
58
+ else:
59
+ spec = importlib.util.spec_from_file_location(mod_name, os.path.join(directory, fname))
60
+ if spec is None or spec.loader is None:
61
+ continue
62
+ mod = importlib.util.module_from_spec(spec)
63
+ spec.loader.exec_module(mod)
64
+ except Exception as e:
65
+ display.warning(f"⚠️ 命令加载失败 [{mod_name}]: {e}")
66
+ continue
67
+
68
+ # 校验接口
69
+ if not hasattr(mod, "name") or not hasattr(mod, "execute"):
70
+ display.warning(f"⚠️ 命令模块 [{mod_name}] 缺少 name/execute,已跳过")
71
+ continue
72
+
73
+ name = mod.name
74
+ if name in _commands:
75
+ display.warning(f"⚠️ 命令 [{name}] 重复定义,已覆盖")
76
+ _commands[name] = mod
77
+
78
+ # 注册别名
79
+ for alias in getattr(mod, "aliases", []):
80
+ if alias in _commands:
81
+ display.warning(f"⚠️ 别名 [{alias}] 与已有命令/别名冲突,已跳过")
82
+ continue
83
+ _commands[alias] = mod
84
+
85
+
86
+ _discover_commands()
87
+
88
+
89
+ def get_command(name: str) -> CommandModule | None:
90
+ """根据命令名(含斜杠)或别名查找命令模块"""
91
+ return _commands.get(name)
92
+
93
+
94
+ def get_all_commands() -> dict[str, str]:
95
+ """返回 {命令名: 描述} 字典(只返回主名称,不含别名)"""
96
+ result: dict[str, str] = {}
97
+ seen: set[int] = set()
98
+ for name, mod in _commands.items():
99
+ mod_id = id(mod)
100
+ if mod_id in seen:
101
+ continue
102
+ if hasattr(mod, "name") and mod.name == name:
103
+ result[name] = getattr(mod, "description", "")
104
+ seen.add(mod_id)
105
+ return result
106
+
107
+
108
+ async def execute(agent, cmd_name: str, arg: str) -> tuple[bool, str]:
109
+ """执行命令,返回 (是否已处理, 输出文本)。
110
+
111
+ 兼容旧版只返回 bool 的命令(自动补为 ("", False/True))。
112
+ 新版命令可返回 tuple[bool, str] 或 tuple[bool, str, str]。
113
+ """
114
+ mod = get_command(cmd_name)
115
+ if mod is None:
116
+ return (False, "")
117
+
118
+ # 执行命令(自动适配同步/异步)
119
+ if asyncio.iscoroutinefunction(mod.execute):
120
+ result = await mod.execute(agent, arg)
121
+ else:
122
+ result = mod.execute(agent, arg)
123
+
124
+ # 兼容旧版:只返回 bool
125
+ if isinstance(result, bool):
126
+ return (result, "")
127
+
128
+ # 新版:返回 (handled, output)
129
+ if isinstance(result, tuple):
130
+ return result
131
+
132
+ return (True, str(result))
@@ -0,0 +1,73 @@
1
+ """back 命令 — 回退到对话的某个历史时刻
2
+
3
+ 用法:
4
+ /back list 查看历史消息列表(仅非 system 消息,按 1-based 编号)
5
+ /back <index> 回退到指定位置(删除后续消息)
6
+ /back <index> 2 同上(删除后续消息)
7
+ """
8
+
9
+ name = "back"
10
+ aliases = []
11
+ description = "回退到对话的某个历史时刻。用法: /back list 查看列表, /back <N> 直接回退"
12
+
13
+
14
+ async def execute(agent, arg: str) -> tuple[bool, str]:
15
+ parts = arg.strip().split()
16
+
17
+ if not parts:
18
+ msg = "❌ 用法: /back list (查看列表) 或 /back <N> (直接回退)"
19
+ agent.io.error(msg)
20
+ return (True, msg)
21
+
22
+ cmd = parts[0]
23
+
24
+ # ── /back list ─────────────────────────────────────────────
25
+ if cmd == "list":
26
+ history_msgs = agent.get_history_for_display()
27
+ if not history_msgs:
28
+ msg = "没有历史记录"
29
+ agent.io.info(msg)
30
+ return (True, msg)
31
+
32
+ roles_zh = {"user": "👤 用户", "assistant": "🤖 AI", "tool": "🔧 工具"}
33
+ lines = [f"📜 对话历史(共 {len(history_msgs)} 条消息,使用 /back <N> 回退):"]
34
+ for i, msg in enumerate(history_msgs):
35
+ role = roles_zh.get(msg["role"], msg["role"])
36
+ content = msg.get("content", "")
37
+ if msg["role"] == "tool":
38
+ content = content[:60] + "..." if len(content) > 60 else content
39
+ else:
40
+ content = content[:120] + "..." if len(content) > 120 else content
41
+ content = content.replace("\n", " ")
42
+ lines.append(f" [{i + 1:3d}] {role}: {content}")
43
+
44
+ agent.io.info(lines[0])
45
+ for line in lines[1:]:
46
+ agent.io.item(line)
47
+
48
+ return (True, "\n".join(lines))
49
+
50
+ # ── /back <index> [2] ──────────────────────────────────────
51
+ try:
52
+ index = int(cmd)
53
+ except ValueError:
54
+ msg = f"❌ 无效参数:'{cmd}' — 请用 /back list 查看列表,/back <N> 回退"
55
+ agent.io.error(msg)
56
+ return (True, msg)
57
+
58
+ mode = None
59
+ if len(parts) >= 2:
60
+ try:
61
+ mode = int(parts[1])
62
+ if mode != 2:
63
+ msg = "❌ mode=1(保留后续消息)暂不支持,请用 mode=2 或 /fork"
64
+ agent.io.error(msg)
65
+ return (True, msg)
66
+ except ValueError:
67
+ msg = f"❌ 无效参数:'{parts[1]}' 不是数字"
68
+ agent.io.error(msg)
69
+ return (True, msg)
70
+
71
+ result = await agent.back(target_idx=index, mode=mode)
72
+ agent.io.info(result)
73
+ return (True, result)
@@ -0,0 +1,14 @@
1
+ """clear 命令 — 清空当前会话"""
2
+
3
+ from fp_core import display
4
+
5
+ name = "clear"
6
+ aliases = []
7
+ description = "清空当前会话"
8
+
9
+
10
+ def execute(agent, arg: str) -> tuple[bool, str]:
11
+ agent.clear_session()
12
+ msg = "🧹 当前会话已清空"
13
+ display.info(msg)
14
+ return (True, msg)
@@ -0,0 +1,10 @@
1
+ """compact 命令 — 压缩对话历史(异步)"""
2
+
3
+ name = "compact"
4
+ aliases = []
5
+ description = "压缩对话历史"
6
+
7
+
8
+ async def execute(agent, arg: str) -> tuple[bool, str]:
9
+ await agent.compact_context()
10
+ return (True, "📦 历史已压缩")
@@ -0,0 +1,27 @@
1
+ """exit! 命令 — 核弹级退出:删除当前会话、不留痕迹"""
2
+
3
+ import os
4
+
5
+ from fp_core import display
6
+
7
+ name = "exit!"
8
+ aliases = []
9
+ description = "核弹级退出:删除当前会话、不留痕迹"
10
+
11
+
12
+ def execute(agent, arg: str) -> bool:
13
+ sid = agent.session.session_id
14
+ path = agent.session.get_session_path()
15
+
16
+ # 标记核弹退出 — shutdown 时会删除会话文件
17
+ agent.set_nuclear_exit()
18
+
19
+ # 提前删除文件(shutdown 也会删,双重保险)
20
+ if os.path.exists(path):
21
+ try:
22
+ os.remove(path)
23
+ display.info(f"💥 会话 {sid} 已删除,不留痕迹")
24
+ except Exception as e:
25
+ display.warning(f"⚠️ 删除失败: {e}")
26
+
27
+ raise SystemExit()
@@ -0,0 +1,9 @@
1
+ """exit 命令 — 退出程序"""
2
+
3
+ name = "exit"
4
+ aliases = ["quit"]
5
+ description = "退出程序"
6
+
7
+
8
+ def execute(agent, arg: str) -> bool:
9
+ raise SystemExit()
@@ -0,0 +1,16 @@
1
+ """fork 命令 — 基于当前上下文新建会话"""
2
+
3
+ from fp_core import display
4
+
5
+ name = "fork"
6
+ aliases = []
7
+ description = "基于当前上下文新建会话"
8
+
9
+
10
+ def execute(agent, arg: str) -> tuple[bool, str]:
11
+ result = agent.fork()
12
+ if result:
13
+ display.info(result)
14
+ return (True, result)
15
+ display.info("当前会话没有消息,无法 fork")
16
+ return (True, "")
@@ -0,0 +1,25 @@
1
+ """help 命令 — 显示帮助信息"""
2
+
3
+ from fp_core import display
4
+
5
+ name = "help"
6
+ aliases = ["?"]
7
+ description = "显示此帮助"
8
+
9
+
10
+ def execute(agent, arg: str) -> tuple[bool, str]:
11
+ from fp_core.commands import get_all_commands
12
+
13
+ cmds = get_all_commands()
14
+ lines = ["可用命令:"]
15
+ for c, d in sorted(cmds.items()):
16
+ lines.append(f" /{c:11s} {d}")
17
+ output = "\n".join(lines)
18
+
19
+ # CLI 模式:输出到终端(保持着色)
20
+ display.info("可用命令:")
21
+ for c, d in sorted(cmds.items()):
22
+ display.item(f" /{c:11s} {d}")
23
+
24
+ # WebUI 模式:通过返回值传递输出
25
+ return (True, output)
@@ -0,0 +1,32 @@
1
+ """history 命令 — 查看当前对话历史"""
2
+
3
+ from fp_core import display
4
+
5
+ name = "history"
6
+ aliases = []
7
+ description = "查看当前对话历史"
8
+
9
+
10
+ def execute(agent, arg: str) -> tuple[bool, str]:
11
+ history_msgs = agent.history()
12
+
13
+ if not history_msgs:
14
+ display.info("暂无对话历史")
15
+ return (True, "")
16
+
17
+ roles_zh = {"user": "👤 用户", "assistant": "🤖 AI", "tool": "🔧 工具"}
18
+ display.info(f"\n📜 对话历史(共 {len(history_msgs)} 条):")
19
+ print()
20
+
21
+ for i, msg in enumerate(history_msgs):
22
+ role = roles_zh.get(msg["role"], msg["role"])
23
+ content = msg.get("content", "")
24
+ if msg["role"] == "tool":
25
+ content = content[:80] + "..." if len(content) > 80 else content
26
+ else:
27
+ content = content[:120] + "..." if len(content) > 120 else content
28
+ content = content.replace("\n", " ")
29
+ display.item(f" [{i + 1:3d}] {role}: {content}")
30
+
31
+ print()
32
+ return (True, "📜 历史已显示(终端)")
@@ -0,0 +1,30 @@
1
+ """memory 命令 — 查看持久化记忆"""
2
+
3
+ import os
4
+
5
+ from fp_core import config, display
6
+
7
+ name = "memory"
8
+ aliases = []
9
+ description = "查看持久化记忆"
10
+
11
+
12
+ def execute(agent, arg: str) -> tuple[bool, str]:
13
+ lines = ["📋 记忆列表:", ""]
14
+ memory_dir = config.MEMORY_DIR
15
+ if os.path.isdir(memory_dir):
16
+ for f in sorted(os.listdir(memory_dir)):
17
+ if f.endswith(".md"):
18
+ name = f[:-3]
19
+ lines.append(f" • {name}")
20
+ if len(lines) == 2:
21
+ lines.append(" (暂无记忆)")
22
+ output = "\n".join(lines)
23
+ hint = "💡 使用 memory_read / memory_save 工具管理记忆"
24
+
25
+ # CLI 模式:保持着色
26
+ display.info(output)
27
+ display.hint(hint)
28
+
29
+ # WebUI 模式:通过返回值传递
30
+ return (True, output + "\n" + hint)
@@ -0,0 +1,24 @@
1
+ """model 命令 — 显示当前模型配置"""
2
+
3
+ from fp_core import config, display
4
+
5
+ name = "model"
6
+ aliases = []
7
+ description = "显示当前模型配置"
8
+
9
+
10
+ def execute(agent, arg: str) -> tuple[bool, str]:
11
+ lines = [
12
+ f"模型: {agent.model}",
13
+ f"温度: {config.LLM_TEMPERATURE}",
14
+ f"最大 Token: {config.LLM_MAX_TOKENS}",
15
+ f"会话目录: {config.SESSIONS_DIR}",
16
+ ]
17
+ output = "\n".join(lines)
18
+
19
+ # CLI 模式
20
+ for line in lines:
21
+ display.info(line)
22
+
23
+ # WebUI 模式
24
+ return (True, output)
@@ -0,0 +1,166 @@
1
+ """resume 命令 — 切换/删除历史会话(非交互式)
2
+
3
+ 用法:
4
+ /resume list 列出所有会话
5
+ /resume latest 切换到最新会话
6
+ /resume <sid> 切换到指定会话
7
+ /resume delete list 列出可删除的会话
8
+ /resume delete <sid> 删除指定会话
9
+ """
10
+
11
+ name = "resume"
12
+ aliases = []
13
+ description = "切换/删除历史会话。用法: /resume list, /resume latest, /resume <sid>, /resume delete <sid>"
14
+
15
+
16
+ def _display_width(s: str) -> int:
17
+ """计算字符串的显示宽度(中文=2,英文/数字/符号=1)"""
18
+ width = 0
19
+ for c in s:
20
+ if "\u4e00" <= c <= "\u9fff" or "\u3000" <= c <= "\u303f" or c in "()":
21
+ width += 2
22
+ else:
23
+ width += 1
24
+ return width
25
+
26
+
27
+ def _pad_to_width(s: str, width: int) -> str:
28
+ """用空格填充到指定显示宽度"""
29
+ return s + " " * max(0, width - _display_width(s))
30
+
31
+
32
+ async def execute(agent, arg: str) -> tuple[bool, str]:
33
+ arg = arg.strip()
34
+
35
+ # ── /resume delete list ────────────────────────────────────
36
+ if arg == "delete list":
37
+ sessions = agent.session.list_sessions()
38
+ if not sessions:
39
+ msg = "暂无历史会话"
40
+ agent.io.info(msg)
41
+ return (True, msg)
42
+
43
+ current_sid = agent.session.session_id
44
+ sorted_items = sorted(
45
+ sessions.items(),
46
+ key=lambda x: x[1].get("updated", ""),
47
+ reverse=True,
48
+ )
49
+
50
+ deletable = [(sid, meta) for sid, meta in sorted_items if sid != current_sid]
51
+ if not deletable:
52
+ msg = "没有可删除的会话(当前会话不可删除)"
53
+ agent.io.info(msg)
54
+ return (True, msg)
55
+
56
+ summary_width = 44
57
+ lines = ["🗑️ 可删除的会话(使用 /resume delete <sid> 删除):"]
58
+ for i, (sid, meta) in enumerate(deletable, 1):
59
+ raw_summary = meta.get("summary", "") or ""
60
+ msg_count = meta.get("message_count", 0)
61
+ created = meta.get("created", "?")[:16]
62
+ if not raw_summary:
63
+ display_summary = "(无摘要)"
64
+ else:
65
+ display_summary = ""
66
+ for ch in raw_summary:
67
+ candidate = display_summary + ch
68
+ if _display_width(candidate) > summary_width - 1:
69
+ display_summary += "…"
70
+ break
71
+ display_summary = candidate
72
+ line = f" [{i}] {_pad_to_width(display_summary, summary_width)} ({msg_count:3d}条, {created}) [{sid}]"
73
+ lines.append(line)
74
+
75
+ agent.io.info(lines[0])
76
+ for line in lines[1:]:
77
+ agent.io.item(line)
78
+
79
+ return (True, "\n".join(lines))
80
+
81
+ # ── /resume delete <sid> ───────────────────────────────────
82
+ if arg.startswith("delete "):
83
+ sid = arg[7:].strip()
84
+ if sid == agent.session.session_id:
85
+ msg = "❌ 不能删除当前正在使用的会话"
86
+ agent.io.error(msg)
87
+ return (True, msg)
88
+ if agent.delete_session(sid):
89
+ msg = f"🗑️ 已删除会话: {sid}"
90
+ agent.io.info(msg)
91
+ return (True, msg)
92
+ else:
93
+ msg = f"❌ 会话 {sid} 不存在或删除失败"
94
+ agent.io.error(msg)
95
+ return (True, msg)
96
+
97
+ # ── /resume delete (无参数) ─────────────────────────────────
98
+ if arg == "delete":
99
+ msg = "❌ 用法: /resume delete list (查看可删除会话) 或 /resume delete <sid> (直接删除)"
100
+ agent.io.error(msg)
101
+ return (True, msg)
102
+
103
+ # ── /resume list ───────────────────────────────────────────
104
+ if arg == "list":
105
+ sessions = agent.session.list_sessions()
106
+ if not sessions:
107
+ msg = "暂无历史会话"
108
+ agent.io.info(msg)
109
+ return (True, msg)
110
+
111
+ current_sid = agent.session.session_id
112
+ sorted_items = sorted(
113
+ sessions.items(),
114
+ key=lambda x: x[1].get("updated", ""),
115
+ reverse=True,
116
+ )
117
+
118
+ summary_width = 44
119
+ lines = ["📂 会话列表(使用 /resume <sid> 切换,/resume latest 切换到最新):"]
120
+ for i, (sid, meta) in enumerate(sorted_items, 1):
121
+ raw_summary = meta.get("summary", "") or ""
122
+ msg_count = meta.get("message_count", 0)
123
+ marker = " ⬅" if sid == current_sid else ""
124
+
125
+ if not raw_summary:
126
+ display_summary = "(无摘要)"
127
+ else:
128
+ display_summary = ""
129
+ for ch in raw_summary:
130
+ candidate = display_summary + ch
131
+ if _display_width(candidate) > summary_width - 1:
132
+ display_summary += "…"
133
+ break
134
+ display_summary = candidate
135
+
136
+ line = f" [{i}] {_pad_to_width(display_summary, summary_width)} ({msg_count:3d}条, {sid}){marker}"
137
+ lines.append(line)
138
+
139
+ agent.io.info(lines[0])
140
+ for line in lines[1:]:
141
+ agent.io.item(line)
142
+
143
+ return (True, "\n".join(lines))
144
+
145
+ # ── /resume latest ─────────────────────────────────────────
146
+ if arg == "latest":
147
+ agent.resume_latest()
148
+ msg = f"📂 已切换到最新会话: {agent.session.session_id}"
149
+ agent.io.info(msg)
150
+ return (True, msg)
151
+
152
+ # ── /resume <无参数> ────────────────────────────────────────
153
+ if not arg:
154
+ msg = "❌ 用法: /resume list (查看列表) | /resume latest | /resume <sid> (直接切换)"
155
+ agent.io.error(msg)
156
+ return (True, msg)
157
+
158
+ # ── /resume <sid> ──────────────────────────────────────────
159
+ if agent.switch_session(arg):
160
+ msg = f"📂 已切换到会话: {arg}"
161
+ agent.io.info(msg)
162
+ return (True, msg)
163
+ else:
164
+ msg = f"❌ 会话 {arg} 不存在。使用 /resume list 查看可用会话"
165
+ agent.io.error(msg)
166
+ return (True, msg)
@@ -0,0 +1,13 @@
1
+ """session 命令 — 显示当前会话信息"""
2
+
3
+ from fp_core import display
4
+
5
+ name = "session"
6
+ aliases = []
7
+ description = "显示当前会话信息"
8
+
9
+
10
+ def execute(agent, arg: str) -> tuple[bool, str]:
11
+ msg = f"📂 当前会话: {agent.session.session_id}"
12
+ display.info(msg)
13
+ return (True, msg)