my-pi-agent 0.1.0

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 (141) hide show
  1. package/README.md +318 -0
  2. package/package.json +45 -0
  3. package/pyproject.toml +50 -0
  4. package/src/my_agent_core/__init__.py +123 -0
  5. package/src/my_agent_core/agent.py +441 -0
  6. package/src/my_agent_core/background.py +121 -0
  7. package/src/my_agent_core/context.py +505 -0
  8. package/src/my_agent_core/events.py +153 -0
  9. package/src/my_agent_core/extensions/__init__.py +9 -0
  10. package/src/my_agent_core/extensions/core.py +197 -0
  11. package/src/my_agent_core/hooks.py +130 -0
  12. package/src/my_agent_core/loop.py +709 -0
  13. package/src/my_agent_core/main.py +134 -0
  14. package/src/my_agent_core/memory.py +241 -0
  15. package/src/my_agent_core/message_queue.py +110 -0
  16. package/src/my_agent_core/plugins.py +212 -0
  17. package/src/my_agent_core/registry.py +186 -0
  18. package/src/my_agent_core/session/__init__.py +79 -0
  19. package/src/my_agent_core/session/entries.py +197 -0
  20. package/src/my_agent_core/session/jsonl.py +60 -0
  21. package/src/my_agent_core/session/memory.py +137 -0
  22. package/src/my_agent_core/session/session.py +400 -0
  23. package/src/my_agent_core/session/storage.py +245 -0
  24. package/src/my_agent_core/session/store.py +131 -0
  25. package/src/my_agent_core/session/tree.py +86 -0
  26. package/src/my_agent_core/skills.py +149 -0
  27. package/src/my_agent_core/subagent_tasks.py +170 -0
  28. package/src/my_agent_core/subagents.py +148 -0
  29. package/src/my_agent_core/task_store.py +248 -0
  30. package/src/my_agent_core/tool_history.py +189 -0
  31. package/src/my_agent_core/tools/__init__.py +5 -0
  32. package/src/my_agent_core/tools/builtin/__init__.py +5 -0
  33. package/src/my_agent_core/tools/builtin/task.py +30 -0
  34. package/src/my_agent_core/tools/builtin/task_tools.py +215 -0
  35. package/src/my_agent_core/tools/core.py +239 -0
  36. package/src/my_agent_llm/__init__.py +45 -0
  37. package/src/my_agent_llm/auth/__init__.py +46 -0
  38. package/src/my_agent_llm/auth/antigravity.py +209 -0
  39. package/src/my_agent_llm/auth/manager.py +259 -0
  40. package/src/my_agent_llm/auth/quota.py +56 -0
  41. package/src/my_agent_llm/auth/schema.py +94 -0
  42. package/src/my_agent_llm/client.py +116 -0
  43. package/src/my_agent_llm/config.py +17 -0
  44. package/src/my_agent_llm/events.py +84 -0
  45. package/src/my_agent_llm/models.py +195 -0
  46. package/src/my_agent_llm/providers/__init__.py +4 -0
  47. package/src/my_agent_llm/providers/_base.py +94 -0
  48. package/src/my_agent_llm/providers/anthropic.py +298 -0
  49. package/src/my_agent_llm/providers/antigravity.py +480 -0
  50. package/src/my_agent_llm/providers/deepseek.py +196 -0
  51. package/src/my_agent_llm/providers/openai.py +364 -0
  52. package/src/my_agent_llm/providers/registry.py +16 -0
  53. package/src/my_agent_llm/stream.py +218 -0
  54. package/src/my_coding_agent/__init__.py +66 -0
  55. package/src/my_coding_agent/agent.py +208 -0
  56. package/src/my_coding_agent/cli.py +78 -0
  57. package/src/my_coding_agent/file_reference.py +80 -0
  58. package/src/my_coding_agent/macro.py +408 -0
  59. package/src/my_coding_agent/mcp.py +243 -0
  60. package/src/my_coding_agent/mutation_queue.py +37 -0
  61. package/src/my_coding_agent/paths.py +119 -0
  62. package/src/my_coding_agent/permissions.py +84 -0
  63. package/src/my_coding_agent/prompt.py +54 -0
  64. package/src/my_coding_agent/rpc_server.py +2817 -0
  65. package/src/my_coding_agent/settings.py +126 -0
  66. package/src/my_coding_agent/tools/__init__.py +55 -0
  67. package/src/my_coding_agent/tools/base.py +58 -0
  68. package/src/my_coding_agent/tools/bash.py +206 -0
  69. package/src/my_coding_agent/tools/edit.py +226 -0
  70. package/src/my_coding_agent/tools/find.py +118 -0
  71. package/src/my_coding_agent/tools/grep.py +177 -0
  72. package/src/my_coding_agent/tools/ls.py +112 -0
  73. package/src/my_coding_agent/tools/read.py +113 -0
  74. package/src/my_coding_agent/tools/write.py +72 -0
  75. package/tui/README.md +27 -0
  76. package/tui/bin/my-agent.js +98 -0
  77. package/tui/dist/app.d.ts +41 -0
  78. package/tui/dist/app.js +110 -0
  79. package/tui/dist/bridge/event-translator.d.ts +92 -0
  80. package/tui/dist/bridge/event-translator.js +216 -0
  81. package/tui/dist/bridge/kernel-bridge.d.ts +48 -0
  82. package/tui/dist/bridge/kernel-bridge.js +132 -0
  83. package/tui/dist/client.d.ts +63 -0
  84. package/tui/dist/client.js +239 -0
  85. package/tui/dist/components/assistant-message.d.ts +19 -0
  86. package/tui/dist/components/assistant-message.js +90 -0
  87. package/tui/dist/components/compaction-summary-message.d.ts +19 -0
  88. package/tui/dist/components/compaction-summary-message.js +46 -0
  89. package/tui/dist/components/custom-editor.d.ts +18 -0
  90. package/tui/dist/components/custom-editor.js +56 -0
  91. package/tui/dist/components/dynamic-border.d.ts +9 -0
  92. package/tui/dist/components/dynamic-border.js +14 -0
  93. package/tui/dist/components/footer.d.ts +39 -0
  94. package/tui/dist/components/footer.js +199 -0
  95. package/tui/dist/components/header.d.ts +4 -0
  96. package/tui/dist/components/header.js +21 -0
  97. package/tui/dist/components/keys.d.ts +5 -0
  98. package/tui/dist/components/keys.js +12 -0
  99. package/tui/dist/components/login-selector.d.ts +26 -0
  100. package/tui/dist/components/login-selector.js +181 -0
  101. package/tui/dist/components/logout-selector.d.ts +19 -0
  102. package/tui/dist/components/logout-selector.js +88 -0
  103. package/tui/dist/components/model-selector.d.ts +40 -0
  104. package/tui/dist/components/model-selector.js +268 -0
  105. package/tui/dist/components/session-selector.d.ts +54 -0
  106. package/tui/dist/components/session-selector.js +393 -0
  107. package/tui/dist/components/settings-selector.d.ts +24 -0
  108. package/tui/dist/components/settings-selector.js +146 -0
  109. package/tui/dist/components/status-indicator.d.ts +25 -0
  110. package/tui/dist/components/status-indicator.js +60 -0
  111. package/tui/dist/components/theme-selector.d.ts +14 -0
  112. package/tui/dist/components/theme-selector.js +77 -0
  113. package/tui/dist/components/thinking-selector.d.ts +21 -0
  114. package/tui/dist/components/thinking-selector.js +128 -0
  115. package/tui/dist/components/tool-execution.d.ts +31 -0
  116. package/tui/dist/components/tool-execution.js +206 -0
  117. package/tui/dist/components/tree-selector.d.ts +40 -0
  118. package/tui/dist/components/tree-selector.js +173 -0
  119. package/tui/dist/components/user-message-selector.d.ts +21 -0
  120. package/tui/dist/components/user-message-selector.js +103 -0
  121. package/tui/dist/components/user-message.d.ts +5 -0
  122. package/tui/dist/components/user-message.js +15 -0
  123. package/tui/dist/index.d.ts +11 -0
  124. package/tui/dist/index.js +11 -0
  125. package/tui/dist/interactive/chat-viewport.d.ts +19 -0
  126. package/tui/dist/interactive/chat-viewport.js +41 -0
  127. package/tui/dist/interactive/components.d.ts +1 -0
  128. package/tui/dist/interactive/components.js +1 -0
  129. package/tui/dist/interactive/interactive-mode.d.ts +89 -0
  130. package/tui/dist/interactive/interactive-mode.js +1625 -0
  131. package/tui/dist/interactive/theme.d.ts +1 -0
  132. package/tui/dist/interactive/theme.js +1 -0
  133. package/tui/dist/interactive/tui-renderer.d.ts +8 -0
  134. package/tui/dist/interactive/tui-renderer.js +10 -0
  135. package/tui/dist/protocol.d.ts +78 -0
  136. package/tui/dist/protocol.js +1 -0
  137. package/tui/dist/theme/dark.json +54 -0
  138. package/tui/dist/theme/light.json +71 -0
  139. package/tui/dist/theme/theme.d.ts +20 -0
  140. package/tui/dist/theme/theme.js +86 -0
  141. package/tui/package.json +25 -0
