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
mosscode/agents/tasks.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""The sub-agents of one session, foreground or background."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from ..messages import UsageTotals
|
|
13
|
+
|
|
14
|
+
STATUS_LABELS = {"running": "运行中", "completed": "已完成", "failed": "失败", "cancelled": "已取消"}
|
|
15
|
+
ORIGIN_LABELS = {"agent": "agent 工具", "skill": "Skill fork", "hook": "Hook"}
|
|
16
|
+
CANCEL_WAIT = 5.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Task:
|
|
21
|
+
id: str
|
|
22
|
+
origin: Literal["agent", "skill", "hook"]
|
|
23
|
+
agent: Literal["fork", "subagent"]
|
|
24
|
+
agent_type: str
|
|
25
|
+
description: str
|
|
26
|
+
prompt: str
|
|
27
|
+
call_id: str = ""
|
|
28
|
+
status: Literal["running", "completed", "failed", "cancelled"] = "running"
|
|
29
|
+
background: bool = False
|
|
30
|
+
started: float = field(default_factory=time.monotonic)
|
|
31
|
+
ended: float | None = None
|
|
32
|
+
usage: UsageTotals = field(default_factory=UsageTotals)
|
|
33
|
+
tool_calls: int = 0
|
|
34
|
+
last_tool: str = ""
|
|
35
|
+
result: str | None = None
|
|
36
|
+
transcript: Path | None = None
|
|
37
|
+
notified: bool = False
|
|
38
|
+
stopped: bool = False # Stopped by /tasks stop or task_stop: never notified.
|
|
39
|
+
job: asyncio.Task | None = field(default=None, repr=False)
|
|
40
|
+
detach: asyncio.Event = field(default_factory=asyncio.Event, repr=False)
|
|
41
|
+
policy: object = field(default=None, repr=False) # The PolicyView, switched to non-interactive on detach.
|
|
42
|
+
isolated: bool = False # Runs in a worktree (asked for and not skipped by plan mode).
|
|
43
|
+
root: Path | None = None # The worktree directory it works in; None for the project.
|
|
44
|
+
# {"name", "path", "branch", "base", "outcome"}; outcome is 使用中 / 已保留 / 已删除 / 删除未完成.
|
|
45
|
+
worktree: dict | None = None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def elapsed(self) -> float:
|
|
49
|
+
return (self.ended if self.ended is not None else time.monotonic()) - self.started
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def tokens(self) -> int:
|
|
53
|
+
return self.usage.input_tokens + self.usage.output_tokens
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def label(self) -> str:
|
|
57
|
+
return f"{self.agent_type} · {self.description}"
|
|
58
|
+
|
|
59
|
+
def to_background(self) -> None:
|
|
60
|
+
self.background = True
|
|
61
|
+
if self.policy is not None:
|
|
62
|
+
self.policy.to_background()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def task_line(task: Task) -> str:
|
|
66
|
+
"""The line shown when a background task starts or ends."""
|
|
67
|
+
state = "已启动" if task.status == "running" else STATUS_LABELS[task.status]
|
|
68
|
+
return f"后台任务 {task.id}({task.label}){state}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Announcer:
|
|
72
|
+
"""One line when a background task starts and one when it ends, however often on_change fires between.
|
|
73
|
+
|
|
74
|
+
Hook tasks are left out: the hook engine reports them.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, say: Callable[[str], None]):
|
|
78
|
+
self.say = say
|
|
79
|
+
self.started: set[str] = set()
|
|
80
|
+
self.ended: set[str] = set()
|
|
81
|
+
|
|
82
|
+
def __call__(self, task: Task) -> None:
|
|
83
|
+
if not task.background or task.origin == "hook":
|
|
84
|
+
return
|
|
85
|
+
if task.id not in self.started:
|
|
86
|
+
self.started.add(task.id)
|
|
87
|
+
if task.status == "running":
|
|
88
|
+
self.say(task_line(task))
|
|
89
|
+
if task.status != "running" and task.id not in self.ended:
|
|
90
|
+
self.ended.add(task.id)
|
|
91
|
+
self.say(task_line(task))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TaskManager:
|
|
95
|
+
def __init__(self, *, max_background: int = 4):
|
|
96
|
+
self.tasks: dict[str, Task] = {}
|
|
97
|
+
self.max_background = max_background
|
|
98
|
+
self._next = 0
|
|
99
|
+
self.on_finish: Callable[[Task], None] | None = None
|
|
100
|
+
self.on_change: Callable[[Task], None] | None = None # The UI refreshes a tool row or the status bar.
|
|
101
|
+
self.finished = asyncio.Event() # Set whenever a task ends; -p waits on it.
|
|
102
|
+
|
|
103
|
+
def new(self, **fields) -> Task:
|
|
104
|
+
self._next += 1
|
|
105
|
+
task = Task(f"t{self._next}", **fields)
|
|
106
|
+
self.tasks[task.id] = task
|
|
107
|
+
return task
|
|
108
|
+
|
|
109
|
+
def get(self, task_id: str) -> Task | None:
|
|
110
|
+
return self.tasks.get(task_id)
|
|
111
|
+
|
|
112
|
+
def running(self) -> list[Task]:
|
|
113
|
+
return [task for task in self.tasks.values() if task.status == "running"]
|
|
114
|
+
|
|
115
|
+
def background(self) -> list[Task]:
|
|
116
|
+
return [task for task in self.running() if task.background]
|
|
117
|
+
|
|
118
|
+
def full(self) -> bool:
|
|
119
|
+
return len(self.background()) >= self.max_background
|
|
120
|
+
|
|
121
|
+
def changed(self, task: Task) -> None:
|
|
122
|
+
if self.on_change is not None:
|
|
123
|
+
try:
|
|
124
|
+
self.on_change(task)
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def launch(self, task: Task, run: Callable[[], Awaitable[str]]) -> asyncio.Task:
|
|
129
|
+
task.job = asyncio.get_running_loop().create_task(self._run(task, run))
|
|
130
|
+
self.changed(task)
|
|
131
|
+
return task.job
|
|
132
|
+
|
|
133
|
+
async def _run(self, task: Task, run) -> str:
|
|
134
|
+
try:
|
|
135
|
+
text = await run()
|
|
136
|
+
except asyncio.CancelledError:
|
|
137
|
+
text = task.result or "[子 Agent 未完成:已取消]"
|
|
138
|
+
task.status = "cancelled"
|
|
139
|
+
except Exception as exc: # run() turns provider errors into text; anything else is a bug.
|
|
140
|
+
text = f"[子 Agent 未完成:内部错误 {type(exc).__name__}]"
|
|
141
|
+
task.status = "failed"
|
|
142
|
+
self.settle(task, text)
|
|
143
|
+
return text
|
|
144
|
+
|
|
145
|
+
def settle(self, task: Task, text: str) -> None:
|
|
146
|
+
"""The end of a task, also of one run outside launch (a Skill fork)."""
|
|
147
|
+
task.result = text
|
|
148
|
+
task.ended = time.monotonic()
|
|
149
|
+
if task.status == "running":
|
|
150
|
+
task.status = "completed"
|
|
151
|
+
self.finished.set()
|
|
152
|
+
self.changed(task)
|
|
153
|
+
if self.on_finish is not None:
|
|
154
|
+
try:
|
|
155
|
+
self.on_finish(task)
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
def detach_foreground(self) -> int:
|
|
160
|
+
"""ctrl+b moves every running foreground sub-agent of the agent tool to the background."""
|
|
161
|
+
count = 0
|
|
162
|
+
for task in self.running():
|
|
163
|
+
if not task.background and task.origin == "agent" and not task.detach.is_set():
|
|
164
|
+
task.detach.set()
|
|
165
|
+
count += 1
|
|
166
|
+
return count
|
|
167
|
+
|
|
168
|
+
async def stop(self, task_id: str) -> bool:
|
|
169
|
+
"""True when it was running; its notification is never sent."""
|
|
170
|
+
task = self.tasks.get(task_id)
|
|
171
|
+
if task is None or task.status != "running" or task.job is None:
|
|
172
|
+
return False
|
|
173
|
+
task.stopped = task.notified = True
|
|
174
|
+
task.job.cancel()
|
|
175
|
+
await asyncio.gather(task.job, return_exceptions=True)
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
async def cancel_all(self, wait: float = CANCEL_WAIT) -> int:
|
|
179
|
+
"""On exit and when the user confirms: cancel, wait a little, never notify."""
|
|
180
|
+
jobs = []
|
|
181
|
+
for task in self.running():
|
|
182
|
+
task.stopped = task.notified = True
|
|
183
|
+
if task.job is not None:
|
|
184
|
+
task.job.cancel()
|
|
185
|
+
jobs.append(task.job)
|
|
186
|
+
if jobs:
|
|
187
|
+
await asyncio.wait(jobs, timeout=wait)
|
|
188
|
+
return len(jobs)
|
|
189
|
+
|
|
190
|
+
async def reset(self) -> None:
|
|
191
|
+
"""/clear and resume start with an empty list."""
|
|
192
|
+
await self.cancel_all()
|
|
193
|
+
self.tasks.clear()
|
mosscode/appearance.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Terminal colors and the mascot of the startup banner."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.style import Style
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
from textual.theme import Theme
|
|
10
|
+
|
|
11
|
+
TEXT_COLOR = "ansi_default"
|
|
12
|
+
TOOL_SUCCESS_COLOR = "#8bd5a6"
|
|
13
|
+
PARAMETER_COLOR = "#6fa8ff"
|
|
14
|
+
OPERATION_COLOR = "#e3b95f"
|
|
15
|
+
ERROR_COLOR = "#e87983"
|
|
16
|
+
COMMAND_HIGHLIGHT_COLOR = "#3673ff"
|
|
17
|
+
# User messages sit on a grey bar, as in Claude Code: a mid grey with near-white
|
|
18
|
+
# text stands out on both dark and light terminal backgrounds.
|
|
19
|
+
USER_MESSAGE_BACKGROUND = "#4a4a4a"
|
|
20
|
+
USER_MESSAGE_COLOR = "#f2f2f2"
|
|
21
|
+
|
|
22
|
+
# The mascot, a little ghost, as pixels: two pixel rows make one line of half blocks, so each pixel is
|
|
23
|
+
# about square. "." is the terminal's own background; the other letters are MASCOT_COLORS.
|
|
24
|
+
MASCOT_PIXELS = (
|
|
25
|
+
"....wwwwww....",
|
|
26
|
+
"..wwwwwwwwww..",
|
|
27
|
+
".wwwwwwwwwwww.",
|
|
28
|
+
".wwkhwwwwkhww.",
|
|
29
|
+
".wwkkwwwwkkww.",
|
|
30
|
+
".wrrwwkkwwrrw.",
|
|
31
|
+
".wwwwwwwwwwww.",
|
|
32
|
+
".wwwwwwwwwwww.",
|
|
33
|
+
".ww.www.www.w.",
|
|
34
|
+
".w...w...w....",
|
|
35
|
+
)
|
|
36
|
+
MASCOT_COLORS = {"w": "#eef1ff", "k": "#26212e", "h": "#ffffff", "r": "#ff9fb2"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Transparent TCSS inherits the parent's painted background. ANSI default means
|
|
40
|
+
# the terminal's own background, and must be preserved by ansi_color=True.
|
|
41
|
+
TERMINAL_CSS = """
|
|
42
|
+
App, Screen, * {
|
|
43
|
+
color: ansi_default !important;
|
|
44
|
+
text-opacity: 100% !important;
|
|
45
|
+
opacity: 1 !important;
|
|
46
|
+
background: ansi_default !important;
|
|
47
|
+
background-tint: transparent !important;
|
|
48
|
+
tint: transparent !important;
|
|
49
|
+
scrollbar-background: ansi_default;
|
|
50
|
+
scrollbar-background-hover: ansi_default;
|
|
51
|
+
scrollbar-background-active: ansi_default;
|
|
52
|
+
scrollbar-corner-color: ansi_default;
|
|
53
|
+
}
|
|
54
|
+
Input > .input--placeholder, Input > .input--suggestion {
|
|
55
|
+
color: ansi_default !important;
|
|
56
|
+
text-style: none !important;
|
|
57
|
+
}
|
|
58
|
+
Markdown MarkdownBlock > .code_inline {
|
|
59
|
+
background: ansi_default !important;
|
|
60
|
+
}
|
|
61
|
+
Input > .input--cursor {
|
|
62
|
+
color: $foreground;
|
|
63
|
+
background: ansi_default !important;
|
|
64
|
+
text-style: reverse;
|
|
65
|
+
}
|
|
66
|
+
Input > .input--selection {
|
|
67
|
+
color: $primary;
|
|
68
|
+
background: ansi_default !important;
|
|
69
|
+
text-style: bold underline;
|
|
70
|
+
}
|
|
71
|
+
OptionList:focus > .option-list--option-highlighted {
|
|
72
|
+
color: $primary;
|
|
73
|
+
background: ansi_default !important;
|
|
74
|
+
text-style: bold;
|
|
75
|
+
}
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def moss_themes() -> tuple[Theme, ...]:
|
|
80
|
+
# Use the terminal's full-strength foreground; never dim supporting text.
|
|
81
|
+
variables = {
|
|
82
|
+
name: TEXT_COLOR
|
|
83
|
+
for name in (
|
|
84
|
+
"text",
|
|
85
|
+
"text-muted",
|
|
86
|
+
"text-disabled",
|
|
87
|
+
"foreground-muted",
|
|
88
|
+
"input-cursor-foreground",
|
|
89
|
+
"link-color",
|
|
90
|
+
"link-color-hover",
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
variables["input-cursor-background"] = "ansi_default"
|
|
94
|
+
return (
|
|
95
|
+
Theme(
|
|
96
|
+
name="moss-dark",
|
|
97
|
+
primary=TEXT_COLOR,
|
|
98
|
+
secondary=TEXT_COLOR,
|
|
99
|
+
accent=TEXT_COLOR,
|
|
100
|
+
foreground=TEXT_COLOR,
|
|
101
|
+
background="#0c0c0c",
|
|
102
|
+
surface="#0c0c0c",
|
|
103
|
+
panel="#0c0c0c",
|
|
104
|
+
success=TEXT_COLOR,
|
|
105
|
+
warning=TEXT_COLOR,
|
|
106
|
+
error=TEXT_COLOR,
|
|
107
|
+
dark=True,
|
|
108
|
+
variables=variables,
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def pixel_art(pixels: tuple[str, ...], colors: dict[str, str]) -> Text:
|
|
114
|
+
"""Each pair of pixel rows as one line: ▀ in the upper color over the lower one as background."""
|
|
115
|
+
art = Text(no_wrap=True)
|
|
116
|
+
for index, (upper, lower) in enumerate(zip(pixels[::2], pixels[1::2])):
|
|
117
|
+
if index:
|
|
118
|
+
art.append("\n")
|
|
119
|
+
for top, bottom in zip(upper, lower):
|
|
120
|
+
top, bottom = colors.get(top), colors.get(bottom)
|
|
121
|
+
if top and top == bottom:
|
|
122
|
+
art.append("█", Style(color=top))
|
|
123
|
+
elif top:
|
|
124
|
+
art.append("▀", Style(color=top, bgcolor=bottom))
|
|
125
|
+
elif bottom:
|
|
126
|
+
art.append("▄", Style(color=bottom))
|
|
127
|
+
else:
|
|
128
|
+
art.append(" ")
|
|
129
|
+
return art
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def startup_banner(
|
|
133
|
+
provider: str,
|
|
134
|
+
) -> Table:
|
|
135
|
+
logo = pixel_art(MASCOT_PIXELS, MASCOT_COLORS)
|
|
136
|
+
info = Text(style="default", no_wrap=True, overflow="ellipsis")
|
|
137
|
+
info.append("MossCode", style=Style(bold=True))
|
|
138
|
+
info.append(f" v{version('mosscode')}\n")
|
|
139
|
+
info.append(provider)
|
|
140
|
+
info.append("\n" + str(Path.cwd()))
|
|
141
|
+
banner = Table.grid(padding=(0, 2), expand=True)
|
|
142
|
+
banner.add_column(width=len(MASCOT_PIXELS[0]), no_wrap=True)
|
|
143
|
+
banner.add_column(ratio=1, no_wrap=True, overflow="ellipsis", vertical="middle")
|
|
144
|
+
banner.add_row(logo, info)
|
|
145
|
+
return banner
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def compact_notice(event) -> str:
|
|
149
|
+
"""One line for a finished or failed compaction, shared by the TUI and headless text output."""
|
|
150
|
+
if hasattr(event, "removed"):
|
|
151
|
+
return (
|
|
152
|
+
f"✓ 已压缩上下文 · {event.removed} 条消息 → 摘要 · 约 {event.summary_tokens} Token"
|
|
153
|
+
f" · 用时 {event.elapsed:.1f}s"
|
|
154
|
+
)
|
|
155
|
+
if event.code == "nothing":
|
|
156
|
+
return "└ " + event.reason
|
|
157
|
+
if event.code == "restore":
|
|
158
|
+
return "! " + event.reason
|
|
159
|
+
if event.manual:
|
|
160
|
+
return "! 压缩上下文失败:" + event.reason
|
|
161
|
+
notice = f"! 自动压缩上下文失败(连续 {event.consecutive} 次):{event.reason}"
|
|
162
|
+
if event.paused:
|
|
163
|
+
notice += "\n 自动压缩已暂停,可以 /compact 手动重试或 /clear"
|
|
164
|
+
return notice
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# The key half of /help; the command half comes from the registry.
|
|
168
|
+
KEY_HELP = """按键
|
|
169
|
+
Enter 发送消息或执行命令
|
|
170
|
+
Shift+Enter / Ctrl+J 换行(也可在行尾输入 \\ 再按 Enter)
|
|
171
|
+
↑ / ↓ 在命令提示中选择;首行/末行时浏览本次输入历史
|
|
172
|
+
Tab 补全命令或参数
|
|
173
|
+
Shift+Tab 依次切换 default → acceptEdits → plan
|
|
174
|
+
运行中 Enter 排队消息,本轮工具执行后发送给模型
|
|
175
|
+
Esc 关闭命令提示(保留输入)
|
|
176
|
+
Esc / Ctrl+C 取消正在生成的回复;排队消息放回输入框
|
|
177
|
+
授权面板 ↑ / ↓ 选择,Enter 确认,Esc 拒绝本次,Ctrl+C 取消任务
|
|
178
|
+
Ctrl+B 把前台运行的子 Agent 转入后台
|
|
179
|
+
Ctrl+T 展开团队成员面板:Enter 查看对话,x 停止成员
|
|
180
|
+
成员视图 PgUp / PgDn 滚动,← / → 切换成员,Esc 返回
|
|
181
|
+
Ctrl+Q 退出(有后台任务时先确认)
|
|
182
|
+
鼠标滚轮 滚动对话记录
|
|
183
|
+
鼠标拖选 使用终端原生文字选择
|
|
184
|
+
Ctrl+V 粘贴(多行内容保持原样)"""
|