mosscode 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.
- mosscode/__init__.py +10 -0
- mosscode/__main__.py +3 -0
- mosscode/agent.py +486 -0
- mosscode/agents/__init__.py +249 -0
- mosscode/agents/builtin/explore.md +16 -0
- mosscode/agents/builtin/general.md +12 -0
- mosscode/agents/builtin/plan.md +16 -0
- mosscode/agents/runtime.py +685 -0
- mosscode/agents/tasks.py +193 -0
- mosscode/appearance.py +184 -0
- mosscode/builtin_commands.py +1363 -0
- mosscode/cli.py +547 -0
- mosscode/commands.py +254 -0
- mosscode/compaction.py +259 -0
- mosscode/config.py +337 -0
- mosscode/context.py +102 -0
- mosscode/conversation.py +1046 -0
- mosscode/coordinator.py +54 -0
- mosscode/doctor.py +303 -0
- mosscode/hooks/__init__.py +22 -0
- mosscode/hooks/actions.py +151 -0
- mosscode/hooks/conditions.py +170 -0
- mosscode/hooks/config.py +404 -0
- mosscode/hooks/engine.py +401 -0
- mosscode/hooks/events.py +48 -0
- mosscode/launcher.py +177 -0
- mosscode/mcp_client.py +460 -0
- mosscode/mcp_config.py +216 -0
- mosscode/member_ui.py +289 -0
- mosscode/memory.py +291 -0
- mosscode/memory_extract.py +245 -0
- mosscode/messages.py +216 -0
- mosscode/paths.py +31 -0
- mosscode/permissions.py +733 -0
- mosscode/prompts.py +483 -0
- mosscode/providers.py +587 -0
- mosscode/scrollback.py +106 -0
- mosscode/security.py +282 -0
- mosscode/serialization.py +121 -0
- mosscode/session.py +563 -0
- mosscode/setup.py +248 -0
- mosscode/skills/__init__.py +455 -0
- mosscode/skills/builtin/commit/SKILL.md +15 -0
- mosscode/skills/builtin/review/SKILL.md +15 -0
- mosscode/skills/builtin/test/SKILL.md +13 -0
- mosscode/skills/builtin/test/scripts/detect.py +96 -0
- mosscode/skills/builtin/test/tool.json +9 -0
- mosscode/teams/__init__.py +216 -0
- mosscode/teams/backends.py +259 -0
- mosscode/teams/board.py +230 -0
- mosscode/teams/lock.py +173 -0
- mosscode/teams/mailbox.py +172 -0
- mosscode/teams/member.py +427 -0
- mosscode/teams/merge.py +207 -0
- mosscode/teams/pane.py +20 -0
- mosscode/teams/pane_app.py +167 -0
- mosscode/teams/process.py +371 -0
- mosscode/teams/runtime.py +1355 -0
- mosscode/teams/view.py +182 -0
- mosscode/tool_budget.py +86 -0
- mosscode/tools/__init__.py +38 -0
- mosscode/tools/agent.py +127 -0
- mosscode/tools/base.py +384 -0
- mosscode/tools/diffing.py +16 -0
- mosscode/tools/execution.py +99 -0
- mosscode/tools/files.py +287 -0
- mosscode/tools/memory.py +65 -0
- mosscode/tools/plan.py +30 -0
- mosscode/tools/process.py +216 -0
- mosscode/tools/search.py +229 -0
- mosscode/tools/search_worker.py +243 -0
- mosscode/tools/shell.py +121 -0
- mosscode/tools/skill.py +118 -0
- mosscode/tools/team.py +447 -0
- mosscode/tools/windows_job.py +75 -0
- mosscode/tui.py +2325 -0
- mosscode/usage.py +68 -0
- mosscode/windows_inline.py +148 -0
- mosscode/worktree/__init__.py +29 -0
- mosscode/worktree/changes.py +59 -0
- mosscode/worktree/git.py +125 -0
- mosscode/worktree/manager.py +462 -0
- mosscode/worktree/meta.py +82 -0
- mosscode/worktree/names.py +52 -0
- mosscode/worktree/setup.py +119 -0
- mosscode-0.1.0.dist-info/METADATA +92 -0
- mosscode-0.1.0.dist-info/RECORD +91 -0
- mosscode-0.1.0.dist-info/WHEEL +5 -0
- mosscode-0.1.0.dist-info/entry_points.txt +3 -0
- mosscode-0.1.0.dist-info/licenses/LICENSE +21 -0
- mosscode-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Sub-agent definitions: Markdown roles with YAML frontmatter, in three places."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import dataclass, field, replace
|
|
8
|
+
from importlib import resources
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from ..paths import mosscode_home
|
|
13
|
+
from ..permissions import ALIASES, MODES
|
|
14
|
+
from ..skills import NAME, NAME_LIMIT, SkillError, _field, _read, _split, _text, split_tool_list
|
|
15
|
+
|
|
16
|
+
Source = Literal["builtin", "user", "project"]
|
|
17
|
+
SOURCE_LABELS = {"builtin": "内置", "user": "用户", "project": "项目"}
|
|
18
|
+
DESCRIPTION_LIMIT = 1024
|
|
19
|
+
CATALOG_DESCRIPTION = 250
|
|
20
|
+
CATALOG_LIMIT = 8000
|
|
21
|
+
FORK = "fork"
|
|
22
|
+
# snake_case first, then the Claude Code and hyphen spellings.
|
|
23
|
+
FIELDS = {
|
|
24
|
+
"name": ("name",),
|
|
25
|
+
"description": ("description",),
|
|
26
|
+
"tools": ("tools",),
|
|
27
|
+
"disallowed_tools": ("disallowed_tools", "disallowedTools", "disallowed-tools"),
|
|
28
|
+
"model": ("model",),
|
|
29
|
+
"max_turns": ("max_turns", "maxTurns", "max-turns"),
|
|
30
|
+
"permission_mode": ("permission_mode", "permissionMode", "permission-mode"),
|
|
31
|
+
"background": ("background",),
|
|
32
|
+
"isolation": ("isolation",),
|
|
33
|
+
}
|
|
34
|
+
# Claude Code fields MossCode does not support; ignored and reported.
|
|
35
|
+
ISOLATIONS = ("none", "worktree")
|
|
36
|
+
UNSUPPORTED = ("color", "effort", "skills", "mcpServers", "hooks", "memory", "initialPrompt")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AgentError(ValueError):
|
|
40
|
+
"""One definition file is unusable; the message names the file and the field."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class AgentDef:
|
|
45
|
+
name: str
|
|
46
|
+
description: str
|
|
47
|
+
prompt: str # The body: the sub-agent's system prompt.
|
|
48
|
+
path: Path
|
|
49
|
+
source: Source
|
|
50
|
+
tools: tuple[str, ...] = () # Aliases expanded; empty means no whitelist.
|
|
51
|
+
disallowed_tools: tuple[str, ...] = ()
|
|
52
|
+
model: str | None = None # None: inherit.
|
|
53
|
+
max_turns: int | None = None
|
|
54
|
+
permission_mode: str | None = None
|
|
55
|
+
background: bool = False
|
|
56
|
+
isolation: str = "none" # "worktree" runs it in its own git worktree.
|
|
57
|
+
unsupported: tuple[str, ...] = ()
|
|
58
|
+
overrides: tuple[Path, ...] = () # Same-named files this one replaced.
|
|
59
|
+
|
|
60
|
+
def allows(self, tool: str) -> bool:
|
|
61
|
+
"""The blacklist first, then the whitelist."""
|
|
62
|
+
if any(_matches(tool, pattern) for pattern in self.disallowed_tools):
|
|
63
|
+
return False
|
|
64
|
+
return not self.tools or any(_matches(tool, pattern) for pattern in self.tools)
|
|
65
|
+
|
|
66
|
+
def unknown_tools(self, available: Iterable[str]) -> list[str]:
|
|
67
|
+
"""Names that match no registered tool now (MCP servers may not be connected yet)."""
|
|
68
|
+
available = tuple(available)
|
|
69
|
+
return [
|
|
70
|
+
pattern
|
|
71
|
+
for pattern in (*self.tools, *self.disallowed_tools)
|
|
72
|
+
if not any(_matches(name, pattern) for name in available)
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _matches(tool: str, pattern: str) -> bool:
|
|
77
|
+
return fnmatch.fnmatchcase(tool, pattern) if "*" in pattern else tool == pattern
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _tools(meta: dict, path: Path, *names: str) -> tuple[str, ...]:
|
|
81
|
+
key, value = _field(meta, path, *names)
|
|
82
|
+
if value is None:
|
|
83
|
+
return ()
|
|
84
|
+
if isinstance(value, str):
|
|
85
|
+
raw = split_tool_list(value)
|
|
86
|
+
elif isinstance(value, list) and all(isinstance(item, str) for item in value):
|
|
87
|
+
raw = [item.strip() for item in value if item.strip()]
|
|
88
|
+
else:
|
|
89
|
+
raise AgentError(f"{path}:{key} 必须是工具名列表或用逗号分隔的字符串。")
|
|
90
|
+
items = []
|
|
91
|
+
for item in raw:
|
|
92
|
+
name = item.split("(", 1)[0].strip() # Bash(git:*) -> Bash: the argument part is ignored.
|
|
93
|
+
items.append(ALIASES.get(name, name))
|
|
94
|
+
return tuple(dict.fromkeys(items))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_agent(path: Path, source: Source) -> AgentDef:
|
|
98
|
+
try:
|
|
99
|
+
meta, body = _split(_read(path), path)
|
|
100
|
+
known = {name for names in FIELDS.values() for name in names}
|
|
101
|
+
unknown = [key for key in meta if key not in known and key not in UNSUPPORTED]
|
|
102
|
+
if unknown:
|
|
103
|
+
raise AgentError(f"{path}:未知字段 {', '.join(map(str, unknown))}。")
|
|
104
|
+
name = _text(meta, path, "name", required=True)
|
|
105
|
+
description = " ".join(_text(meta, path, "description", required=True).split())
|
|
106
|
+
model = _text(meta, path, "model") or None
|
|
107
|
+
except SkillError as exc:
|
|
108
|
+
raise AgentError(str(exc)) from None
|
|
109
|
+
if not NAME.fullmatch(name) or len(name) > NAME_LIMIT:
|
|
110
|
+
raise AgentError(f"{path}:name 须以小写字母开头,只含小写字母、数字和 -,最长 {NAME_LIMIT} 个字符。")
|
|
111
|
+
if name == FORK:
|
|
112
|
+
raise AgentError(f"{path}:name 不能是 fork(省略类型即为 Fork 式)。")
|
|
113
|
+
if len(description) > DESCRIPTION_LIMIT:
|
|
114
|
+
raise AgentError(f"{path}:description 超过 {DESCRIPTION_LIMIT} 个字符。")
|
|
115
|
+
if not body:
|
|
116
|
+
raise AgentError(f"{path}:正文为空。")
|
|
117
|
+
try:
|
|
118
|
+
tools = _tools(meta, path, *FIELDS["tools"])
|
|
119
|
+
disallowed = _tools(meta, path, *FIELDS["disallowed_tools"])
|
|
120
|
+
key, turns = _field(meta, path, *FIELDS["max_turns"])
|
|
121
|
+
mode_key, mode = _field(meta, path, *FIELDS["permission_mode"])
|
|
122
|
+
except SkillError as exc:
|
|
123
|
+
raise AgentError(str(exc)) from None
|
|
124
|
+
if turns is not None and (type(turns) is not int or turns <= 0):
|
|
125
|
+
raise AgentError(f"{path}:{key} 必须是正整数。")
|
|
126
|
+
if mode is not None and mode not in MODES:
|
|
127
|
+
raise AgentError(f"{path}:{mode_key} 只能是 {' / '.join(MODES)},当前为 {mode!r}。")
|
|
128
|
+
background = meta.get("background", False)
|
|
129
|
+
if not isinstance(background, bool):
|
|
130
|
+
raise AgentError(f"{path}:background 必须是 true 或 false。")
|
|
131
|
+
isolation = meta.get("isolation", "none")
|
|
132
|
+
if isolation not in ISOLATIONS:
|
|
133
|
+
raise AgentError(f"{path}:isolation 只能是 {' / '.join(ISOLATIONS)},当前为 {isolation!r}。")
|
|
134
|
+
if model == "inherit":
|
|
135
|
+
model = None
|
|
136
|
+
return AgentDef(
|
|
137
|
+
name,
|
|
138
|
+
description,
|
|
139
|
+
body,
|
|
140
|
+
path,
|
|
141
|
+
source,
|
|
142
|
+
tools,
|
|
143
|
+
disallowed,
|
|
144
|
+
model,
|
|
145
|
+
turns,
|
|
146
|
+
mode,
|
|
147
|
+
background,
|
|
148
|
+
isolation,
|
|
149
|
+
tuple(key for key in UNSUPPORTED if key in meta),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Catalog ----------------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def builtin_dir() -> Path:
|
|
157
|
+
return Path(str(resources.files(__name__).joinpath("builtin")))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def agent_dirs(root: Path, home: Path | None = None, builtin: Path | None = None) -> list[tuple[Source, Path]]:
|
|
161
|
+
"""Lowest priority first."""
|
|
162
|
+
return [
|
|
163
|
+
("builtin", builtin if builtin is not None else builtin_dir()),
|
|
164
|
+
("user", mosscode_home(home) / "agents"),
|
|
165
|
+
("project", root / ".mosscode" / "agents"),
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class AgentCatalog:
|
|
171
|
+
agents: dict[str, AgentDef] = field(default_factory=dict)
|
|
172
|
+
problems: tuple[str, ...] = ()
|
|
173
|
+
_origin: tuple = field(default=(), repr=False)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def load(cls, root: Path, home: Path | None = None, *, builtin: Path | None = None) -> AgentCatalog:
|
|
177
|
+
"""A later place replaces a same-named definition; within a place the first file name wins."""
|
|
178
|
+
agents: dict[str, AgentDef] = {}
|
|
179
|
+
problems: list[str] = []
|
|
180
|
+
for source, base in agent_dirs(root, home, builtin):
|
|
181
|
+
try:
|
|
182
|
+
files = sorted(child for child in base.iterdir() if child.is_file() and child.suffix == ".md")
|
|
183
|
+
except OSError:
|
|
184
|
+
continue
|
|
185
|
+
seen: dict[str, Path] = {}
|
|
186
|
+
for path in files:
|
|
187
|
+
try:
|
|
188
|
+
agent = parse_agent(path, source)
|
|
189
|
+
except AgentError as exc:
|
|
190
|
+
problems.append(f"Agent 定义已跳过:{exc}")
|
|
191
|
+
continue
|
|
192
|
+
if agent.name in seen:
|
|
193
|
+
problems.append(f"Agent 定义已跳过:{path}:name {agent.name} 与 {seen[agent.name]} 重复。")
|
|
194
|
+
continue
|
|
195
|
+
seen[agent.name] = path
|
|
196
|
+
previous = agents.get(agent.name)
|
|
197
|
+
if previous is not None:
|
|
198
|
+
agent = replace(agent, overrides=previous.overrides + (previous.path,))
|
|
199
|
+
agents[agent.name] = agent
|
|
200
|
+
return cls(dict(sorted(agents.items())), tuple(problems), _origin=(root, home, builtin))
|
|
201
|
+
|
|
202
|
+
def reload(self) -> AgentCatalog:
|
|
203
|
+
if not self._origin:
|
|
204
|
+
return self
|
|
205
|
+
root, home, builtin = self._origin
|
|
206
|
+
return AgentCatalog.load(root, home, builtin=builtin)
|
|
207
|
+
|
|
208
|
+
def get(self, name: str) -> AgentDef | None:
|
|
209
|
+
return self.agents.get(name)
|
|
210
|
+
|
|
211
|
+
def names(self) -> tuple[str, ...]:
|
|
212
|
+
return tuple(self.agents)
|
|
213
|
+
|
|
214
|
+
def catalog_section(self) -> str:
|
|
215
|
+
"""What the model sees; the agent tool's own description never lists types."""
|
|
216
|
+
lines, size = [], 0
|
|
217
|
+
for agent in self.agents.values():
|
|
218
|
+
description = agent.description
|
|
219
|
+
if len(description) > CATALOG_DESCRIPTION:
|
|
220
|
+
description = description[: CATALOG_DESCRIPTION - 1] + "…"
|
|
221
|
+
line = f"- {agent.name}:{description}(工具:{tool_summary(agent)})"
|
|
222
|
+
if size + len(line) + 1 > CATALOG_LIMIT:
|
|
223
|
+
continue
|
|
224
|
+
lines.append(line)
|
|
225
|
+
size += len(line) + 1
|
|
226
|
+
text = (
|
|
227
|
+
"## 子 Agent\n用 agent 工具把可以独立完成的工作交给子 Agent,主对话只收到结论。"
|
|
228
|
+
"agent_type 写下列类型之一时,子 Agent 从空白对话开始,按该类型的角色工作,prompt 要写清楚完整的任务和背景;"
|
|
229
|
+
"省略 agent_type 时为 Fork:分身继承当前对话的全部内容,在后台运行,完成后通知你。"
|
|
230
|
+
"多个互不依赖的子任务可以在一次回复中同时发起。"
|
|
231
|
+
)
|
|
232
|
+
if lines:
|
|
233
|
+
text += "\n\n" + "\n".join(lines)
|
|
234
|
+
return text
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def tool_summary(agent: AgentDef) -> str:
|
|
238
|
+
parts = []
|
|
239
|
+
if agent.permission_mode == "plan":
|
|
240
|
+
parts.append("只读")
|
|
241
|
+
if agent.tools:
|
|
242
|
+
parts.append("、".join(agent.tools))
|
|
243
|
+
elif not parts:
|
|
244
|
+
parts.append("全部")
|
|
245
|
+
if agent.disallowed_tools:
|
|
246
|
+
parts.append("不含 " + "、".join(agent.disallowed_tools))
|
|
247
|
+
if agent.isolation == "worktree":
|
|
248
|
+
parts.append("在独立的工作树中运行")
|
|
249
|
+
return ",".join(parts)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explore
|
|
3
|
+
description: 只读的代码探索:查找某个功能的实现、理清调用链、了解项目结构。不修改任何文件。prompt 中写清楚要找什么、找到后需要报告哪些信息。
|
|
4
|
+
permission_mode: plan
|
|
5
|
+
max_turns: 30
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
你是 MossCode 的代码探索子 Agent。这是一个只读任务:不要创建、修改或删除文件,不要运行改变系统状态的命令。
|
|
9
|
+
|
|
10
|
+
工作方法:
|
|
11
|
+
- 用 glob 按文件名查找,用 grep 搜索内容,用 read_file 读取已知路径;
|
|
12
|
+
- 只读命令(如 git log、git diff、ls)可以用 execute_command;
|
|
13
|
+
- 互不依赖的搜索和读取,在一次回复中同时发起;
|
|
14
|
+
- 先广泛搜索确定范围,再读取关键文件,不要逐个读取无关文件。
|
|
15
|
+
|
|
16
|
+
完成后给出简洁的报告:直接回答问题,列出关键文件路径(必要时带行号)和依据。调用方会把结论转述给用户,只写要点。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: general
|
|
3
|
+
description: 通用子 Agent,拥有与主对话相同的工具,用于需要完整能力、但过程不必留在主对话中的多步任务(例如批量修改、运行并修复测试)。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
你是 MossCode 的子 Agent。根据任务说明,使用可用工具把任务做完:不要过度设计,也不要做一半就停。
|
|
7
|
+
|
|
8
|
+
- 不确定位置时先广泛搜索,确定路径后直接读取;
|
|
9
|
+
- 优先修改已有文件,不要主动创建文档文件;
|
|
10
|
+
- 你无法向用户提问;遇到需要用户决定的事,在报告中说明。
|
|
11
|
+
|
|
12
|
+
完成后用简洁的报告回复:做了什么、关键发现、改动了哪些文件、还有什么没完成。调用方会把结论转述给用户,只写要点。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: 只读的方案规划:探索代码库后给出分步实现方案,不执行修改。适合需要长时间分析、又不想占用主对话上下文的设计工作。
|
|
4
|
+
permission_mode: plan
|
|
5
|
+
max_turns: 30
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
你是 MossCode 的规划子 Agent。这是一个只读任务:不要创建、修改或删除文件,不要运行改变系统状态的命令。
|
|
9
|
+
|
|
10
|
+
工作流程:
|
|
11
|
+
1. 理解需求,明确要解决的问题和约束;
|
|
12
|
+
2. 用 glob、grep、read_file 探索代码库:找到现有的模式和约定,理解相关的架构,找出可以参考的类似功能;
|
|
13
|
+
3. 设计方案:确定实现路径,说明取舍;
|
|
14
|
+
4. 输出计划:分步的实现策略,标明依赖和顺序,指出可能的风险。
|
|
15
|
+
|
|
16
|
+
报告末尾列出 3–5 个对实现最关键的文件路径。
|