Files
xiaoli-cli/plugins/operit/super_admin.ts
T
xiaoli-cli bot dfe21298b9 release: v5.4.2 - terminal模拟器 + Operit桥接
新增插件:
- terminal: 持久交互式终端,纯标准库,全平台,13个测试
- operit_bridge: Operit AI工具格式桥接,47个脚本288个工具,12个测试

其他:
- plugins/operit/: 47个Operit官方脚本
- 更新plugin_registry.json/plugins_config.py
- 更新about.txt/CHANGELOG.md至v5.4.2
2026-06-23 08:50:15 +00:00

424 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
METADATA
{
"name": "super_admin",
"display_name": {
"zh": "超级管理员",
"en": "Super Admin"
},
"description": { "zh": "超级管理员工具集,提供终端命令和Shell操作的高级功能。terminal工具运行在Ubuntu环境中(已正确挂载sdcard和storage),shell工具通过Shizuku/Root直接执行Android系统命令。适合需要进行底层系统管理和命令行操作的场景。", "en": "Super admin toolkit providing advanced terminal and shell capabilities. The terminal tool runs in an Ubuntu environment (with sdcard/storage mounted). The shell tool executes Android system commands directly via Shizuku/Root. Useful for low-level system administration and CLI operations." },
"enabledByDefault": true,
"category": "System",
"tools": [
{
"name": "terminal",
"description": { "zh": "在Ubuntu环境中执行命令并收集输出结果。运行环境:完整的Ubuntu系统,已正确挂载sdcard和storage目录,可访问Android存储空间。所有命令将会在相同的会话执行且上下文连贯。强烈建议每次都显式传 timeoutMs,避免命令卡住。禁止使用 `set -e`、`set -o errexit` 等会改变 shell 退出行为的命令,这会导致终端会话直接退出并卡死。若未传,前台默认15秒超时;background=true 时不使用该默认超时。命令超时时不会被自动取消,不需要重新执行命令,请继续通过 terminal_getscreen 跟踪当前屏幕内容。", "en": "Execute commands in an Ubuntu environment and collect output. Environment: full Ubuntu system with sdcard/storage mounted, allowing access to Android storage. Automatically preserves working-directory context. Strongly recommend explicitly passing timeoutMs every time to avoid hangs. Do not use commands such as `set -e` or `set -o errexit` that change shell exit behavior, because they can cause the terminal session to exit and hang. If omitted, foreground mode defaults to 15s timeout; background=true does not use this default timeout. When a command times out, it is not automatically cancelled. Do not rerun the command; continue tracking the current screen via terminal_getscreen." },
"parameters": [
{
"name": "command",
"description": { "zh": "要执行的命令", "en": "Command to execute." },
"type": "string",
"required": true
},
{
"name": "background",
"description": { "zh": "是否在后台运行命令,\"true\" 表示后台执行并立即返回,适合启动服务器等长时间运行的任务(AI 不会收到该命令的输出结果),\"false\" 或未提供则前台执行并等待并返回命令结果", "en": "Run command in background. 'true' runs in background and returns immediately (good for long-running tasks like servers; AI will not receive output). 'false' or omitted runs in foreground and returns the command result." },
"type": "string",
"required": false
},
{
"name": "timeoutMs",
"description": { "zh": "可选超时(毫秒,最低3000ms)。强烈建议显式传入;未传时前台默认15000ms,background=true时不使用默认超时。", "en": "Optional timeout (ms, minimum 3000ms). Strongly recommended to pass explicitly; if omitted, foreground defaults to 15000ms, and background=true does not use the default timeout." },
"type": "string",
"required": false
}
]
},
{
"name": "terminal_wait",
"description": { "zh": "等待同一终端会话中的上一条命令执行完成。适用于安装/编译等长命令在 timeout 后继续后台执行的场景。与 sleep 不同,本工具会在命令实际完成时提前返回,而不是固定睡眠。", "en": "Wait until the previous command in the same terminal session finishes. Useful when long install/build commands continue running after a timeout. Unlike sleep, this tool can return early as soon as the command actually completes." },
"parameters": [
{
"name": "sessionId",
"description": { "zh": "可选目标会话ID。不传则使用当前对话的默认会话;无 chatId 时为 super_admin_default_session。", "en": "Optional target session ID. If omitted, uses the current chat's default session; without chatId it is super_admin_default_session." },
"type": "string",
"required": false
},
{
"name": "timeoutMs",
"description": { "zh": "可选超时(毫秒,最低3000ms)。未传时默认300000ms(5分钟)。", "en": "Optional timeout (ms, minimum 3000ms). Defaults to 300000ms (5 minutes) if omitted." },
"type": "string",
"required": false
}
]
},
{
"name": "terminal_getscreen",
"description": { "zh": "获取当前终端会话可见屏幕内容(仅一屏,不包含历史滚动缓冲)。", "en": "Get the current visible screen content for the active terminal session (single screen only, no scrollback history)." },
"parameters": []
},
{
"name": "terminal_input",
"description": { "zh": "向当前终端会话写入输入。input 与 control 至少传一个。常见用法:先写 input,再写 control=enter 提交;control=ctrl 且 input=c 可发送 Ctrl+C。", "en": "Write input to the active terminal session. Provide at least one of input or control. Typical usage: send input first, then control=enter to submit; use control=ctrl with input=c for Ctrl+C." },
"parameters": [
{
"name": "input",
"description": { "zh": "写入终端的文本", "en": "Text to write to terminal." },
"type": "string",
"required": false
},
{
"name": "control",
"description": { "zh": "控制键,例如 enter / tab / esc / ctrl", "en": "Control key, e.g. enter / tab / esc / ctrl." },
"type": "string",
"required": false
}
]
},
{
"name": "shell",
"description": { "zh": "通过Shizuku/Root权限直接在Android系统中执行Shell命令。运行环境:直接访问Android系统,具有系统级权限,适用于需要操作Android系统底层的场景(如pm、am等系统命令)。", "en": "Execute shell commands directly on Android with Shizuku/Root. Environment: direct Android system access with system-level privileges, suitable for low-level commands such as pm/am." },
"parameters": [
{
"name": "command",
"description": { "zh": "要执行的Shell命令", "en": "Shell command to execute." },
"type": "string",
"required": true
}
]
}
]
}*/
const superAdmin = (function () {
const MAX_INLINE_TERMINAL_OUTPUT_CHARS = 12_000;
const DEFAULT_FOREGROUND_TIMEOUT_MS = 15_000;
const DEFAULT_WAIT_TIMEOUT_MS = 300_000;
const MIN_TIMEOUT_MS = 3_000;
const DEFAULT_TERMINAL_SESSION_NAME = "super_admin_default_session";
const BACKGROUND_TERMINAL_SESSION_PREFIX = "super_admin_background";
function getCurrentChatSessionSuffix(): string {
const chatId = getChatId();
if (chatId === undefined) {
return "";
}
const normalizedChatId = chatId.trim();
if (!normalizedChatId) {
return "";
}
return normalizedChatId.replace(/[^a-zA-Z0-9._-]+/g, "_");
}
function getDefaultTerminalSessionName(): string {
const chatSuffix = getCurrentChatSessionSuffix();
return chatSuffix
? `${DEFAULT_TERMINAL_SESSION_NAME}_${chatSuffix}`
: DEFAULT_TERMINAL_SESSION_NAME;
}
function getBackgroundTerminalSessionName(): string {
const chatSuffix = getCurrentChatSessionSuffix();
const prefix = chatSuffix
? `${BACKGROUND_TERMINAL_SESSION_PREFIX}_${chatSuffix}`
: BACKGROUND_TERMINAL_SESSION_PREFIX;
return `${prefix}_${Date.now()}`;
}
async function persistTerminalOutputIfTooLong(command: string, result: any): Promise<any | null> {
const outputStr = typeof result?.output === "string"
? result.output
: String(result?.output ?? "");
if (outputStr.length <= MAX_INLINE_TERMINAL_OUTPUT_CHARS) {
return null;
}
await Tools.Files.mkdir(OPERIT_CLEAN_ON_EXIT_DIR, true);
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const rand = Math.floor(Math.random() * 1_000_000);
const filePath = `${OPERIT_CLEAN_ON_EXIT_DIR}/terminal_output_${timestamp}_${rand}.log`;
await Tools.Files.write(filePath, outputStr, false);
return {
command,
output: "(saved_to_file)",
exitCode: result?.exitCode,
sessionId: result?.sessionId,
context_preserved: true,
output_saved_to: filePath,
output_chars: outputStr.length,
operit_clean_on_exit_dir: OPERIT_CLEAN_ON_EXIT_DIR,
hint: "Output is large and saved to file. Use read_file_part or grep_code to inspect it.",
};
}
/**
* 在Ubuntu环境中执行终端命令并收集输出结果
* 运行环境:完整的Ubuntu系统,已正确挂载sdcard和storage目录
* 禁止使用 set -e / set -o errexit 等会改变 shell 退出行为的命令,否则可能导致终端会话退出并卡死
* @param command - 要执行的命令
* @param background - 是否后台运行("true" 为后台执行并立即返回,适合启动服务器等长时间运行任务,AI 不会收到该命令的输出结果)
* @param timeoutMs - 可选的超时时间(毫秒,最低 3000ms)。强烈建议显式传入;前台未传时默认 15000ms,后台模式不应用该默认值。
*/
async function terminal(params: { command: string, background?: string, timeoutMs?: string }): Promise<any> {
try {
if (!params.command) {
throw new Error("命令不能为空");
}
const command = params.command;
const background = params.background;
const timeoutMs = params.timeoutMs;
console.log(`执行终端命令: ${command}`);
const isBackground = background === "true";
let timeout: number | undefined;
if (!isBackground) {
if (timeoutMs !== undefined) {
const parsedTimeout = parseInt(timeoutMs, 10);
if (!Number.isFinite(parsedTimeout) || parsedTimeout < MIN_TIMEOUT_MS) {
throw new Error(`timeoutMs必须是整数且不少于${MIN_TIMEOUT_MS}毫秒`);
}
timeout = parsedTimeout;
} else {
timeout = DEFAULT_FOREGROUND_TIMEOUT_MS;
}
}
if (isBackground) {
const session = await Tools.System.terminal.create(getBackgroundTerminalSessionName());
const sessionId = session.sessionId;
// 调用系统工具执行终端命令
(async () => {
try {
await Tools.System.terminal.exec(sessionId, command);
} catch (error) {
console.error(`[terminal/background] 错误: ${error.message}`);
console.error(error.stack);
}
})();
return {
command: command,
background: true,
sessionId: sessionId,
started: true
};
}
// 创建或获取一个默认会话
const session = await Tools.System.terminal.create(getDefaultTerminalSessionName());
const sessionId = session.sessionId;
// 调用系统工具执行终端命令
const result = await Tools.System.terminal.exec(sessionId, command, timeout);
const timedOut = result.timedOut === true;
let timeoutScreen: { sessionId: string, rows: number, cols: number, content: string } | null = null;
if (timedOut) {
const screenResult = await Tools.System.terminal.screen(sessionId);
timeoutScreen = {
sessionId: screenResult.sessionId ?? sessionId,
rows: screenResult.rows,
cols: screenResult.cols,
content: screenResult.content
};
}
const persistedResult = await persistTerminalOutputIfTooLong(command, result);
if (persistedResult) {
persistedResult.timeoutMsUsed = timeout;
if (timeoutScreen) {
persistedResult.timeoutScreen = timeoutScreen;
}
return persistedResult;
}
return {
command: command,
output: result.output,
exitCode: result.exitCode,
sessionId: result.sessionId,
timedOut: timedOut,
timeoutMsUsed: timeout,
timeoutScreen: timeoutScreen,
context_preserved: true // 标记此命令保留了目录上下文
};
} catch (error) {
console.error(`[terminal] 错误: ${error.message}`);
console.error(error.stack);
throw error;
}
}
/**
* 等待同一终端会话中的上一条命令执行完成
* 原理:向同会话追加一个内部 marker 命令。由于会话按序执行,marker 开始执行即代表前序命令已完成。
* @param sessionId - 可选会话ID;不传时使用当前对话的默认会话,无 chatId 时为 super_admin_default_session
* @param timeoutMs - 可选超时(毫秒,最低 3000ms);未传默认 300000ms
*/
async function terminal_wait(params: { sessionId?: string, timeoutMs?: string } = {}): Promise<any> {
try {
const timeoutMs = params.timeoutMs;
let timeout = DEFAULT_WAIT_TIMEOUT_MS;
if (timeoutMs !== undefined) {
const parsedTimeout = parseInt(timeoutMs, 10);
if (!Number.isFinite(parsedTimeout) || parsedTimeout < MIN_TIMEOUT_MS) {
throw new Error(`timeoutMs必须是整数且不少于${MIN_TIMEOUT_MS}毫秒`);
}
timeout = parsedTimeout;
}
const session =
params.sessionId
? { sessionId: params.sessionId }
: await Tools.System.terminal.create(getDefaultTerminalSessionName());
const sessionId = session.sessionId;
const marker = `__OPERIT_TERMINAL_WAIT_DONE_${Date.now()}_${Math.floor(Math.random() * 1_000_000)}__`;
const waitCommand = `printf '${marker}\\n'`;
const startedAt = Date.now();
const result = await Tools.System.terminal.exec(sessionId, waitCommand, timeout);
const elapsedMs = Date.now() - startedAt;
const timedOut = result?.timedOut === true;
let timeoutScreen: { sessionId: string, rows: number, cols: number, content: string } | null = null;
if (timedOut) {
const screenResult = await Tools.System.terminal.screen(sessionId);
timeoutScreen = {
sessionId: screenResult.sessionId ?? sessionId,
rows: screenResult.rows,
cols: screenResult.cols,
content: screenResult.content
};
}
const outputStr = typeof result?.output === "string"
? result.output
: String(result?.output ?? "");
const markerSeen = outputStr.includes(marker);
return {
sessionId,
timedOut,
timeoutMsUsed: timeout,
elapsedMs,
waitCompleted: !timedOut && markerSeen,
markerSeen,
exitCode: result?.exitCode,
timeoutScreen,
context_preserved: true
};
} catch (error) {
console.error(`[terminal_wait] 错误: ${error.message}`);
console.error(error.stack);
throw error;
}
}
/**
* 通过Shizuku/Root权限在Android系统中执行Shell命令
* 运行环境:直接访问Android系统,具有系统级权限
* @param command - 要执行的Shell命令
*/
async function shell(params: { command: string }): Promise<any> {
try {
if (!params.command) {
throw new Error("命令不能为空");
}
const command = params.command;
console.log(`执行Shell命令: ${command}`);
// 通过Shizuku/Root权限执行shell操作
const result = await Tools.System.shell(`${command}`);
return {
command: command,
output: result.output,
exitCode: result.exitCode
};
} catch (error) {
console.error(`[shell] 错误: ${error.message}`);
console.error(error.stack);
throw error;
}
}
/**
* 获取当前终端会话可见屏幕内容(仅一屏,不包含历史)
*/
async function terminal_getscreen(_params: {} = {}): Promise<any> {
try {
const session = await Tools.System.terminal.create(getDefaultTerminalSessionName());
const sessionId = session.sessionId;
const result = await Tools.System.terminal.screen(sessionId);
return {
sessionId: result.sessionId ?? sessionId,
rows: result.rows,
cols: result.cols,
content: result.content,
commandRunning: result.commandRunning === true
};
} catch (error) {
console.error(`[terminal_getscreen] 错误: ${error.message}`);
console.error(error.stack);
throw error;
}
}
/**
* 向当前终端会话写入输入
* @param input - 文本输入
* @param control - 控制键
*/
async function terminal_input(params: { input?: string, control?: string } = {}): Promise<any> {
try {
if (params.input === undefined && params.control === undefined) {
throw new Error("input和control至少需要提供一个");
}
const session = await Tools.System.terminal.create(getDefaultTerminalSessionName());
const sessionId = session.sessionId;
const result = await Tools.System.terminal.input(sessionId, {
input: params.input,
control: params.control
});
return {
sessionId: sessionId,
input: params.input,
control: params.control,
result: result?.value ?? String(result ?? "")
};
} catch (error) {
console.error(`[terminal_input] 错误: ${error.message}`);
console.error(error.stack);
throw error;
}
}
return {
terminal,
terminal_wait,
terminal_getscreen,
terminal_input,
shell
};
})();
// 逐个导出
exports.terminal = superAdmin.terminal;
exports.terminal_wait = superAdmin.terminal_wait;
exports.terminal_getscreen = superAdmin.terminal_getscreen;
exports.terminal_input = superAdmin.terminal_input;
exports.shell = superAdmin.shell;