feat: unify terminal session controls
This commit is contained in:
parent
dc6d998afb
commit
4be61fe76e
@ -1317,7 +1317,7 @@ class MainTerminal:
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "sleep",
|
||||
"description": "等待指定的秒数,用于短暂延迟/节奏控制(例如让终端产生更多输出、或在两次快照之间留出间隔)。命令是否完成必须用 terminal_snapshot 确认;需要控制命令最长运行时间请使用 run_command/terminal_input 的 timeout。",
|
||||
"description": "等待指定的秒数,用于短暂延迟/节奏控制(例如让终端产生更多输出、或在两次快照之间留出间隔)。命令是否完成必须用 terminal_snapshot 确认;需要强制超时终止请使用 run_command。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
@ -1543,12 +1543,12 @@ class MainTerminal:
|
||||
"properties": self._inject_intent({
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["open", "close", "list", "switch"],
|
||||
"description": "操作类型:open-打开新终端,close-关闭终端,list-列出所有终端,switch-切换活动终端"
|
||||
"enum": ["open", "close", "list", "reset"],
|
||||
"description": "操作类型:open-打开新终端,close-关闭终端,list-列出所有终端,reset-重置终端"
|
||||
},
|
||||
"session_name": {
|
||||
"type": "string",
|
||||
"description": "终端会话名称(open、close、switch时需要)"
|
||||
"description": "终端会话名称(open、close、reset时需要)"
|
||||
},
|
||||
"working_dir": {
|
||||
"type": "string",
|
||||
@ -1563,7 +1563,7 @@ class MainTerminal:
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "terminal_input",
|
||||
"description": "向活动终端发送命令或输入。禁止启动会占用终端界面的程序(python/node/nano/vim 等);如遇卡死请结合 terminal_snapshot 并使用 terminal_reset 恢复。timeout 必填:\n1) 传入数字(秒,最大300)会对命令进行硬超时封装。系统终端执行环境若存在 timeout/gtimeout,会采用类似 timeout -k 2 {秒}s sh -c '命令; echo __CMD_DONE__...__' 的封装;若没有 timeout/gtimeout(少见情况),则退化为外层 sh -c 的 sleep/kill 包装,例如:sh -c '运行的指令 & CMD_PID=$!; (sleep 300 && kill -s INT $CMD_PID >/dev/null 2>&1 && sleep 2 && kill -s KILL $CMD_PID >/dev/null 2>&1) & WAITER=$!; wait $CMD_PID; CMD_STATUS=$?; kill $WAITER >/dev/null 2>&1; echo __CMD_DONE__1770826047975__; exit $CMD_STATUS'。超时后会先 INT 再 KILL,进程会被不可恢复地打断(可能留下半写文件、锁或残留子进程)。\n2) 传入 never 表示不封装、不杀进程,命令原样进入终端并维护状态,会在运行完成前持续占用终端窗口,在确认完成前无法发送新的指令;此时快照可能无法判断完成情况,需要在其他终端或用run_command使用 curl/ps/ls 等主动验证。\n适合 timeout=never 的场景示例:启动常驻服务/开发服务器(npm run dev、python web_server.py、uvicorn ...)、开启后台进程后在另一个终端测试、在后台运行时间极长的任务同时做其他事情、Github大项目克隆、大文件下载、持续输出/长时间任务(tail -f 日志、长时间编译/训练/备份/大下载)、需要维持会话状态的操作(例如登录远程服务器后连续执行多条命令)。适合用数字超时的示例:ls/rg/pytest/短脚本等快速命令。\n若不确定上一条命令是否结束,先用 terminal_snapshot 确认后再继续输入。",
|
||||
"description": "向指定终端发送命令或输入。禁止启动会占用终端界面的程序(python/node/nano/vim 等);如遇卡死请结合 terminal_snapshot 并使用 terminal_session 的 reset 恢复。timeout 必填:传入数字(秒,最大300)表示本次等待输出的时长,不会封装命令、不会强杀进程;在等待窗口内若检测到命令已完成会提前返回,否则在超时后返回已产生的输出并保持命令继续运行。需要强制超时终止请使用 run_command。\n若不确定上一条命令是否结束,先用 terminal_snapshot 确认后再继续输入。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
@ -1573,14 +1573,14 @@ class MainTerminal:
|
||||
},
|
||||
"session_name": {
|
||||
"type": "string",
|
||||
"description": "目标终端会话名称(可选,默认使用活动终端)"
|
||||
"description": "目标终端会话名称(必填)"
|
||||
},
|
||||
"timeout": {
|
||||
"type": ["number", "string"],
|
||||
"description": "等待输出的最长秒数,必填,最大300;填 never 表示不封装超时且不中断进程(数字超时会触发外层封装)"
|
||||
"type": "number",
|
||||
"description": "等待输出的最长秒数,必填,最大300;不会封装命令、不会中断进程"
|
||||
}
|
||||
}),
|
||||
"required": ["command", "timeout"]
|
||||
"required": ["command", "timeout", "session_name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1608,22 +1608,6 @@ class MainTerminal:
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "terminal_reset",
|
||||
"description": "重置指定终端:关闭当前进程并重新创建同名会话,用于从卡死或非法状态中恢复。请在总结中说明重置原因。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
"session_name": {
|
||||
"type": "string",
|
||||
"description": "目标终端会话名称(可选,默认活动终端)"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@ -2073,12 +2057,12 @@ class MainTerminal:
|
||||
elif action == "list":
|
||||
result = self.terminal_manager.list_terminals()
|
||||
|
||||
elif action == "switch":
|
||||
result = self.terminal_manager.switch_terminal(
|
||||
session_name=arguments.get("session_name", "default")
|
||||
elif action == "reset":
|
||||
result = self.terminal_manager.reset_terminal(
|
||||
session_name=arguments.get("session_name")
|
||||
)
|
||||
if result["success"]:
|
||||
print(f"{OUTPUT_FORMATS['session']} 切换到终端: {arguments.get('session_name', 'default')}")
|
||||
print(f"{OUTPUT_FORMATS['session']} 终端会话已重置: {result['session']}")
|
||||
|
||||
else:
|
||||
result = {"success": False, "error": f"未知操作: {action}"}
|
||||
@ -2101,13 +2085,6 @@ class MainTerminal:
|
||||
max_chars=arguments.get("max_chars")
|
||||
)
|
||||
|
||||
elif tool_name == "terminal_reset":
|
||||
result = self.terminal_manager.reset_terminal(
|
||||
session_name=arguments.get("session_name")
|
||||
)
|
||||
if result["success"]:
|
||||
print(f"{OUTPUT_FORMATS['session']} 终端会话已重置: {result['session']}")
|
||||
|
||||
# sleep工具
|
||||
elif tool_name == "sleep":
|
||||
seconds = arguments.get("seconds", 1)
|
||||
|
||||
@ -55,7 +55,6 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
|
||||
"terminal_session",
|
||||
"terminal_input",
|
||||
"terminal_snapshot",
|
||||
"terminal_reset",
|
||||
"sleep",
|
||||
],
|
||||
),
|
||||
|
||||
@ -14,6 +14,7 @@ import queue
|
||||
from collections import deque
|
||||
import shutil
|
||||
import uuid
|
||||
import codecs
|
||||
try:
|
||||
from config import (
|
||||
OUTPUT_FORMATS,
|
||||
@ -115,6 +116,7 @@ class PersistentTerminal:
|
||||
self.output_queue = queue.Queue()
|
||||
self.reader_thread = None
|
||||
self.is_reading = False
|
||||
self._decoder = None
|
||||
|
||||
# 状态标志
|
||||
self.is_interactive = False # 是否在等待输入
|
||||
@ -355,6 +357,8 @@ class PersistentTerminal:
|
||||
"run",
|
||||
"--rm",
|
||||
"-i",
|
||||
]
|
||||
cmd += [
|
||||
"--name",
|
||||
container_name,
|
||||
"-w",
|
||||
@ -453,19 +457,18 @@ class PersistentTerminal:
|
||||
|
||||
def _read_output(self):
|
||||
"""后台线程:持续读取输出(修复版,正确处理编码)"""
|
||||
if self._decoder is None:
|
||||
encoding = 'gbk' if self.is_windows else 'utf-8'
|
||||
self._decoder = codecs.getincrementaldecoder(encoding)(errors='replace')
|
||||
while self.is_reading and self.process:
|
||||
try:
|
||||
# 始终读取字节(因为我们没有使用text=True)
|
||||
line_bytes = self.process.stdout.readline()
|
||||
|
||||
if line_bytes:
|
||||
# 解码字节到字符串
|
||||
line = self._decode_output(line_bytes)
|
||||
|
||||
# 处理输出
|
||||
self.output_queue.put(line)
|
||||
self._process_output(line)
|
||||
|
||||
# 读取任意字节块,不依赖换行符
|
||||
chunk = self.process.stdout.read(1024)
|
||||
if chunk:
|
||||
text = self._decoder.decode(chunk)
|
||||
if text:
|
||||
self.output_queue.put(text)
|
||||
self._process_output(text)
|
||||
elif self.process.poll() is not None:
|
||||
# 进程已结束
|
||||
self.is_running = False
|
||||
@ -759,7 +762,10 @@ class PersistentTerminal:
|
||||
"output_with_echo": "命令产生输出,但终端疑似重复回显",
|
||||
"timeout": f"命令超时({int(timeout)}秒)"
|
||||
}
|
||||
message = message_map.get(status, "命令执行完成")
|
||||
if timeout >= 60:
|
||||
message = f"[运行了{int(timeout)}秒的所有输出]"
|
||||
else:
|
||||
message = message_map.get(status, "命令执行完成")
|
||||
if output_truncated:
|
||||
message += f"(输出已截断,保留末尾{TERMINAL_INPUT_MAX_CHARS}字符)"
|
||||
elapsed_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
@ -528,78 +528,48 @@ class TerminalManager:
|
||||
|
||||
# 发送命令
|
||||
terminal = self.terminals[target_session]
|
||||
never_timeout = False
|
||||
if isinstance(timeout, str):
|
||||
if timeout.lower() == "never":
|
||||
never_timeout = True
|
||||
else:
|
||||
try:
|
||||
timeout = float(timeout)
|
||||
except (TypeError, ValueError):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "timeout 参数必须是数字或 'never'",
|
||||
"status": "error",
|
||||
"output": "timeout 参数无效"
|
||||
}
|
||||
|
||||
if not never_timeout:
|
||||
if timeout is None or timeout <= 0:
|
||||
try:
|
||||
timeout = float(timeout)
|
||||
except (TypeError, ValueError):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "timeout 参数必填且需大于0,或设置为 'never'",
|
||||
"error": "timeout 参数必须是数字",
|
||||
"status": "error",
|
||||
"output": "timeout 参数缺失"
|
||||
"output": "timeout 参数无效"
|
||||
}
|
||||
timeout = min(timeout, 300)
|
||||
|
||||
base_timeout = timeout
|
||||
marker = f"__CMD_DONE__{int(time.time()*1000)}__"
|
||||
if timeout is None or timeout <= 0:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "timeout 参数必填且需大于0",
|
||||
"status": "error",
|
||||
"output": "timeout 参数缺失"
|
||||
}
|
||||
timeout = min(timeout, 300)
|
||||
|
||||
wrapped_command, wait_timeout = self._build_wrapped_command(command, marker, timeout)
|
||||
|
||||
result = terminal.send_command(
|
||||
wrapped_command,
|
||||
timeout=wait_timeout,
|
||||
timeout_cutoff=base_timeout,
|
||||
enforce_full_timeout=True,
|
||||
sentinel=marker,
|
||||
)
|
||||
result["timeout"] = base_timeout
|
||||
result["never_timeout"] = False
|
||||
self._apply_terminal_input_timeout_hint(target_session, result)
|
||||
return result
|
||||
|
||||
# never_timeout 分支:不包装命令,不发送结束标记,不强杀进程
|
||||
result = terminal.send_command(
|
||||
command,
|
||||
timeout=None,
|
||||
timeout_cutoff=None,
|
||||
enforce_full_timeout=False,
|
||||
timeout=timeout,
|
||||
timeout_cutoff=timeout,
|
||||
enforce_full_timeout=True,
|
||||
sentinel=None,
|
||||
)
|
||||
result["timeout"] = "never"
|
||||
result["never_timeout"] = True
|
||||
result["timeout"] = timeout
|
||||
result["never_timeout"] = False
|
||||
self._apply_terminal_input_timeout_hint(target_session, result)
|
||||
return result
|
||||
|
||||
def _apply_terminal_input_timeout_hint(self, session_name: str, result: Dict) -> None:
|
||||
"""在终端输入连续超时时追加提示标记(仅数值型 timeout)。/ Add hint after consecutive timeouts (numeric only)."""
|
||||
"""在终端输入连续超时时追加提示标记。/ Add hint after consecutive timeouts."""
|
||||
status = (result.get("status") or "").lower()
|
||||
never_timeout = result.get("never_timeout")
|
||||
if never_timeout is None:
|
||||
timeout_value = result.get("timeout")
|
||||
if isinstance(timeout_value, str) and timeout_value.lower() == "never":
|
||||
never_timeout = True
|
||||
if status == "timeout" and not never_timeout:
|
||||
if status == "timeout":
|
||||
streak = self._terminal_input_timeout_streaks.get(session_name, 0) + 1
|
||||
else:
|
||||
streak = 0
|
||||
self._terminal_input_timeout_streaks[session_name] = streak
|
||||
if streak == 2:
|
||||
result["timeout_hint"] = "suggest_adjust_timeout"
|
||||
elif streak == 3:
|
||||
result["timeout_hint"] = "suggest_never_timeout"
|
||||
|
||||
def _build_wrapped_command(self, command: str, marker: str, timeout: int) -> (str, int):
|
||||
"""
|
||||
|
||||
25
package-lock.json
generated
25
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "4.1.0",
|
||||
"dependencies": {
|
||||
"@types/html2canvas": "^0.5.35",
|
||||
"enquirer": "^2.4.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"katex": "^0.16.9",
|
||||
"marked": "^11.1.0",
|
||||
@ -1842,11 +1843,19 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -2195,6 +2204,19 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enquirer": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
|
||||
"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-colors": "^4.1.1",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
@ -3721,7 +3743,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/html2canvas": "^0.5.35",
|
||||
"enquirer": "^2.4.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"katex": "^0.16.9",
|
||||
"marked": "^11.1.0",
|
||||
|
||||
@ -62,10 +62,10 @@
|
||||
### 3.4 终端操作
|
||||
|
||||
**持久终端**(`terminal_session` 系列):
|
||||
- `terminal_session`:管理会话(open/close/list/switch),最多3个
|
||||
- `terminal_input`:发送命令(`timeout` 必填,最大300s或 `never`)
|
||||
- `terminal_session`:管理会话(open/close/list/reset),最多3个
|
||||
- `terminal_input`:发送命令(`timeout` 必填,最大300s)
|
||||
- `terminal_snapshot`:获取输出快照(判断状态必备)
|
||||
- `terminal_reset`:重置卡死终端
|
||||
- 终端卡死需用 `terminal_session` 的 reset 操作恢复
|
||||
|
||||
**快速执行**:
|
||||
- `run_command`:一次性命令(最长30s,输出限10000字符)
|
||||
|
||||
@ -80,10 +80,10 @@
|
||||
### 3.4 终端操作
|
||||
|
||||
**持久终端**(`terminal_session` 系列):
|
||||
- `terminal_session`:管理会话(open/close/list/switch),最多3个
|
||||
- `terminal_input`:发送命令(`timeout` 必填,最大300s或 `never`)
|
||||
- `terminal_session`:管理会话(open/close/list/reset),最多3个
|
||||
- `terminal_input`:发送命令(`timeout` 必填,最大300s)
|
||||
- `terminal_snapshot`:获取输出快照(判断状态必备)
|
||||
- `terminal_reset`:重置卡死终端
|
||||
- 终端卡死需用 `terminal_session` 的 reset 操作恢复
|
||||
|
||||
**快速执行**:
|
||||
- `run_command`:一次性命令(最长30s,输出限10000字符)
|
||||
|
||||
@ -981,7 +981,7 @@
|
||||
await helpers.sleep(1200);
|
||||
|
||||
helpers.setStatus('正在规划');
|
||||
await helpers.showBubble('terminal_reset:快速清场并恢复初始提示符。', { duration: 2200 });
|
||||
await helpers.showBubble('terminal_session reset:快速清场并恢复初始提示符。', { duration: 2200 });
|
||||
await helpers.moveMouseTo(resetBtn, { duration: 650 });
|
||||
await helpers.click({ count: 1 });
|
||||
helpers.appendTerminalLines(['session reset complete', '$'], { reset: true });
|
||||
|
||||
@ -3362,10 +3362,14 @@ export class MonitorDirector implements MonitorDriver {
|
||||
};
|
||||
|
||||
this.sceneHandlers.terminalSession = async (payload, runtime) => {
|
||||
this.applySceneStatus(runtime, 'terminalSession', '打开终端');
|
||||
const action = (payload?.arguments?.action || payload?.action || '').toLowerCase();
|
||||
// 特殊处理:如果是关闭/切换终端,不要无意中新建会话
|
||||
if (action === 'close' || action === 'switch') {
|
||||
if (action === 'reset') {
|
||||
this.applySceneStatus(runtime, 'terminalSession', '正在重置终端');
|
||||
} else {
|
||||
this.applySceneStatus(runtime, 'terminalSession', '打开终端');
|
||||
}
|
||||
// 特殊处理:如果是关闭/重置终端,不要无意中新建会话
|
||||
if (action === 'close' || action === 'reset') {
|
||||
terminalMenuDebug('terminalSession:action', { action, payload });
|
||||
const targetSession = this.resolveExistingSessionId(payload);
|
||||
if (!targetSession) {
|
||||
@ -3379,15 +3383,14 @@ export class MonitorDirector implements MonitorDriver {
|
||||
} else {
|
||||
this.showWindow(shell.element);
|
||||
}
|
||||
if (action === 'switch') {
|
||||
const tab = this.getTabElement(targetSession);
|
||||
if (tab) {
|
||||
await this.movePointerToElement(tab, { duration: 420 });
|
||||
await this.click();
|
||||
this.activateSession(targetSession);
|
||||
}
|
||||
if (action === 'reset') {
|
||||
await this.openTerminalContextMenu(targetSession);
|
||||
await this.chooseTerminalMenuAction('reset');
|
||||
this.terminalHistories.set(targetSession, [{ text: '➜ ', role: 'prompt' }]);
|
||||
this.renderTerminalHistory(targetSession);
|
||||
this.appendTerminalNote(targetSession, '终端已重置');
|
||||
this.ensurePromptLine(targetSession);
|
||||
await sleep(200);
|
||||
await sleep(300);
|
||||
return;
|
||||
}
|
||||
// action === 'close'
|
||||
@ -3476,20 +3479,6 @@ export class MonitorDirector implements MonitorDriver {
|
||||
await sleep(300);
|
||||
};
|
||||
|
||||
this.sceneHandlers.terminalReset = async (payload, runtime) => {
|
||||
this.applySceneStatus(runtime, 'terminalReset', '正在重置终端');
|
||||
const { sessionId } = await this.ensureTerminalSessionReady(payload, { focusPrompt: false, activate: false });
|
||||
terminalMenuDebug('scene:terminalReset:start', { sessionId });
|
||||
await this.openTerminalContextMenu(sessionId);
|
||||
terminalMenuDebug('scene:terminalReset:menu-opened', { sessionId });
|
||||
await this.chooseTerminalMenuAction('reset');
|
||||
this.terminalHistories.set(sessionId, [{ text: '➜ ', role: 'prompt' }]);
|
||||
this.renderTerminalHistory(sessionId);
|
||||
this.appendTerminalNote(sessionId, '终端已重置');
|
||||
this.ensurePromptLine(sessionId);
|
||||
await sleep(300);
|
||||
};
|
||||
|
||||
this.sceneHandlers.terminalSleep = async (payload, runtime) => {
|
||||
this.applySceneStatus(runtime, 'terminalSleep', '正在等待');
|
||||
const duration =
|
||||
|
||||
@ -110,7 +110,6 @@ const TOOL_SCENE_MAP: Record<string, string> = {
|
||||
terminal_session: 'terminalSession',
|
||||
terminal_input: 'terminalInput',
|
||||
terminal_snapshot: 'terminalSnapshot',
|
||||
terminal_reset: 'terminalReset',
|
||||
sleep: 'terminalSleep',
|
||||
update_memory: 'memoryUpdate',
|
||||
todo_create: 'todoCreate',
|
||||
|
||||
@ -23,7 +23,6 @@ const RUNNING_ANIMATIONS: Record<string, string> = {
|
||||
terminal_session: 'terminal-animation',
|
||||
terminal_input: 'terminal-animation',
|
||||
terminal_snapshot: 'terminal-animation',
|
||||
terminal_reset: 'terminal-animation',
|
||||
todo_create: 'file-animation',
|
||||
todo_update_task: 'file-animation',
|
||||
create_sub_agent: 'terminal-animation',
|
||||
@ -48,8 +47,7 @@ const RUNNING_STATUS_TEXTS: Record<string, string> = {
|
||||
update_memory: '正在更新记忆...',
|
||||
terminal_session: '正在管理终端会话...',
|
||||
terminal_input: '调用 terminal_input',
|
||||
terminal_snapshot: '正在获取终端快照...',
|
||||
terminal_reset: '正在重置终端...'
|
||||
terminal_snapshot: '正在获取终端快照...'
|
||||
};
|
||||
|
||||
const COMPLETED_STATUS_TEXTS: Record<string, string> = {
|
||||
@ -71,8 +69,7 @@ const COMPLETED_STATUS_TEXTS: Record<string, string> = {
|
||||
update_memory: '记忆更新成功',
|
||||
terminal_session: '终端操作完成',
|
||||
terminal_input: '终端输入完成',
|
||||
terminal_snapshot: '终端快照已返回',
|
||||
terminal_reset: '终端已重置'
|
||||
terminal_snapshot: '终端快照已返回'
|
||||
};
|
||||
|
||||
const LANGUAGE_CLASS_MAP: Record<string, string> = {
|
||||
|
||||
@ -62,7 +62,6 @@ export const TOOL_ICON_MAP = Object.freeze({
|
||||
todo_create: 'stickyNote',
|
||||
todo_update_task: 'check',
|
||||
terminal_input: 'keyboard',
|
||||
terminal_reset: 'recycle',
|
||||
terminal_session: 'monitor',
|
||||
terminal_snapshot: 'clipboard',
|
||||
unfocus_file: 'eye',
|
||||
|
||||
@ -302,10 +302,10 @@ def _format_terminal_session(result_data: Dict[str, Any]) -> str:
|
||||
f"终端 {result_data.get('session')} 已关闭,新的活动会话: {new_active}。"
|
||||
f"剩余会话: {', '.join(remaining) if remaining else '无'}"
|
||||
)
|
||||
if action == "switch":
|
||||
previous = result_data.get("previous") or "无"
|
||||
current = result_data.get("current") or "未知"
|
||||
return f"终端已从 {previous} 切换到 {current}。"
|
||||
if action == "reset":
|
||||
session = result_data.get("session") or "未知会话"
|
||||
working_dir = result_data.get("working_dir") or "未知目录"
|
||||
return f"终端 {session} 已重置,工作目录 {working_dir}。"
|
||||
if action == "list":
|
||||
sessions = result_data.get("sessions") or []
|
||||
total = result_data.get("total", len(sessions))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user