@@ -0,0 +1,208 @@
1
+ """生产级编码智能体门面 (Dual API 架构与 7 大工具自动装配)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from collections.abc import AsyncIterator
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from my_agent_core import Agent # pyright: ignore[reportMissingImports]
12
+ from my_agent_core.events import Event # pyright: ignore[reportMissingImports]
13
+ from my_agent_core.hooks import HookResult, ToolCallHook, UserInputHook # pyright: ignore[reportMissingImports]
14
+ from my_agent_core.session import Session # pyright: ignore[reportMissingImports]
15
+ from my_agent_core.tools import Tool # pyright: ignore[reportMissingImports]
16
+
17
+ from my_coding_agent.file_reference import FileReferenceParser
18
+ from my_coding_agent.mcp import MCPClientManager
19
+ from my_coding_agent.mutation_queue import FileMutationQueue
20
+ from my_coding_agent.prompt import build_default_coding_prompt
21
+ from my_coding_agent.tools import build_coding_tools
22
+
23
+ if TYPE_CHECKING:
24
+ from my_coding_agent.permissions import PermissionGate
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class CodingAgent:
30
+ """生产级编码智能体门面 (Dual API 架构)"""
31
+
32
+ def __init__(
33
+ self,
34
+ *,
35
+ workspace: str | Path,
36
+ llm,
37
+ session: Session,
38
+ system_prompt: str | None = None,
39
+ extra_tools: list[Tool] | tuple[Tool, ...] = (),
40
+ permission_gate: PermissionGate | None = None,
41
+ auto_load_mcp: bool = True,
42
+ **kw,
43
+ ):
44
+ self.workspace = Path(workspace).resolve()
45
+ self.mutation_queue = FileMutationQueue()
46
+ self._permission_gate = permission_gate
47
+ self.auto_load_mcp = auto_load_mcp
48
+ self._mcp_manager: MCPClientManager | None = None
49
+ self._mcp_loaded: bool = False
50
+ self._loop: asyncio.AbstractEventLoop | None = None
51
+
52
+ if isinstance(session, (str, Path)):
53
+ session = Session(path=Path(session), cwd=str(self.workspace))
54
+
55
+ # 1. 自动生成或应用系统提示词
56
+ effective_prompt = system_prompt if system_prompt is not None else build_default_coding_prompt(self.workspace)
57
+
58
+ # 2. 构造框架通用 Agent
59
+ self.agent = Agent(
60
+ llm=llm,
61
+ session=session,
62
+ tools=list(extra_tools),
63
+ system_prompt=effective_prompt,
64
+ **kw,
65
+ )
66
+
67
+ # 3. 若注入了权限门禁,注册到 ToolCallHook
68
+ if self._permission_gate is not None:
69
+ self.agent.hooks.register(ToolCallHook, self._permission_gate)
70
+
71
+ # 4. 装配 FileReferenceParser 并注册到 UserInputHook
72
+ self.file_reference_parser = FileReferenceParser(self.workspace)
73
+
74
+ def _expand_file_refs(hook: UserInputHook) -> HookResult | None:
75
+ expanded = self.file_reference_parser.expand_references(hook.input_text)
76
+ if expanded != hook.input_text:
77
+ return HookResult(updated_input=expanded)
78
+ return None
79
+
80
+ self.agent.hooks.register(UserInputHook, _expand_file_refs)
81
+
82
+ # 5. 装配 7 大编码专属工具
83
+ coding_tools = build_coding_tools(
84
+ self.workspace,
85
+ mutation_queue=self.mutation_queue,
86
+ background_runner=self.agent.background_runner,
87
+ )
88
+ for t in coding_tools:
89
+ self.agent.registry.register(t)
90
+
91
+ @property
92
+ def permission_gate(self) -> PermissionGate | None:
93
+ """底层安全权限门禁。"""
94
+ return self._permission_gate
95
+
96
+ @property
97
+ def task_store(self):
98
+ """底层的任务看板仓库。"""
99
+ return self.agent.task_store
100
+
101
+ @property
102
+ def background_runner(self):
103
+ """底层的后台作业执行器。"""
104
+ return self.agent.background_runner
105
+
106
+ @property
107
+ def session(self) -> Session:
108
+ """底层会话对象。"""
109
+ return self.agent.session
110
+
111
+ @property
112
+ def mcp_manager(self) -> MCPClientManager | None:
113
+ """底层 MCP 客户端管理器。"""
114
+ return self._mcp_manager
115
+
116
+ async def ensure_mcp_loaded(self) -> None:
117
+ """按需自动扫描并加载工作区 .mcp.json 配置的 MCP 服务与工具。"""
118
+ if not self.auto_load_mcp or self._mcp_loaded:
119
+ return
120
+ self._mcp_loaded = True
121
+
122
+ mcp_file = self.workspace / ".mcp.json"
123
+ if not mcp_file.is_file():
124
+ return
125
+
126
+ try:
127
+ if self._mcp_manager is None:
128
+ self._mcp_manager = MCPClientManager.from_config_file(mcp_file)
129
+ await self._mcp_manager.connect_all()
130
+ tools = self._mcp_manager.get_all_tools()
131
+ for t in tools:
132
+ setattr(t, "is_mcp", True)
133
+ self.agent.registry.register(t)
134
+ except Exception as exc:
135
+ logger.warning("加载工作区 .mcp.json 失败: %s", exc)
136
+
137
+ async def close_mcp(self) -> None:
138
+ """异步关闭并回收已挂载的 MCP 连接与子进程资源。"""
139
+ if self._mcp_manager is not None:
140
+ await self._mcp_manager.close_all()
141
+ self._mcp_manager = None
142
+ # 反注册已挂载的 MCP 工具,防止向大模型下发失效的外部工具 Schema
143
+ registry = getattr(self.agent, "registry", None)
144
+ if registry and hasattr(registry, "_tools"):
145
+ mcp_tool_names = [name for name, t in registry._tools.items() if getattr(t, "is_mcp", False)]
146
+ for name in mcp_tool_names:
147
+ if hasattr(registry, "unregister") and callable(registry.unregister):
148
+ registry.unregister(name)
149
+ self._mcp_loaded = False
150
+
151
+ async def __aenter__(self) -> CodingAgent:
152
+ """异步上下文管理器入口:自动确保 MCP 工具完成挂载。"""
153
+ await self.ensure_mcp_loaded()
154
+ return self
155
+
156
+ async def __aexit__(
157
+ self,
158
+ exc_type: type[BaseException] | None,
159
+ exc_val: BaseException | None,
160
+ exc_tb: Any,
161
+ ) -> None:
162
+ """异步上下文管理器出口:优雅回收 MCP 资源。"""
163
+ await self.close_mcp()
164
+
165
+ async def compact(self, instructions: str | None = None):
166
+ """手动触发智能上下文压缩。"""
167
+ return await self.agent.compact(instructions=instructions)
168
+
169
+ async def run(self, user_input: str) -> str:
170
+ """批处理高阶入口:聚合最终助手文本"""
171
+ try:
172
+ self._loop = asyncio.get_running_loop()
173
+ except RuntimeError:
174
+ pass
175
+ await self.ensure_mcp_loaded()
176
+ res = await self.agent.run(user_input)
177
+ return res if res is not None else ""
178
+
179
+ async def run_stream(self, user_input: str) -> AsyncIterator[Event]:
180
+ """流式一等公民入口:实时产出全生命周期事件"""
181
+ try:
182
+ self._loop = asyncio.get_running_loop()
183
+ except RuntimeError:
184
+ pass
185
+ await self.ensure_mcp_loaded()
186
+ async for event in self.agent.prompt_stream(user_input):
187
+ yield event
188
+
189
+ def steer(self, message: str) -> None:
190
+ """注入即时转向指令(在下一个安全点打断/干预模型执行路线)。"""
191
+ self.agent.steer(message)
192
+
193
+ def follow_up(self, message: str) -> None:
194
+ """追加排队追问指令(在当前任务彻底完成后自动开启下一段任务)。"""
195
+ self.agent.follow_up(message)
196
+
197
+ def abort(self) -> None:
198
+ """中止当前运行中的任务(取消流式输出,丢弃未完成半截文本并清空干预队列)。"""
199
+ try:
200
+ asyncio.get_running_loop()
201
+ self.agent.abort()
202
+ except (RuntimeError,):
203
+ self.agent._aborted = True
204
+ if self.agent._current_signal is not None:
205
+ self.agent._current_signal.cancel()
206
+ self.agent.message_queue.clear()
207
+ if self._loop is not None and self._loop.is_running():
208
+ self._loop.call_soon_threadsafe(lambda: asyncio.create_task(self.agent.background_runner.cancel_all()))
@@ -0,0 +1,78 @@
1
+ """my-pi-agent Python CLI entrypoint facade.
2
+
3
+ This module provides the main() entrypoint when my-pi-agent is launched via
4
+ Python (e.g. `uvx my-pi-agent`, `uv tool install my-pi-agent`, or `python -m my_coding_agent.cli`).
5
+ It detects the system Node.js runtime and bridges to the Pi-TUI frontend.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ def find_tui_entry() -> Path | None:
17
+ """Find the my-agent.js TUI entrypoint across dev and packaged locations."""
18
+ # 1. Development workspace tree: <repo_root>/tui/bin/my-agent.js
19
+ repo_root = Path(__file__).resolve().parent.parent.parent
20
+ dev_path = repo_root / "tui" / "bin" / "my-agent.js"
21
+ if dev_path.exists():
22
+ return dev_path
23
+
24
+ # 2. Package-bundled data directory: <package_dir>/tui/bin/my-agent.js
25
+ pkg_bundled = Path(__file__).resolve().parent / "tui" / "bin" / "my-agent.js"
26
+ if pkg_bundled.exists():
27
+ return pkg_bundled
28
+
29
+ # 3. System prefix share directory: <sys.prefix>/share/my-pi-agent/tui/bin/my-agent.js
30
+ prefix_path = Path(sys.prefix) / "share" / "my-pi-agent" / "tui" / "bin" / "my-agent.js"
31
+ if prefix_path.exists():
32
+ return prefix_path
33
+
34
+ return None
35
+
36
+
37
+ def main(argv: list[str] | None = None) -> int:
38
+ """Main CLI entrypoint bridging to the Pi-TUI frontend."""
39
+ if argv is None:
40
+ argv = sys.argv[1:]
41
+
42
+ node_bin = shutil.which("node")
43
+ if not node_bin:
44
+ print(
45
+ "\n[my-pi-agent] 错误: 未检测到 Node.js 运行时环境。\n"
46
+ "my-pi-agent 采用像素级 Pi-TUI 呈现层,需要 Node.js (>= 18.0) 运行交互界面。\n\n"
47
+ "请安装 Node.js 后重试:\n"
48
+ " - 官方网站下载: https://nodejs.org\n"
49
+ " - 或使用极速版本管理器安装: fnm (https://github.com/Schniz/fnm) 或 nvm\n",
50
+ file=sys.stderr,
51
+ )
52
+ return 1
53
+
54
+ tui_entry = find_tui_entry()
55
+ if not tui_entry or not tui_entry.exists():
56
+ print(
57
+ "\n[my-pi-agent] 错误: 未找到 TUI 启动入口脚本 (my-agent.js)。\n"
58
+ "请确保已正确构建前端: npm run build --prefix tui\n",
59
+ file=sys.stderr,
60
+ )
61
+ return 1
62
+
63
+ cmd = [node_bin, str(tui_entry), *argv]
64
+ if "--python-executable" not in argv:
65
+ cmd.extend(["--python-executable", sys.executable])
66
+
67
+ try:
68
+ proc = subprocess.run(cmd)
69
+ return proc.returncode
70
+ except KeyboardInterrupt:
71
+ return 130
72
+ except Exception as exc:
73
+ print(f"[my-pi-agent] 启动异常: {exc}", file=sys.stderr)
74
+ return 1
75
+
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main())
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from my_coding_agent.tools.base import (
7
+ DEFAULT_MAX_BYTES,
8
+ DEFAULT_MAX_LINES,
9
+ is_binary_file,
10
+ resolve_path,
11
+ )
12
+
13
+
14
+ class FileReferenceParser:
15
+ """对标 Pig-Mono 的提示词 @ 文件引用解析与快照注入器。"""
16
+
17
+ def __init__(self, workspace: Path | str) -> None:
18
+ self.workspace = Path(workspace).resolve()
19
+
20
+ def parse_references(self, text: str) -> list[str]:
21
+ """从输入文本中匹配提取 @path/to/file 或 @filename 文件引用。"""
22
+ pattern = r"@([\w\-./]+\.\w+)"
23
+ matches = re.findall(pattern, text)
24
+ # 去重保序
25
+ seen: set[str] = set()
26
+ unique_refs: list[str] = []
27
+ for m in matches:
28
+ if m not in seen:
29
+ seen.add(m)
30
+ unique_refs.append(m)
31
+ return unique_refs
32
+
33
+ def resolve_reference(self, ref: str) -> tuple[bool, Path | None, str]:
34
+ """解析引用并读取文件内容。"""
35
+ try:
36
+ target = resolve_path(self.workspace, ref)
37
+ if not target.exists():
38
+ return False, None, f"File not found: {ref}"
39
+ if target.is_dir():
40
+ return False, target, f"Path is a directory: {ref}"
41
+ if is_binary_file(target):
42
+ return False, target, f"Cannot reference binary file: {ref}"
43
+
44
+ text = target.read_text(encoding="utf-8", errors="replace")
45
+ lines = text.splitlines()
46
+ if len(lines) > DEFAULT_MAX_LINES:
47
+ lines = lines[:DEFAULT_MAX_LINES]
48
+ text = "\n".join(lines) + f"\n[File truncated at {DEFAULT_MAX_LINES} lines]"
49
+
50
+ encoded = text.encode("utf-8")
51
+ if len(encoded) > DEFAULT_MAX_BYTES:
52
+ text = encoded[:DEFAULT_MAX_BYTES].decode("utf-8", errors="ignore") + "\n[File truncated at 50KB]"
53
+
54
+ return True, target, text
55
+ except Exception as e:
56
+ return False, None, f"Error resolving file reference: {e}"
57
+
58
+ def expand_references(self, text: str) -> str:
59
+ """解析所有引用并将有效文件快照追加到提示词尾部。"""
60
+ refs = self.parse_references(text)
61
+ if not refs:
62
+ return text
63
+
64
+ injected_blocks: list[str] = []
65
+ for r in refs:
66
+ ok, target, content = self.resolve_reference(r)
67
+ if ok and target:
68
+ try:
69
+ rel_path = str(target.relative_to(self.workspace)).replace("\\", "/")
70
+ except ValueError:
71
+ rel_path = str(target).replace("\\", "/")
72
+ injected_blocks.append(f'<referenced_file path="{rel_path}">\n{content}\n</referenced_file>')
73
+
74
+ if not injected_blocks:
75
+ return text
76
+
77
+ return text + "\n\n<referenced_files>\n" + "\n\n".join(injected_blocks) + "\n</referenced_files>"
78
+
79
+ # 别名支持
80
+ expand = expand_references