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.
- package/README.md +318 -0
- package/package.json +45 -0
- package/pyproject.toml +50 -0
- package/src/my_agent_core/__init__.py +123 -0
- package/src/my_agent_core/agent.py +441 -0
- package/src/my_agent_core/background.py +121 -0
- package/src/my_agent_core/context.py +505 -0
- package/src/my_agent_core/events.py +153 -0
- package/src/my_agent_core/extensions/__init__.py +9 -0
- package/src/my_agent_core/extensions/core.py +197 -0
- package/src/my_agent_core/hooks.py +130 -0
- package/src/my_agent_core/loop.py +709 -0
- package/src/my_agent_core/main.py +134 -0
- package/src/my_agent_core/memory.py +241 -0
- package/src/my_agent_core/message_queue.py +110 -0
- package/src/my_agent_core/plugins.py +212 -0
- package/src/my_agent_core/registry.py +186 -0
- package/src/my_agent_core/session/__init__.py +79 -0
- package/src/my_agent_core/session/entries.py +197 -0
- package/src/my_agent_core/session/jsonl.py +60 -0
- package/src/my_agent_core/session/memory.py +137 -0
- package/src/my_agent_core/session/session.py +400 -0
- package/src/my_agent_core/session/storage.py +245 -0
- package/src/my_agent_core/session/store.py +131 -0
- package/src/my_agent_core/session/tree.py +86 -0
- package/src/my_agent_core/skills.py +149 -0
- package/src/my_agent_core/subagent_tasks.py +170 -0
- package/src/my_agent_core/subagents.py +148 -0
- package/src/my_agent_core/task_store.py +248 -0
- package/src/my_agent_core/tool_history.py +189 -0
- package/src/my_agent_core/tools/__init__.py +5 -0
- package/src/my_agent_core/tools/builtin/__init__.py +5 -0
- package/src/my_agent_core/tools/builtin/task.py +30 -0
- package/src/my_agent_core/tools/builtin/task_tools.py +215 -0
- package/src/my_agent_core/tools/core.py +239 -0
- package/src/my_agent_llm/__init__.py +45 -0
- package/src/my_agent_llm/auth/__init__.py +46 -0
- package/src/my_agent_llm/auth/antigravity.py +209 -0
- package/src/my_agent_llm/auth/manager.py +259 -0
- package/src/my_agent_llm/auth/quota.py +56 -0
- package/src/my_agent_llm/auth/schema.py +94 -0
- package/src/my_agent_llm/client.py +116 -0
- package/src/my_agent_llm/config.py +17 -0
- package/src/my_agent_llm/events.py +84 -0
- package/src/my_agent_llm/models.py +195 -0
- package/src/my_agent_llm/providers/__init__.py +4 -0
- package/src/my_agent_llm/providers/_base.py +94 -0
- package/src/my_agent_llm/providers/anthropic.py +298 -0
- package/src/my_agent_llm/providers/antigravity.py +480 -0
- package/src/my_agent_llm/providers/deepseek.py +196 -0
- package/src/my_agent_llm/providers/openai.py +364 -0
- package/src/my_agent_llm/providers/registry.py +16 -0
- package/src/my_agent_llm/stream.py +218 -0
- package/src/my_coding_agent/__init__.py +66 -0
- package/src/my_coding_agent/agent.py +208 -0
- package/src/my_coding_agent/cli.py +78 -0
- package/src/my_coding_agent/file_reference.py +80 -0
- package/src/my_coding_agent/macro.py +408 -0
- package/src/my_coding_agent/mcp.py +243 -0
- package/src/my_coding_agent/mutation_queue.py +37 -0
- package/src/my_coding_agent/paths.py +119 -0
- package/src/my_coding_agent/permissions.py +84 -0
- package/src/my_coding_agent/prompt.py +54 -0
- package/src/my_coding_agent/rpc_server.py +2817 -0
- package/src/my_coding_agent/settings.py +126 -0
- package/src/my_coding_agent/tools/__init__.py +55 -0
- package/src/my_coding_agent/tools/base.py +58 -0
- package/src/my_coding_agent/tools/bash.py +206 -0
- package/src/my_coding_agent/tools/edit.py +226 -0
- package/src/my_coding_agent/tools/find.py +118 -0
- package/src/my_coding_agent/tools/grep.py +177 -0
- package/src/my_coding_agent/tools/ls.py +112 -0
- package/src/my_coding_agent/tools/read.py +113 -0
- package/src/my_coding_agent/tools/write.py +72 -0
- package/tui/README.md +27 -0
- package/tui/bin/my-agent.js +98 -0
- package/tui/dist/app.d.ts +41 -0
- package/tui/dist/app.js +110 -0
- package/tui/dist/bridge/event-translator.d.ts +92 -0
- package/tui/dist/bridge/event-translator.js +216 -0
- package/tui/dist/bridge/kernel-bridge.d.ts +48 -0
- package/tui/dist/bridge/kernel-bridge.js +132 -0
- package/tui/dist/client.d.ts +63 -0
- package/tui/dist/client.js +239 -0
- package/tui/dist/components/assistant-message.d.ts +19 -0
- package/tui/dist/components/assistant-message.js +90 -0
- package/tui/dist/components/compaction-summary-message.d.ts +19 -0
- package/tui/dist/components/compaction-summary-message.js +46 -0
- package/tui/dist/components/custom-editor.d.ts +18 -0
- package/tui/dist/components/custom-editor.js +56 -0
- package/tui/dist/components/dynamic-border.d.ts +9 -0
- package/tui/dist/components/dynamic-border.js +14 -0
- package/tui/dist/components/footer.d.ts +39 -0
- package/tui/dist/components/footer.js +199 -0
- package/tui/dist/components/header.d.ts +4 -0
- package/tui/dist/components/header.js +21 -0
- package/tui/dist/components/keys.d.ts +5 -0
- package/tui/dist/components/keys.js +12 -0
- package/tui/dist/components/login-selector.d.ts +26 -0
- package/tui/dist/components/login-selector.js +181 -0
- package/tui/dist/components/logout-selector.d.ts +19 -0
- package/tui/dist/components/logout-selector.js +88 -0
- package/tui/dist/components/model-selector.d.ts +40 -0
- package/tui/dist/components/model-selector.js +268 -0
- package/tui/dist/components/session-selector.d.ts +54 -0
- package/tui/dist/components/session-selector.js +393 -0
- package/tui/dist/components/settings-selector.d.ts +24 -0
- package/tui/dist/components/settings-selector.js +146 -0
- package/tui/dist/components/status-indicator.d.ts +25 -0
- package/tui/dist/components/status-indicator.js +60 -0
- package/tui/dist/components/theme-selector.d.ts +14 -0
- package/tui/dist/components/theme-selector.js +77 -0
- package/tui/dist/components/thinking-selector.d.ts +21 -0
- package/tui/dist/components/thinking-selector.js +128 -0
- package/tui/dist/components/tool-execution.d.ts +31 -0
- package/tui/dist/components/tool-execution.js +206 -0
- package/tui/dist/components/tree-selector.d.ts +40 -0
- package/tui/dist/components/tree-selector.js +173 -0
- package/tui/dist/components/user-message-selector.d.ts +21 -0
- package/tui/dist/components/user-message-selector.js +103 -0
- package/tui/dist/components/user-message.d.ts +5 -0
- package/tui/dist/components/user-message.js +15 -0
- package/tui/dist/index.d.ts +11 -0
- package/tui/dist/index.js +11 -0
- package/tui/dist/interactive/chat-viewport.d.ts +19 -0
- package/tui/dist/interactive/chat-viewport.js +41 -0
- package/tui/dist/interactive/components.d.ts +1 -0
- package/tui/dist/interactive/components.js +1 -0
- package/tui/dist/interactive/interactive-mode.d.ts +89 -0
- package/tui/dist/interactive/interactive-mode.js +1625 -0
- package/tui/dist/interactive/theme.d.ts +1 -0
- package/tui/dist/interactive/theme.js +1 -0
- package/tui/dist/interactive/tui-renderer.d.ts +8 -0
- package/tui/dist/interactive/tui-renderer.js +10 -0
- package/tui/dist/protocol.d.ts +78 -0
- package/tui/dist/protocol.js +1 -0
- package/tui/dist/theme/dark.json +54 -0
- package/tui/dist/theme/light.json +71 -0
- package/tui/dist/theme/theme.d.ts +20 -0
- package/tui/dist/theme/theme.js +86 -0
- package/tui/package.json +25 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""AgentPaths: 统一管理全局用户目录与项目本地资源的路径调度中心。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
__all__ = ["AgentPaths"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class AgentPaths:
|
|
16
|
+
"""集中解析与管理 my-pi-agent 的全局与项目级路径。"""
|
|
17
|
+
|
|
18
|
+
home: Path = field(
|
|
19
|
+
default_factory=lambda: Path(os.environ.get("MY_AGENT_HOME") or (Path.home() / ".my-pi-agent")).resolve()
|
|
20
|
+
)
|
|
21
|
+
agents_home: Path = field(default_factory=lambda: (Path.home() / ".agents").resolve())
|
|
22
|
+
|
|
23
|
+
# ── 全局资源路径 ──
|
|
24
|
+
@property
|
|
25
|
+
def auth_path(self) -> Path:
|
|
26
|
+
return self.home / "auth.json"
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def auth_lock_path(self) -> Path:
|
|
30
|
+
return self.home / "auth.json.lock"
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def settings_path(self) -> Path:
|
|
34
|
+
return self.home / "settings.json"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def sessions_dir(self) -> Path:
|
|
38
|
+
return self.home / "sessions"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def skills_dir(self) -> Path:
|
|
42
|
+
return self.home / "skills"
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def prompts_dir(self) -> Path:
|
|
46
|
+
return self.home / "prompts"
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def themes_dir(self) -> Path:
|
|
50
|
+
return self.home / "themes"
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def extensions_dir(self) -> Path:
|
|
54
|
+
return self.home / "extensions"
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def logs_dir(self) -> Path:
|
|
58
|
+
return self.home / "logs"
|
|
59
|
+
|
|
60
|
+
# ── 项目局部路径 ──
|
|
61
|
+
def project_agent_dir(self, cwd: Path) -> Path:
|
|
62
|
+
return cwd / ".my-pi-agent"
|
|
63
|
+
|
|
64
|
+
def project_settings_path(self, cwd: Path) -> Path:
|
|
65
|
+
return self.project_agent_dir(cwd) / "settings.json"
|
|
66
|
+
|
|
67
|
+
def project_skills_dir(self, cwd: Path) -> Path:
|
|
68
|
+
return self.project_agent_dir(cwd) / "skills"
|
|
69
|
+
|
|
70
|
+
def project_agents_skills_dir(self, cwd: Path) -> Path:
|
|
71
|
+
return cwd / ".agents" / "skills"
|
|
72
|
+
|
|
73
|
+
# ── 会话分区映射算法 (Tau Slug + Hash 优化版) ──
|
|
74
|
+
def project_session_dir(self, cwd: Path) -> Path:
|
|
75
|
+
"""根据项目 cwd 计算全局唯一的 sessions/<slug>-<hash> 目录。"""
|
|
76
|
+
resolved = cwd.resolve()
|
|
77
|
+
digest = sha256(str(resolved).encode("utf-8")).hexdigest()[:6]
|
|
78
|
+
slug = self._slugify_path(resolved)
|
|
79
|
+
target = self.sessions_dir / f"{slug}-{digest}"
|
|
80
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
return target
|
|
82
|
+
|
|
83
|
+
def default_session_path(self, cwd: Path) -> Path:
|
|
84
|
+
return self.project_session_dir(cwd) / "default.jsonl"
|
|
85
|
+
|
|
86
|
+
def ensure_directories(self) -> None:
|
|
87
|
+
"""初次启动自动建巢,静默初始化目录骨架。"""
|
|
88
|
+
for d in (
|
|
89
|
+
self.home,
|
|
90
|
+
self.sessions_dir,
|
|
91
|
+
self.skills_dir,
|
|
92
|
+
self.prompts_dir,
|
|
93
|
+
self.themes_dir,
|
|
94
|
+
self.extensions_dir,
|
|
95
|
+
self.logs_dir,
|
|
96
|
+
):
|
|
97
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _slugify_path(path: Path, max_length: int = 48) -> str:
|
|
101
|
+
parts = [p for p in path.parts if p not in (path.anchor, "")]
|
|
102
|
+
try:
|
|
103
|
+
rel = path.relative_to(Path.home())
|
|
104
|
+
parts = ["home", *rel.parts]
|
|
105
|
+
except ValueError:
|
|
106
|
+
pass
|
|
107
|
+
normalized = [clean for p in parts if (clean := re.sub(r"[^a-zA-Z0-9._-]+", "-", p).strip(".-_").lower())]
|
|
108
|
+
slug = "-".join(normalized)
|
|
109
|
+
if len(slug) <= max_length:
|
|
110
|
+
return slug or "project"
|
|
111
|
+
|
|
112
|
+
suffix_parts: list[str] = []
|
|
113
|
+
cur_len = 0
|
|
114
|
+
for p in reversed(normalized):
|
|
115
|
+
if cur_len + len(p) + 1 > max_length:
|
|
116
|
+
break
|
|
117
|
+
suffix_parts.append(p)
|
|
118
|
+
cur_len += len(p) + 1
|
|
119
|
+
return "-".join(reversed(suffix_parts)) or slug[-max_length:].strip("-") or "project"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""业务权限门禁(PermissionGate):基于 ToolCallHook 实现的无侵入安全审批门禁。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
from my_agent_core.hooks import HookResult, ToolCallHook
|
|
11
|
+
|
|
12
|
+
PermissionMode = Literal["review", "autonomous", "yolo", "strict"]
|
|
13
|
+
READONLY_TOOLS = frozenset({"read", "grep", "find"})
|
|
14
|
+
SAFE_BASH_PREFIXES = (
|
|
15
|
+
"git status",
|
|
16
|
+
"git diff",
|
|
17
|
+
"git log",
|
|
18
|
+
"pytest",
|
|
19
|
+
"python -m pytest",
|
|
20
|
+
"uv run",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class PermissionRequest:
|
|
26
|
+
"""权限审批请求对象。"""
|
|
27
|
+
|
|
28
|
+
action: str
|
|
29
|
+
target: str
|
|
30
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
31
|
+
preview: str | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PermissionGate:
|
|
35
|
+
"""基于 ToolCallHook 实现的无侵入安全审批门禁。"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
mode: PermissionMode = "review",
|
|
40
|
+
confirm_callback: Callable[[PermissionRequest], Awaitable[bool] | bool] | None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
self.mode: PermissionMode = "autonomous" if mode == "yolo" else mode
|
|
43
|
+
self.confirm_callback = confirm_callback
|
|
44
|
+
|
|
45
|
+
async def __call__(self, hook: ToolCallHook) -> HookResult:
|
|
46
|
+
if self.mode == "autonomous":
|
|
47
|
+
return HookResult()
|
|
48
|
+
|
|
49
|
+
tool_name = hook.tool_name
|
|
50
|
+
args = hook.args or {}
|
|
51
|
+
|
|
52
|
+
# 1. 只读工具放行 (除非 strict 模式)
|
|
53
|
+
if self.mode != "strict" and tool_name in READONLY_TOOLS:
|
|
54
|
+
return HookResult()
|
|
55
|
+
|
|
56
|
+
# 2. 安全 Shell 命令放行
|
|
57
|
+
if tool_name == "bash" and self.mode == "review":
|
|
58
|
+
cmd = str(args.get("command", "")).strip()
|
|
59
|
+
if any(cmd.startswith(p) for p in SAFE_BASH_PREFIXES):
|
|
60
|
+
return HookResult()
|
|
61
|
+
|
|
62
|
+
# 3. 发起用户交互审查
|
|
63
|
+
if not self.confirm_callback:
|
|
64
|
+
# 无交互回调时默认放行
|
|
65
|
+
return HookResult()
|
|
66
|
+
|
|
67
|
+
target = str(args.get("path") or args.get("command") or tool_name)
|
|
68
|
+
preview = None
|
|
69
|
+
if tool_name in ("write", "edit"):
|
|
70
|
+
preview = args.get("content") or str(args.get("edits", ""))
|
|
71
|
+
|
|
72
|
+
req = PermissionRequest(
|
|
73
|
+
action=tool_name,
|
|
74
|
+
target=target,
|
|
75
|
+
details=args,
|
|
76
|
+
preview=preview,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
res = self.confirm_callback(req)
|
|
80
|
+
approved = await res if inspect.isawaitable(res) else res
|
|
81
|
+
if not approved:
|
|
82
|
+
return HookResult(block=True, reason=f"用户拒绝了工具 [{tool_name}] 的执行请求。")
|
|
83
|
+
|
|
84
|
+
return HookResult()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""专业编码系统提示词与上下文自动注入。"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
DEFAULT_CODING_INSTRUCTIONS = """You are an expert coding assistant operating inside the codebase.
|
|
9
|
+
|
|
10
|
+
Core Engineering Principles:
|
|
11
|
+
1. Read Before Edit: Always inspect file contents with 'read' before calling 'edit'. Never guess line numbers or code blocks.
|
|
12
|
+
2. Surgical Edits: Keep edits concise and provide sufficient context in 'oldText' to ensure a unique match.
|
|
13
|
+
3. Verification Before Completion: Run tests or linters using 'bash' to verify changes before declaring work complete.
|
|
14
|
+
4. Minimal Changes: Do not add unsolicited refactorings, comments, or unnecessary abstractions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_default_coding_prompt(workspace: Path | str) -> str:
|
|
19
|
+
"""构建专业编码系统提示词,自动扫描并注入项目指导文件。
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
workspace: 工作区目录路径(支持 Path 或 str)。
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
包含工程规范、工作区路径以及项目上下文的完整系统提示词。
|
|
26
|
+
"""
|
|
27
|
+
workspace_path = Path(workspace).resolve()
|
|
28
|
+
sections = [
|
|
29
|
+
DEFAULT_CODING_INSTRUCTIONS,
|
|
30
|
+
f"Workspace Directory: {workspace_path}",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# 扫描并注入项目指导文件
|
|
34
|
+
context_files = ["AGENTS.md", "CLAUDE.md", "README.md"]
|
|
35
|
+
injected_contexts = []
|
|
36
|
+
for fname in context_files:
|
|
37
|
+
fpath = workspace_path / fname
|
|
38
|
+
if fpath.is_file():
|
|
39
|
+
try:
|
|
40
|
+
content = fpath.read_text(encoding="utf-8", errors="replace")
|
|
41
|
+
injected_contexts.append(
|
|
42
|
+
f'<project_instructions path="{fpath}">\n{content}\n</project_instructions>'
|
|
43
|
+
)
|
|
44
|
+
except Exception:
|
|
45
|
+
logger.debug("Failed to read context file: %s", fpath, exc_info=True)
|
|
46
|
+
|
|
47
|
+
if injected_contexts:
|
|
48
|
+
sections.append(
|
|
49
|
+
"<project_context>\n"
|
|
50
|
+
+ "\n\n".join(injected_contexts)
|
|
51
|
+
+ "\n</project_context>"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return "\n\n".join(sections)
|