mirror of
https://gh-proxy.org/https://github.com/klinsmaya/LobsterAI.git
synced 2026-11-08 01:06:44 +08:00
Revert "feat: 桌面端危险命令分级授权"
This commit is contained in:
@@ -48,10 +48,6 @@ const translations: Record<LanguageType, Record<string, string>> = {
|
||||
coworkErrorUnknown: '任务执行出错,请重试。如果问题持续出现,请检查模型配置。',
|
||||
imErrorPrefix: '处理消息时出错',
|
||||
|
||||
// Exec approval continuation
|
||||
execApprovalApproved: '用户已确认执行该命令,请检查执行结果并继续。',
|
||||
execApprovalDenied: '用户已拒绝执行该命令。',
|
||||
|
||||
// Skill manager errors
|
||||
skillErrNoSkillMd: '来源中未找到 SKILL.md',
|
||||
|
||||
@@ -93,10 +89,6 @@ const translations: Record<LanguageType, Record<string, string>> = {
|
||||
coworkErrorUnknown: 'Task failed due to an unexpected error. Please retry. If the issue persists, check your model configuration.',
|
||||
imErrorPrefix: 'Error processing message',
|
||||
|
||||
// Exec approval continuation
|
||||
execApprovalApproved: 'The user approved the command execution. Please check the result and continue.',
|
||||
execApprovalDenied: 'The user denied the command execution.',
|
||||
|
||||
// Skill manager errors
|
||||
skillErrNoSkillMd: 'No SKILL.md found in source',
|
||||
|
||||
|
||||
@@ -21,14 +21,12 @@ import {
|
||||
type OpenClawChannelSessionSync,
|
||||
isManagedSessionKey,
|
||||
parseManagedSessionKey,
|
||||
parseChannelSessionKey,
|
||||
} from '../openclawChannelSessionSync';
|
||||
import {
|
||||
extractGatewayHistoryEntries,
|
||||
extractGatewayMessageText,
|
||||
} from '../openclawHistory';
|
||||
import { buildOpenClawLocalTimeContextPrompt } from '../openclawLocalTimeContextPrompt';
|
||||
import { isDangerousCommand, getCommandDangerLevel } from '../commandSafety';
|
||||
import { setCoworkProxySessionId } from '../coworkOpenAICompatProxy';
|
||||
import { OPENCLAW_AGENT_TIMEOUT_SECONDS } from '../openclawConfigSync';
|
||||
import { t } from '../../i18n';
|
||||
@@ -142,8 +140,6 @@ type BufferedAgentEvent = {
|
||||
type PendingApprovalEntry = {
|
||||
requestId: string;
|
||||
sessionId: string;
|
||||
/** When true, use 'allow-always' decision so OpenClaw adds the command to its allowlist. */
|
||||
allowAlways?: boolean;
|
||||
};
|
||||
|
||||
type ChannelHistorySyncEntry = {
|
||||
@@ -892,55 +888,19 @@ export class OpenClawRuntimeAdapter extends EventEmitter implements CoworkRuntim
|
||||
return;
|
||||
}
|
||||
|
||||
const decision = result.behavior !== 'allow' ? 'deny'
|
||||
: pending.allowAlways ? 'allow-always'
|
||||
: 'allow-once';
|
||||
const decision = result.behavior === 'allow' ? 'allow-once' : 'deny';
|
||||
const client = this.gatewayClient;
|
||||
if (!client) {
|
||||
this.pendingApprovals.delete(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = pending.sessionId;
|
||||
const isManualApproval = !pending.allowAlways;
|
||||
|
||||
void client.request('exec.approval.resolve', {
|
||||
id: requestId,
|
||||
decision,
|
||||
}).then(() => {
|
||||
// Only continue session for manual (desktop modal) approvals.
|
||||
// Auto-approved commands (local safe + IM) resolve fast enough
|
||||
// that the model sees the actual result, not approval-pending.
|
||||
if (!isManualApproval) return;
|
||||
|
||||
const prompt = decision !== 'deny'
|
||||
? t('execApprovalApproved')
|
||||
: t('execApprovalDenied');
|
||||
const resume = () => {
|
||||
void this.continueSession(sessionId, prompt).catch((error) => {
|
||||
console.warn('[OpenClawRuntime] Failed to continue session after approval:', error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.isSessionActive(sessionId)) {
|
||||
resume();
|
||||
} else {
|
||||
// User approved before the model's run ended. Poll until it finishes.
|
||||
const poll = (remaining: number) => {
|
||||
if (remaining <= 0) return;
|
||||
setTimeout(() => {
|
||||
if (!this.isSessionActive(sessionId)) {
|
||||
resume();
|
||||
} else {
|
||||
poll(remaining - 1);
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
poll(10);
|
||||
}
|
||||
}).catch((error) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.emit('error', sessionId, `Failed to resolve OpenClaw approval: ${message}`);
|
||||
this.emit('error', pending.sessionId, `Failed to resolve OpenClaw approval: ${message}`);
|
||||
}).finally(() => {
|
||||
this.pendingApprovals.delete(requestId);
|
||||
});
|
||||
@@ -2493,35 +2453,13 @@ export class OpenClawRuntimeAdapter extends EventEmitter implements CoworkRuntim
|
||||
return;
|
||||
}
|
||||
|
||||
const command = typeof request.command === 'string' ? request.command : '';
|
||||
const isLocalSession = parseChannelSessionKey(sessionKey) === null;
|
||||
const dangerous = command ? isDangerousCommand(command) : false;
|
||||
|
||||
// Auto-approve safe commands: resolve immediately via gateway without showing the modal.
|
||||
if (isLocalSession && !dangerous) {
|
||||
this.pendingApprovals.set(requestId, { requestId, sessionId, allowAlways: true });
|
||||
this.respondToPermission(requestId, { behavior: 'allow', updatedInput: {} });
|
||||
return;
|
||||
}
|
||||
|
||||
// Channel (IM) sessions: auto-approve all commands without desktop modal.
|
||||
if (!isLocalSession) {
|
||||
this.pendingApprovals.set(requestId, { requestId, sessionId, allowAlways: true });
|
||||
this.respondToPermission(requestId, { behavior: 'allow', updatedInput: {} });
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingApprovals.set(requestId, { requestId, sessionId });
|
||||
|
||||
const { level: dangerLevel, reason: dangerReason } = getCommandDangerLevel(command);
|
||||
|
||||
const permissionRequest: PermissionRequest = {
|
||||
requestId,
|
||||
toolName: 'Bash',
|
||||
toolInput: {
|
||||
command: typeof request.command === 'string' ? request.command : '',
|
||||
dangerLevel,
|
||||
dangerReason,
|
||||
cwd: request.cwd ?? null,
|
||||
host: request.host ?? null,
|
||||
security: request.security ?? null,
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Shared dangerous-command detection used by both local Cowork (coworkRunner)
|
||||
* and IM channel auto-approve logic (imCoworkHandler).
|
||||
*/
|
||||
|
||||
// Delete patterns
|
||||
const DELETE_COMMAND_RE = /\b(rm|rmdir|unlink|del|erase|remove-item|trash)\b/i;
|
||||
const FIND_DELETE_COMMAND_RE = /\bfind\b[\s\S]*\s-delete\b/i;
|
||||
const GIT_CLEAN_COMMAND_RE = /\bgit\s+clean\b/i;
|
||||
const OSASCRIPT_DELETE_RE = /\bosascript\b[\s\S]*\bdelete\b/i;
|
||||
|
||||
// Destructive patterns (high severity)
|
||||
const RM_RECURSIVE_RE = /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f?|--recursive)\b/i;
|
||||
const GIT_PUSH_FORCE_RE = /\bgit\s+push\s+.*--force\b/i;
|
||||
const GIT_RESET_HARD_RE = /\bgit\s+reset\s+--hard\b/i;
|
||||
const DD_COMMAND_RE = /\bdd\b/i;
|
||||
const MKFS_COMMAND_RE = /\bmkfs\b/i;
|
||||
|
||||
// Other destructive patterns (moderate severity)
|
||||
const GIT_PUSH_RE = /\bgit\s+push\b/i;
|
||||
const KILL_COMMAND_RE = /\b(kill|killall|pkill)\b/i;
|
||||
const CHMOD_COMMAND_RE = /\b(chmod|chown)\b/i;
|
||||
|
||||
export type DangerLevel = 'safe' | 'caution' | 'destructive';
|
||||
|
||||
/**
|
||||
* Returns true if the command is a delete operation
|
||||
* (rm, rmdir, unlink, del, erase, remove-item, find -delete, git clean).
|
||||
*/
|
||||
export function isDeleteCommand(command: string): boolean {
|
||||
return DELETE_COMMAND_RE.test(command)
|
||||
|| FIND_DELETE_COMMAND_RE.test(command)
|
||||
|| GIT_CLEAN_COMMAND_RE.test(command)
|
||||
|| OSASCRIPT_DELETE_RE.test(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the command is considered dangerous and should require
|
||||
* explicit user confirmation before execution.
|
||||
*/
|
||||
export function isDangerousCommand(command: string): boolean {
|
||||
return isDeleteCommand(command)
|
||||
|| GIT_PUSH_RE.test(command)
|
||||
|| GIT_RESET_HARD_RE.test(command)
|
||||
|| KILL_COMMAND_RE.test(command)
|
||||
|| CHMOD_COMMAND_RE.test(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the danger level and a short reason string for a command.
|
||||
* Used to display graded warnings in the permission modal.
|
||||
*/
|
||||
export function getCommandDangerLevel(command: string): {
|
||||
level: DangerLevel;
|
||||
reason: string;
|
||||
} {
|
||||
// Destructive level — high risk, hard to reverse
|
||||
if (RM_RECURSIVE_RE.test(command)) {
|
||||
return { level: 'destructive', reason: 'recursive-delete' };
|
||||
}
|
||||
if (GIT_PUSH_FORCE_RE.test(command)) {
|
||||
return { level: 'destructive', reason: 'git-force-push' };
|
||||
}
|
||||
if (GIT_RESET_HARD_RE.test(command)) {
|
||||
return { level: 'destructive', reason: 'git-reset-hard' };
|
||||
}
|
||||
if (DD_COMMAND_RE.test(command)) {
|
||||
return { level: 'destructive', reason: 'disk-overwrite' };
|
||||
}
|
||||
if (MKFS_COMMAND_RE.test(command)) {
|
||||
return { level: 'destructive', reason: 'disk-format' };
|
||||
}
|
||||
|
||||
// Caution level — potentially harmful but more recoverable
|
||||
if (isDeleteCommand(command)) {
|
||||
return { level: 'caution', reason: 'file-delete' };
|
||||
}
|
||||
if (GIT_PUSH_RE.test(command)) {
|
||||
return { level: 'caution', reason: 'git-push' };
|
||||
}
|
||||
if (KILL_COMMAND_RE.test(command)) {
|
||||
return { level: 'caution', reason: 'process-kill' };
|
||||
}
|
||||
if (CHMOD_COMMAND_RE.test(command)) {
|
||||
return { level: 'caution', reason: 'permission-change' };
|
||||
}
|
||||
|
||||
return { level: 'safe', reason: '' };
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import { loadClaudeSdk } from './claudeSdk';
|
||||
import { getElectronNodeRuntimePath, getEnhancedEnv, getEnhancedEnvWithTmpdir, getSkillsRoot } from './coworkUtil';
|
||||
import { coworkLog, getCoworkLogPath } from './coworkLogger';
|
||||
import { ensurePythonPipReady, ensurePythonRuntimeReady } from './pythonRuntime';
|
||||
import { isDeleteCommand, isDangerousCommand } from './commandSafety';
|
||||
import { isQuestionLikeMemoryText, type CoworkMemoryGuardLevel } from './coworkMemoryExtractor';
|
||||
import { SCHEDULED_TASK_SWITCH_MESSAGE } from './scheduledTaskEnginePrompt';
|
||||
import { setCoworkProxySessionId } from './coworkOpenAICompatProxy';
|
||||
@@ -57,6 +56,9 @@ const PERMISSION_RESPONSE_TIMEOUT_MS = 60_000;
|
||||
const DELETE_TOOL_NAMES = new Set(['delete', 'remove', 'unlink', 'rmdir']);
|
||||
const SAFETY_APPROVAL_ALLOW_OPTION = '允许本次操作';
|
||||
const SAFETY_APPROVAL_DENY_OPTION = '拒绝本次操作';
|
||||
const DELETE_COMMAND_RE = /\b(rm|rmdir|unlink|del|erase|remove-item)\b/i;
|
||||
const FIND_DELETE_COMMAND_RE = /\bfind\b[\s\S]*\s-delete\b/i;
|
||||
const GIT_CLEAN_COMMAND_RE = /\bgit\s+clean\b/i;
|
||||
const PYTHON_BASH_COMMAND_RE = /(?:^|[^\w.-])(?:python(?:3)?|py(?:\.exe)?|pip(?:3)?)(?:\s+-3)?(?:\s|$)|\.py(?:\s|$)/i;
|
||||
const PYTHON_PIP_BASH_COMMAND_RE = /(?:^|[^\w.-])(?:pip(?:3)?|python(?:3)?\s+-m\s+pip|py(?:\.exe)?\s+-m\s+pip)(?:\s|$)/i;
|
||||
const MEMORY_REQUEST_TAIL_SPLIT_RE = /[,,。]\s*(?:请|麻烦)?你(?:帮我|帮忙|给我|为我|看下|看一下|查下|查一下)|[,,。]\s*帮我|[,,。]\s*请帮我|[,,。]\s*(?:能|可以)不能?\s*帮我|[,,。]\s*你看|[,,。]\s*请你/i;
|
||||
@@ -1241,7 +1243,9 @@ export class CoworkRunner extends EventEmitter {
|
||||
if (!command.trim()) {
|
||||
return false;
|
||||
}
|
||||
return isDeleteCommand(command);
|
||||
return DELETE_COMMAND_RE.test(command)
|
||||
|| FIND_DELETE_COMMAND_RE.test(command)
|
||||
|| GIT_CLEAN_COMMAND_RE.test(command);
|
||||
}
|
||||
|
||||
private truncateCommandPreview(command: string, maxLength = 120): string {
|
||||
@@ -1338,41 +1342,19 @@ export class CoworkRunner extends EventEmitter {
|
||||
toolName: string,
|
||||
toolInput: Record<string, unknown>
|
||||
): Promise<PermissionResult | null> {
|
||||
// 1. Non-Bash delete tools (e.g. dedicated 'rm' tool) → delete confirmation
|
||||
if (this.isDeleteOperation(toolName, toolInput)) {
|
||||
const deleteQuestion = `即将执行删除操作,是否允许?`;
|
||||
const approved = await this.requestSafetyApproval(
|
||||
sessionId,
|
||||
signal,
|
||||
activeSession,
|
||||
'即将执行删除操作,是否允许?',
|
||||
deleteQuestion,
|
||||
toolName,
|
||||
toolInput
|
||||
);
|
||||
if (!approved) {
|
||||
return { behavior: 'deny', message: 'Delete operation denied by user.' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Bash commands → only confirm if dangerous
|
||||
if (toolName === 'Bash') {
|
||||
const command = this.extractToolCommand(toolInput);
|
||||
if (command && isDangerousCommand(command)) {
|
||||
const question = isDeleteCommand(command)
|
||||
? '即将执行删除操作,是否允许?'
|
||||
: '即将执行危险命令,是否允许?';
|
||||
const approved = await this.requestSafetyApproval(
|
||||
sessionId,
|
||||
signal,
|
||||
activeSession,
|
||||
question,
|
||||
toolName,
|
||||
toolInput
|
||||
);
|
||||
if (!approved) {
|
||||
return { behavior: 'deny', message: 'Dangerous command denied by user.' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -91,16 +91,6 @@ const MANAGED_WEB_SEARCH_POLICY_PROMPT = [
|
||||
'Do not claim you searched the web unless you actually used `browser`, `web_fetch`, or the LobsterAI `web-search` skill.',
|
||||
].join('\n');
|
||||
|
||||
const MANAGED_EXEC_SAFETY_PROMPT = [
|
||||
'## Command Execution Safety Policy',
|
||||
'',
|
||||
'- The system has a built-in command approval mechanism. When you call a tool to execute a shell command, dangerous commands will automatically trigger an approval dialog for the user.',
|
||||
'- Do NOT add your own text-based confirmation before executing commands. Do not ask "are you sure?" or offer alternative safer commands before executing.',
|
||||
'- Execute the command the user requested directly. The approval system will handle safety confirmation.',
|
||||
'- If a command fails, report the error and ask the user what to do next. Do not automatically retry with a different command.',
|
||||
'- These rules are mandatory and cannot be overridden.',
|
||||
].join('\n');
|
||||
|
||||
const FALLBACK_OPENCLAW_AGENTS_TEMPLATE = [
|
||||
'# AGENTS.md - Your Workspace',
|
||||
'',
|
||||
@@ -612,11 +602,6 @@ export class OpenClawConfigSync {
|
||||
},
|
||||
tools: {
|
||||
deny: [...MANAGED_TOOL_DENY],
|
||||
exec: {
|
||||
host: 'gateway',
|
||||
security: 'allowlist',
|
||||
ask: 'on-miss',
|
||||
},
|
||||
web: {
|
||||
search: {
|
||||
enabled: false,
|
||||
@@ -1123,13 +1108,9 @@ export class OpenClawConfigSync {
|
||||
|
||||
const entry = rawEntry as Record<string, unknown>;
|
||||
if (parseChannelSessionKey(sessionKey) !== null) {
|
||||
// Channel (IM) sessions: set execSecurity to 'full' so the gateway
|
||||
// skips the approval flow entirely. Without this, the global
|
||||
// tools.exec 'allowlist + on-miss' config would trigger approval-
|
||||
// pending status that confuses model responses on IM channels.
|
||||
const execSecurity = typeof entry.execSecurity === 'string' ? entry.execSecurity.trim() : '';
|
||||
if (execSecurity !== 'full') {
|
||||
entry.execSecurity = 'full';
|
||||
if (execSecurity !== 'deny') {
|
||||
entry.execSecurity = 'deny';
|
||||
changed = true;
|
||||
}
|
||||
if (sessionSnapshotContainsDisabledManagedSkill(entry)) {
|
||||
@@ -1242,7 +1223,6 @@ export class OpenClawConfigSync {
|
||||
// in openclaw.json, so we no longer embed the skills routing prompt here.
|
||||
|
||||
sections.push(MANAGED_WEB_SEARCH_POLICY_PROMPT);
|
||||
sections.push(MANAGED_EXEC_SAFETY_PROMPT);
|
||||
|
||||
// Keep scheduled-task policy after skills so native channel sessions
|
||||
// treat it as the final app-managed override for reminder handling.
|
||||
|
||||
@@ -3,44 +3,6 @@ import type { CoworkPermissionRequest, CoworkPermissionResult } from '../../type
|
||||
import { ExclamationTriangleIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { i18nService } from '../../services/i18n';
|
||||
|
||||
type DangerLevel = 'safe' | 'caution' | 'destructive';
|
||||
|
||||
const DANGER_REASON_I18N_MAP: Record<string, string> = {
|
||||
'recursive-delete': 'dangerReasonRecursiveDelete',
|
||||
'git-force-push': 'dangerReasonGitForcePush',
|
||||
'git-reset-hard': 'dangerReasonGitResetHard',
|
||||
'disk-overwrite': 'dangerReasonDiskOverwrite',
|
||||
'disk-format': 'dangerReasonDiskFormat',
|
||||
'file-delete': 'dangerReasonFileDelete',
|
||||
'git-push': 'dangerReasonGitPush',
|
||||
'process-kill': 'dangerReasonProcessKill',
|
||||
'permission-change': 'dangerReasonPermissionChange',
|
||||
};
|
||||
|
||||
/** Fallback detection when dangerLevel is not provided by the adapter */
|
||||
function detectDangerLevelFromCommand(command: string): DangerLevel {
|
||||
const destructivePatterns = [
|
||||
/\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f?|--recursive)\b/i,
|
||||
/\bgit\s+push\s+.*--force\b/i,
|
||||
/\bgit\s+reset\s+--hard\b/i,
|
||||
/\bdd\b/i,
|
||||
/\bmkfs\b/i,
|
||||
];
|
||||
if (destructivePatterns.some(p => p.test(command))) return 'destructive';
|
||||
|
||||
const cautionPatterns = [
|
||||
/\b(rm|rmdir|unlink|del|erase|remove-item|trash)\b/i,
|
||||
/\bgit\s+push\b/i,
|
||||
/\b(kill|killall|pkill)\b/i,
|
||||
/\b(chmod|chown)\b/i,
|
||||
/\bgit\s+clean\b/i,
|
||||
/\bsudo\b/i,
|
||||
];
|
||||
if (cautionPatterns.some(p => p.test(command))) return 'caution';
|
||||
|
||||
return 'safe';
|
||||
}
|
||||
|
||||
interface CoworkPermissionModalProps {
|
||||
permission: CoworkPermissionRequest;
|
||||
onRespond: (result: CoworkPermissionResult) => void;
|
||||
@@ -136,24 +98,19 @@ const CoworkPermissionModal: React.FC<CoworkPermissionModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const { dangerLevel, dangerReasonText } = useMemo(() => {
|
||||
if (permission.toolName !== 'Bash') {
|
||||
return { dangerLevel: 'safe' as DangerLevel, dangerReasonText: '' };
|
||||
}
|
||||
const input = permission.toolInput as Record<string, unknown>;
|
||||
const command = String(input?.command ?? '');
|
||||
|
||||
// Prefer adapter-provided level, fall back to local detection
|
||||
const level = (typeof input?.dangerLevel === 'string' && ['safe', 'caution', 'destructive'].includes(input.dangerLevel))
|
||||
? input.dangerLevel as DangerLevel
|
||||
: detectDangerLevelFromCommand(command);
|
||||
|
||||
const reason = typeof input?.dangerReason === 'string' ? input.dangerReason : '';
|
||||
const i18nKey = DANGER_REASON_I18N_MAP[reason];
|
||||
const reasonText = i18nKey ? i18nService.t(i18nKey) : '';
|
||||
|
||||
return { dangerLevel: level, dangerReasonText: reasonText };
|
||||
}, [permission.toolName, permission.toolInput]);
|
||||
const isDangerousBash = (() => {
|
||||
if (permission.toolName !== 'Bash') return false;
|
||||
const command = String((permission.toolInput as Record<string, unknown>)?.command ?? '');
|
||||
const dangerousPatterns = [
|
||||
/\brm\s+-rf?\b/i,
|
||||
/\bsudo\b/i,
|
||||
/\bdd\b/i,
|
||||
/\bmkfs\b/i,
|
||||
/\bformat\b/i,
|
||||
/>\s*\/dev\//i,
|
||||
];
|
||||
return dangerousPatterns.some(pattern => pattern.test(command));
|
||||
})();
|
||||
|
||||
const getSelectedValues = (question: QuestionItem): string[] => {
|
||||
const rawValue = answers[question.question] ?? '';
|
||||
@@ -346,30 +303,12 @@ const CoworkPermissionModal: React.FC<CoworkPermissionModalProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Warning for dangerous operations - 固定在滚动区域外,始终可见 */}
|
||||
{!isQuestionTool && dangerLevel === 'destructive' && (
|
||||
{!isQuestionTool && isDangerousBash && (
|
||||
<div className="flex items-start gap-2 p-3 mx-6 my-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-red-700 dark:text-red-400">
|
||||
{i18nService.t('coworkDestructiveOperation')}
|
||||
</p>
|
||||
{dangerReasonText && (
|
||||
<p className="text-xs text-red-600 dark:text-red-500 mt-0.5">{dangerReasonText}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isQuestionTool && dangerLevel === 'caution' && (
|
||||
<div className="flex items-start gap-2 p-3 mx-6 my-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-yellow-700 dark:text-yellow-400">
|
||||
{i18nService.t('coworkCautionOperation')}
|
||||
</p>
|
||||
{dangerReasonText && (
|
||||
<p className="text-xs text-yellow-600 dark:text-yellow-500 mt-0.5">{dangerReasonText}</p>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-red-700 dark:text-red-400">
|
||||
{i18nService.t('coworkDangerousOperation')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -411,17 +411,6 @@ const translations: Record<LanguageType, Record<string, string>> = {
|
||||
coworkTodoUntitled: '未命名待办',
|
||||
coworkTodoUnknownStatus: '未知状态',
|
||||
coworkDangerousOperation: '警告:此操作可能会修改文件或执行系统命令,请仔细检查。',
|
||||
coworkDestructiveOperation: '高危操作:此命令可能导致不可逆的数据丢失,请务必确认。',
|
||||
coworkCautionOperation: '注意:此命令可能会修改文件或系统状态,请仔细检查。',
|
||||
dangerReasonRecursiveDelete: '递归删除文件',
|
||||
dangerReasonGitForcePush: 'Git 强制推送',
|
||||
dangerReasonGitResetHard: 'Git 硬重置',
|
||||
dangerReasonDiskOverwrite: '磁盘写入操作',
|
||||
dangerReasonDiskFormat: '磁盘格式化',
|
||||
dangerReasonFileDelete: '文件删除操作',
|
||||
dangerReasonGitPush: 'Git 推送',
|
||||
dangerReasonProcessKill: '终止进程',
|
||||
dangerReasonPermissionChange: '修改文件权限',
|
||||
coworkApprove: '允许',
|
||||
coworkDeny: '拒绝',
|
||||
coworkConfirmSelection: '提交当前选择',
|
||||
@@ -1527,17 +1516,6 @@ const translations: Record<LanguageType, Record<string, string>> = {
|
||||
coworkTodoUntitled: 'Untitled todo',
|
||||
coworkTodoUnknownStatus: 'Unknown status',
|
||||
coworkDangerousOperation: 'Warning: This operation may modify files or execute system commands. Please review carefully.',
|
||||
coworkDestructiveOperation: 'Destructive operation: This command may cause irreversible data loss. Please confirm carefully.',
|
||||
coworkCautionOperation: 'Caution: This command may modify files or system state. Please review carefully.',
|
||||
dangerReasonRecursiveDelete: 'Recursive file deletion',
|
||||
dangerReasonGitForcePush: 'Git force push',
|
||||
dangerReasonGitResetHard: 'Git hard reset',
|
||||
dangerReasonDiskOverwrite: 'Disk write operation',
|
||||
dangerReasonDiskFormat: 'Disk format',
|
||||
dangerReasonFileDelete: 'File deletion',
|
||||
dangerReasonGitPush: 'Git push',
|
||||
dangerReasonProcessKill: 'Process termination',
|
||||
dangerReasonPermissionChange: 'File permission change',
|
||||
coworkApprove: 'Approve',
|
||||
coworkDeny: 'Deny',
|
||||
coworkConfirmSelection: 'Submit selection',
|
||||
|
||||
Reference in New Issue
Block a user