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,197 @@
1
+ """extension 机制 —— 外部扩展加载(事件订阅 + 工具注册 + 命令)。
2
+
3
+ ExtensionAPI 是传给 extension 的能力面(on / tool / command 三件套);
4
+ ExtensionManager 是 Repository(发现 / 加载 / 命令调度),对标 SkillManager /
5
+ SubagentManager。加载协议:扩展文件定义 `def extension(api)`(或 default /
6
+ 第一个形参名含 api 的函数),由 importlib 动态加载。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import importlib.util
12
+ import inspect
13
+ from collections.abc import Callable, Sequence
14
+ from pathlib import Path
15
+ from typing import TYPE_CHECKING, Any, overload
16
+
17
+ from my_agent_core.events import Event
18
+ from my_agent_core.tools import Tool
19
+ from my_agent_core.tools import tool as _tool
20
+
21
+ if TYPE_CHECKING:
22
+ from my_agent_core.agent import Agent
23
+
24
+ CommandHandler = Callable[..., Any]
25
+
26
+
27
+ class ExtensionAPI:
28
+ """暴露给 extension 的能力面:事件订阅 + 工具注册 + 命令。"""
29
+
30
+ def __init__(self, agent: Agent) -> None:
31
+ self.agent = agent
32
+ self._commands: dict[str, CommandHandler] = {}
33
+ self._descriptions: dict[str, str] = {}
34
+
35
+ # ── 事件订阅 / Hook 拦截注册(支持 @api.on 作装饰器)────────
36
+
37
+ @overload
38
+ def on(
39
+ self, target: type, handler: None = None
40
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ...
41
+
42
+ @overload
43
+ def on(self, target: type, handler: Callable[..., Any]) -> None: ...
44
+
45
+ def on(self, target: type, handler: Callable[..., Any] | None = None) -> Any:
46
+ """注册只读事件订阅(Event 监听)或双向 Hook 拦截点回调(Hook 拦截干预)。
47
+
48
+ handler 签名统一为 (payload, api)。
49
+ - 若 target 是 Event 子类:注册至 agent.subscribe() 作为只读通知,忽略返回值;
50
+ - 否则:注册至 agent.hooks.register(),可返回 HookResult 进行干预。
51
+ """
52
+
53
+ def _register(h: Callable[..., Any]) -> Callable[..., Any]:
54
+ if isinstance(target, type) and issubclass(target, Event):
55
+
56
+ def event_listener(event: Event) -> Any:
57
+ if isinstance(event, target):
58
+ return h(event, self)
59
+
60
+ self.agent.subscribe(event_listener)
61
+ else:
62
+ if inspect.iscoroutinefunction(h):
63
+
64
+ async def wrapped_async(hook_payload: Any) -> Any:
65
+ return await h(hook_payload, self)
66
+
67
+ self.agent.hooks.register(target, wrapped_async)
68
+ else:
69
+
70
+ def wrapped_sync(hook_payload: Any) -> Any:
71
+ return h(hook_payload, self)
72
+
73
+ self.agent.hooks.register(target, wrapped_sync)
74
+ return h
75
+
76
+ if handler is not None:
77
+ _register(handler)
78
+ return None
79
+ return _register
80
+
81
+ # ── 工具注册(复用 @tool)────────────────────────────────────────
82
+
83
+ def register_tool(self, tool: Tool) -> None:
84
+ """注册工具 → agent.registry(撞名静默覆盖,registry 语义)。"""
85
+ self.agent.registry.register(tool)
86
+
87
+ def tool(self, **kwargs: Any):
88
+ """@api.tool(description=...) 装饰器:@tool 包装 + register_tool。"""
89
+
90
+ def decorator(func) -> Tool:
91
+ t = _tool(**kwargs)(func)
92
+ self.register_tool(t)
93
+ return t
94
+
95
+ return decorator
96
+
97
+ # ── 命令(注册 + 存表,调度在 ExtensionManager)────────────────────
98
+
99
+ def register_command(
100
+ self, name: str, handler: CommandHandler, description: str = ""
101
+ ) -> None:
102
+ """注册命令(name 不含 /)。"""
103
+ self._commands[name] = handler
104
+ self._descriptions[name] = description
105
+
106
+ def command(self, name: str, description: str = ""):
107
+ """@api.command("now") 装饰器。"""
108
+
109
+ def decorator(func: CommandHandler) -> CommandHandler:
110
+ self.register_command(name, func, description)
111
+ return func
112
+
113
+ return decorator
114
+
115
+ def get_commands(self) -> dict[str, CommandHandler]:
116
+ """已注册命令的拷贝(name → handler)。"""
117
+ return self._commands.copy()
118
+
119
+
120
+ class ExtensionManager:
121
+ """扩展管理器(Repository):发现 / 加载 / 命令调度。"""
122
+
123
+ DEFAULT_DIR_NAME = "extensions" # <cwd>/.agents/extensions
124
+
125
+ def __init__(
126
+ self, agent: Agent, extension_dirs: Sequence[str | Path] | None = None
127
+ ):
128
+ """解析目录(三态同 skill_dirs):None → 探测 <cwd>/.agents/extensions;
129
+ [] → 禁用;非空 → 只扫这些目录。load() 显式加载(有副作用)。"""
130
+ self.agent = agent
131
+ self.api = ExtensionAPI(agent)
132
+ self.extensions: dict[str, Any] = {}
133
+ if extension_dirs is None:
134
+ dirs = [Path.cwd() / ".agents" / self.DEFAULT_DIR_NAME]
135
+ else:
136
+ dirs = list(extension_dirs)
137
+ self._dirs: list[Path] = [Path(d) for d in dirs]
138
+
139
+ def discover(self, directory: Path | str) -> list[Path]:
140
+ """扫目录下 **/*.py(递归),跳过 _ 开头私有文件;目录不存在 → []。"""
141
+ directory = Path(directory)
142
+ if not directory.exists():
143
+ return []
144
+ return [p for p in directory.glob("**/*.py") if not p.name.startswith("_")]
145
+
146
+ async def load_extension(self, path: Path | str) -> None:
147
+ """importlib 动态加载 .py;找 extension → default → 第一个形参名含 api 的函数;
148
+ 找不到 → ValueError;找到则 extension_func(self.api) 执行并登记。支持 async/sync 扩展。"""
149
+ path = Path(path)
150
+ if not path.exists():
151
+ raise FileNotFoundError(f"Extension not found: {path}")
152
+ spec = importlib.util.spec_from_file_location(path.stem, path)
153
+ if spec is None or spec.loader is None:
154
+ raise ImportError(f"Cannot load extension: {path}")
155
+ module = importlib.util.module_from_spec(spec)
156
+ spec.loader.exec_module(module)
157
+
158
+ extension_func = None
159
+ if hasattr(module, "extension"):
160
+ extension_func = module.extension
161
+ elif hasattr(module, "default"):
162
+ extension_func = module.default
163
+ else:
164
+ for _name, obj in inspect.getmembers(module, inspect.isfunction):
165
+ sig = inspect.signature(obj)
166
+ params = list(sig.parameters.values())
167
+ if params and "api" in params[0].name.lower():
168
+ extension_func = obj
169
+ break
170
+ if extension_func is None:
171
+ raise ValueError(
172
+ f"Extension {path} must define an 'extension' function that takes ExtensionAPI"
173
+ )
174
+ if inspect.iscoroutinefunction(extension_func):
175
+ await extension_func(self.api)
176
+ else:
177
+ extension_func(self.api)
178
+ self.extensions[path.name] = module
179
+
180
+ async def load(self) -> None:
181
+ """遍历 self._dirs 逐个 load_extension;单个失败只 print 不抛(隔离坏扩展)。"""
182
+ for directory in self._dirs:
183
+ for path in self.discover(directory):
184
+ try:
185
+ await self.load_extension(path)
186
+ except Exception as exc:
187
+ print(f"Failed to load extension {path}: {exc}")
188
+
189
+ def handle_command(self, command: str, args: str | None = None) -> Any:
190
+ """查表调用命令(未知命令 ValueError;0 参直接调,>0 参传 args)。"""
191
+ commands = self.api.get_commands()
192
+ if command not in commands:
193
+ raise ValueError(f"Unknown command: /{command}")
194
+ handler = commands[command]
195
+ if len(inspect.signature(handler).parameters) > 0:
196
+ return handler(args)
197
+ return handler()
@@ -0,0 +1,130 @@
1
+ """专职 Hook 拦截点契约与 HookRegistry —— Agent 关键节点双向拦截干预与控制流门禁。
2
+
3
+ 架构设计(对齐 Pi 架构):
4
+ 1. 专职 Hook 拦截点契约(*Hook):独立于 Event,专职控制流拦截、入参安全审批与出参改写。
5
+ 2. 统一干预结果模型(HookResult):携带 block、reason、updated_* 属性,指示核心状态机如何处置。
6
+ 3. HookRegistry:负责拦截钩子的注册、注销、async/sync 混合调用及严格的 Never-Throw 异常隔离。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import contextlib
12
+ import inspect
13
+ import logging
14
+ from collections.abc import Callable
15
+ from dataclasses import dataclass
16
+ from typing import Any
17
+
18
+ from my_agent_llm import Message # pyright: ignore[reportMissingImports]
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ # ── 五大专职 Hook 拦截点契约(独立门禁系统,非 Event)
24
+ @dataclass(frozen=True)
25
+ class UserInputHook:
26
+ """Hook 1 (input): 拦截或改写用户原始输入文本。"""
27
+
28
+ input_text: str
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class AgentStartHook:
33
+ """Hook 2 (before_agent_start): 拦截启动或动态重写 system_prompt。"""
34
+
35
+ system_prompt: str
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class BeforeModelCallHook:
40
+ """Hook 3 (context): 调模型前 1ms 审查或临时改写发送视图。"""
41
+
42
+ messages: list[Message]
43
+ iteration: int
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class ToolCallHook:
48
+ """Hook 4 (tool_call): 工具执行前安全审批、阻断高危命令或改写入参。"""
49
+
50
+ tool_call_id: str
51
+ tool_name: str
52
+ args: dict[str, Any]
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class ToolResultHook:
57
+ """Hook 5 (tool_result): 工具执行后改写返回内容或篡改报错状态。"""
58
+
59
+ tool_call_id: str
60
+ tool_name: str
61
+ result: str
62
+ is_error: bool
63
+ terminate: bool = False
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class HookResult:
68
+ """Hook 拦截点的统一干预结果。返回 None = 纯观察,返回 HookResult = 干预。
69
+
70
+ - UserInputHook 用 block / reason / updated_input(拦截 / 改写用户输入)
71
+ - AgentStartHook 用 block / reason / updated_system_prompt(拦截 / 改写 system prompt)
72
+ - BeforeModelCallHook 用 block / reason / updated_messages(拦截 / 临时改写送给 LLM 的 messages 视图)
73
+ - ToolCallHook 用 block / reason / updated_args(拦截 / 改参数)
74
+ - ToolResultHook 用 updated_result(改结果)
75
+ """
76
+
77
+ block: bool = False
78
+ reason: str | None = None
79
+ updated_input: str | None = None
80
+ updated_system_prompt: str | None = None
81
+ updated_messages: list[Message] | None = None
82
+ updated_args: dict[str, Any] | None = None
83
+ updated_result: str | None = None
84
+ terminate: bool | None = None
85
+
86
+
87
+ class HookRegistry:
88
+ """Hook 注册表:Hook 类型 → callback 列表。
89
+
90
+ 支持 async / sync 钩子混合执行与 Never-Throw 异常捕获隔离。
91
+ """
92
+
93
+ def __init__(self) -> None:
94
+ self._handlers: dict[type, list[Callable[..., Any]]] = {}
95
+ self._hooks = self._handlers
96
+
97
+ def register(self, hook_cls: type, callback: Callable[..., Any]) -> None:
98
+ """挂一个 hook 回调到 hook 类型。同一 hook 可挂多个,按注册顺序触发。"""
99
+ self._handlers.setdefault(hook_cls, []).append(callback)
100
+
101
+ def unregister(self, hook_cls: type, callback: Callable[..., Any]) -> None:
102
+ """移除 hook 回调。"""
103
+ with contextlib.suppress(ValueError):
104
+ self._handlers.get(hook_cls, []).remove(callback)
105
+
106
+ async def emit(self, hook_payload: Any) -> HookResult | None:
107
+ """异步触发 hook 的所有回调,支持协程与普通函数。
108
+
109
+ - 返回第一个非 None 结果(短路)。
110
+ - 坚守 Never-Throw 保证:若回调执行抛出异常,捕获并记录日志,绝不向外抛出异常,继续执行后续回调。
111
+ """
112
+ for cb in list(self._handlers.get(type(hook_payload), [])):
113
+ try:
114
+ if inspect.iscoroutinefunction(cb):
115
+ result = await cb(hook_payload)
116
+ else:
117
+ result = cb(hook_payload)
118
+ if inspect.isawaitable(result):
119
+ result = await result
120
+ if result is not None:
121
+ return result
122
+ except Exception as e:
123
+ logger.error(
124
+ "Hook callback %r failed on %r: %s",
125
+ cb,
126
+ hook_payload,
127
+ e,
128
+ exc_info=True,
129
+ )
130
+ return None