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.
Files changed (91) hide show
  1. mosscode/__init__.py +10 -0
  2. mosscode/__main__.py +3 -0
  3. mosscode/agent.py +486 -0
  4. mosscode/agents/__init__.py +249 -0
  5. mosscode/agents/builtin/explore.md +16 -0
  6. mosscode/agents/builtin/general.md +12 -0
  7. mosscode/agents/builtin/plan.md +16 -0
  8. mosscode/agents/runtime.py +685 -0
  9. mosscode/agents/tasks.py +193 -0
  10. mosscode/appearance.py +184 -0
  11. mosscode/builtin_commands.py +1363 -0
  12. mosscode/cli.py +547 -0
  13. mosscode/commands.py +254 -0
  14. mosscode/compaction.py +259 -0
  15. mosscode/config.py +337 -0
  16. mosscode/context.py +102 -0
  17. mosscode/conversation.py +1046 -0
  18. mosscode/coordinator.py +54 -0
  19. mosscode/doctor.py +303 -0
  20. mosscode/hooks/__init__.py +22 -0
  21. mosscode/hooks/actions.py +151 -0
  22. mosscode/hooks/conditions.py +170 -0
  23. mosscode/hooks/config.py +404 -0
  24. mosscode/hooks/engine.py +401 -0
  25. mosscode/hooks/events.py +48 -0
  26. mosscode/launcher.py +177 -0
  27. mosscode/mcp_client.py +460 -0
  28. mosscode/mcp_config.py +216 -0
  29. mosscode/member_ui.py +289 -0
  30. mosscode/memory.py +291 -0
  31. mosscode/memory_extract.py +245 -0
  32. mosscode/messages.py +216 -0
  33. mosscode/paths.py +31 -0
  34. mosscode/permissions.py +733 -0
  35. mosscode/prompts.py +483 -0
  36. mosscode/providers.py +587 -0
  37. mosscode/scrollback.py +106 -0
  38. mosscode/security.py +282 -0
  39. mosscode/serialization.py +121 -0
  40. mosscode/session.py +563 -0
  41. mosscode/setup.py +248 -0
  42. mosscode/skills/__init__.py +455 -0
  43. mosscode/skills/builtin/commit/SKILL.md +15 -0
  44. mosscode/skills/builtin/review/SKILL.md +15 -0
  45. mosscode/skills/builtin/test/SKILL.md +13 -0
  46. mosscode/skills/builtin/test/scripts/detect.py +96 -0
  47. mosscode/skills/builtin/test/tool.json +9 -0
  48. mosscode/teams/__init__.py +216 -0
  49. mosscode/teams/backends.py +259 -0
  50. mosscode/teams/board.py +230 -0
  51. mosscode/teams/lock.py +173 -0
  52. mosscode/teams/mailbox.py +172 -0
  53. mosscode/teams/member.py +427 -0
  54. mosscode/teams/merge.py +207 -0
  55. mosscode/teams/pane.py +20 -0
  56. mosscode/teams/pane_app.py +167 -0
  57. mosscode/teams/process.py +371 -0
  58. mosscode/teams/runtime.py +1355 -0
  59. mosscode/teams/view.py +182 -0
  60. mosscode/tool_budget.py +86 -0
  61. mosscode/tools/__init__.py +38 -0
  62. mosscode/tools/agent.py +127 -0
  63. mosscode/tools/base.py +384 -0
  64. mosscode/tools/diffing.py +16 -0
  65. mosscode/tools/execution.py +99 -0
  66. mosscode/tools/files.py +287 -0
  67. mosscode/tools/memory.py +65 -0
  68. mosscode/tools/plan.py +30 -0
  69. mosscode/tools/process.py +216 -0
  70. mosscode/tools/search.py +229 -0
  71. mosscode/tools/search_worker.py +243 -0
  72. mosscode/tools/shell.py +121 -0
  73. mosscode/tools/skill.py +118 -0
  74. mosscode/tools/team.py +447 -0
  75. mosscode/tools/windows_job.py +75 -0
  76. mosscode/tui.py +2325 -0
  77. mosscode/usage.py +68 -0
  78. mosscode/windows_inline.py +148 -0
  79. mosscode/worktree/__init__.py +29 -0
  80. mosscode/worktree/changes.py +59 -0
  81. mosscode/worktree/git.py +125 -0
  82. mosscode/worktree/manager.py +462 -0
  83. mosscode/worktree/meta.py +82 -0
  84. mosscode/worktree/names.py +52 -0
  85. mosscode/worktree/setup.py +119 -0
  86. mosscode-0.1.0.dist-info/METADATA +92 -0
  87. mosscode-0.1.0.dist-info/RECORD +91 -0
  88. mosscode-0.1.0.dist-info/WHEEL +5 -0
  89. mosscode-0.1.0.dist-info/entry_points.txt +3 -0
  90. mosscode-0.1.0.dist-info/licenses/LICENSE +21 -0
  91. 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 个对实现最关键的文件路径。