feat: add silent disable option and workspace prompt split

This commit is contained in:
JOJO 2026-02-03 23:11:15 +08:00
parent 50b0dd9336
commit 55ef45e04d
11 changed files with 300 additions and 150 deletions

View File

@ -145,6 +145,7 @@ class MainTerminal:
)
self.easter_egg_manager = EasterEggManager()
self._announced_sub_agent_tasks = set()
self.silent_tool_disable = False # 是否静默工具禁用提示
self.current_session_id = 0 # 用于标识不同的任务会话
# 工具类别(可被管理员动态覆盖)
self.tool_categories_map = dict(TOOL_CATEGORIES)
@ -428,6 +429,9 @@ class MainTerminal:
except ValueError:
logger.warning("忽略无效默认运行模式: %s", preferred_mode)
# 静默禁用工具提示
self.silent_tool_disable = bool(effective_config.get("silent_tool_disable"))
def _handle_read_tool(self, arguments: Dict) -> Dict:
"""集中处理 read_file 工具的三种模式。"""
@ -664,6 +668,8 @@ class MainTerminal:
def _format_disabled_tool_notice(self) -> Optional[str]:
"""生成禁用工具提示信息 / Format disabled tool notice."""
if getattr(self, "silent_tool_disable", False):
return None
if not self.disabled_notice_tools:
return None
@ -2508,6 +2514,10 @@ class MainTerminal:
{"role": "system", "content": system_prompt}
]
workspace_system = self.context_manager._build_workspace_system_message(context)
if workspace_system:
messages.append({"role": "system", "content": workspace_system})
if self.tool_category_states.get("todo", True):
todo_prompt = self.load_prompt("todo_guidelines").strip()
if todo_prompt:

View File

@ -38,6 +38,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"tool_intent_enabled": True,
"default_model": "kimi-k2.5",
"image_compression": "original", # original / 1080p / 720p / 540p
"silent_tool_disable": False, # 禁用工具时不向模型插入提示
}
__all__ = [
@ -164,6 +165,12 @@ def sanitize_personalization_payload(
elif base.get("image_compression") not in allowed_image_modes:
base["image_compression"] = "original"
# 静默禁用工具提示
if "silent_tool_disable" in data:
base["silent_tool_disable"] = bool(data.get("silent_tool_disable"))
else:
base["silent_tool_disable"] = bool(base.get("silent_tool_disable"))
return base

View File

@ -136,22 +136,7 @@
---
## 7. 工作区信息
- **运行环境**:隔离容器中(挂载目录 {container_path}),宿主机路径已隐藏
- **资源限制**CPU {container_cpus} 核,内存 {container_memory},磁盘配额 {project_storage}
- **当前时间**{current_time}
- **项目结构**
```
{file_tree}
```
- **长期记忆**{memory}
---
## 8. 核心原则
## 7. 核心原则
1. **安全第一**:只操作授权范围内的文件
2. **沟通为主**:不确定时多问,不要自作主张
@ -159,8 +144,6 @@
4. **用户友好**:用简单的语言解释复杂的操作
5. **正确执行**:主动确认细节,获得明确许可后再开始
---
## 9. 个性化配置
## 8. 个性化配置
当用户的个性化信息与上文冲突时,以用户的个性化信息为准。

View File

@ -154,22 +154,7 @@
---
## 7. 工作区信息
- **运行环境**:隔离容器中(挂载目录 {container_path}),宿主机路径已隐藏
- **资源限制**CPU {container_cpus} 核,内存 {container_memory},磁盘配额 {project_storage}
- **当前时间**{current_time}
- **项目结构**
```
{file_tree}
```
- **长期记忆**{memory}
---
## 8. 核心原则
## 7. 核心原则
1. **安全第一**:只操作授权范围内的文件
2. **沟通为主**:不确定时多问,不要自作主张
@ -177,8 +162,6 @@
4. **用户友好**:用简单的语言解释复杂的操作
5. **正确执行**:主动确认细节,获得明确许可后再开始
---
## 9. 个性化配置
## 8. 个性化配置
当用户的个性化信息与上文冲突时,以用户的个性化信息为准。

View File

@ -0,0 +1,12 @@
## 工作区信息
- **运行环境**{runtime_environment}
- **资源限制**{resource_limit}
- **当前时间**{current_time}
- **项目结构**
```
{file_tree}
```
- **长期记忆**{memory}

View File

@ -63,6 +63,7 @@
</span>
</label>
</div>
<p class="section-note">仅作用于当前工作区可随时在左下角开关</p>
<div class="personalization-sections">
<div class="personal-left-column">
<div class="personal-section personal-info">
@ -254,25 +255,6 @@
Qwen-Max 仅支持快速模式Qwen-VL 不支持深度思考模式选择时会给出提示
</p>
</div>
</div>
<div class="personal-actions-row">
<div class="personal-form-actions card-aligned">
<div class="personal-status-group">
<transition name="personal-status-fade">
<span class="status success" v-if="status">{{ status }}</span>
</transition>
<transition name="personal-status-fade">
<span class="status error" v-if="error">{{ error }}</span>
</transition>
</div>
<button type="submit" class="primary" :disabled="saving">
{{ saving ? '保存中...' : '保存设置' }}
</button>
</div>
</div>
</section>
<section v-else-if="activeTab === 'behavior'" key="behavior" class="personal-page behavior-page">
<div class="behavior-section">
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">默认思考模型</span>
@ -296,6 +278,61 @@
</button>
</div>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">思考频率</span>
<p class="field-desc">控制连续快速模式运行多少轮后系统会强制切换到思考模型</p>
</div>
<div class="thinking-presets">
<button
v-for="preset in thinkingPresets"
:key="preset.id"
type="button"
:class="{ active: isPresetActive(preset.value) }"
@click.prevent="applyThinkingPreset(preset.value)"
>
{{ preset.label }}
<small>{{ preset.value }} </small>
</button>
</div>
<div class="thinking-input-row">
<label>
<span>自定义轮数</span>
<input
type="number"
:min="thinkingIntervalRange.min"
:max="thinkingIntervalRange.max"
:placeholder="`默认 ${thinkingIntervalDefault} 轮`"
:value="form.thinking_interval ?? ''"
@input="handleThinkingInput"
@focus="personalization.clearFeedback()"
/>
</label>
<span class="thinking-hint">范围 {{ thinkingIntervalRange.min }}~{{ thinkingIntervalRange.max }} </span>
<button type="button" class="link-button" @click.prevent="restoreThinkingInterval">
恢复默认
</button>
</div>
</div>
</div>
<div class="personal-actions-row">
<div class="personal-form-actions card-aligned">
<div class="personal-status-group">
<transition name="personal-status-fade">
<span class="status success" v-if="status">{{ status }}</span>
</transition>
<transition name="personal-status-fade">
<span class="status error" v-if="error">{{ error }}</span>
</transition>
</div>
<button type="submit" class="primary" :disabled="saving">
{{ saving ? '保存中...' : '保存设置' }}
</button>
</div>
</div>
</section>
<section v-else-if="activeTab === 'behavior'" key="behavior" class="personal-page behavior-page">
<div class="behavior-section">
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">堆叠块显示</span>
@ -319,6 +356,29 @@
<span>在对话区使用堆叠动画可随时切换回传统列表</span>
</label>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">静默禁用</span>
<p class="field-desc">禁用工具时不再注入提示消息模型不会感知被禁用项</p>
</div>
<label class="toggle-row">
<input
type="checkbox"
:checked="form.silent_tool_disable"
@change="personalization.updateField({ key: 'silent_tool_disable', value: $event.target.checked })"
/>
<span class="fancy-check" aria-hidden="true">
<svg viewBox="0 0 64 64">
<path
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
pathLength="575.0541381835938"
class="fancy-path"
></path>
</svg>
</span>
<span>静默禁用工具不向模型提示</span>
</label>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">自动生成对话标题</span>
@ -365,42 +425,6 @@
<span>在工具块显示我要做什么的简短提示</span>
</label>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">思考频率</span>
<p class="field-desc">控制连续快速模式运行多少轮后系统会强制切换到思考模型</p>
</div>
<div class="thinking-presets">
<button
v-for="preset in thinkingPresets"
:key="preset.id"
type="button"
:class="{ active: isPresetActive(preset.value) }"
@click.prevent="applyThinkingPreset(preset.value)"
>
{{ preset.label }}
<small>{{ preset.value }} </small>
</button>
</div>
<div class="thinking-input-row">
<label>
<span>自定义轮数</span>
<input
type="number"
:min="thinkingIntervalRange.min"
:max="thinkingIntervalRange.max"
:placeholder="`默认 ${thinkingIntervalDefault} 轮`"
:value="form.thinking_interval ?? ''"
@input="handleThinkingInput"
@focus="personalization.clearFeedback()"
/>
</label>
<span class="thinking-hint">范围 {{ thinkingIntervalRange.min }}~{{ thinkingIntervalRange.max }} </span>
<button type="button" class="link-button" @click.prevent="restoreThinkingInterval">
恢复默认
</button>
</div>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">默认禁用工具类别</span>
@ -617,9 +641,9 @@ const {
} = storeToRefs(personalization);
const baseTabs = [
{ id: 'preferences', label: '个性化设置' },
{ id: 'preferences', label: '个性化设置', description: '称呼、语气与注意事项' },
{ id: 'model', label: '模型偏好', description: '默认模型选择' },
{ id: 'behavior', label: '模型行为' },
{ id: 'behavior', label: '模型行为', description: '工具提示与界面表现' },
{ id: 'image', label: '图片压缩', description: '发送图片的尺寸策略' },
{ id: 'theme', label: '主题切换', description: '浅色 / 深色 / Claude' },
{ id: 'experiments', label: '实验功能', description: 'Liquid Glass' }
@ -935,6 +959,12 @@ const applyThemeOption = (theme: ThemeKey) => {
color: var(--claude-text-secondary);
}
.section-note {
margin: 6px 0 14px;
font-size: 12px;
color: var(--claude-text-secondary);
}
.admin-monitor-actions {
display: flex;
align-items: center;

View File

@ -6,6 +6,7 @@ interface PersonalForm {
enabled: boolean;
auto_generate_title: boolean;
tool_intent_enabled: boolean;
silent_tool_disable: boolean;
self_identify: string;
user_name: string;
profession: string;
@ -58,6 +59,7 @@ const defaultForm = (): PersonalForm => ({
enabled: false,
auto_generate_title: true,
tool_intent_enabled: true,
silent_tool_disable: false,
self_identify: '',
user_name: '',
profession: '',
@ -186,6 +188,7 @@ export const usePersonalizationStore = defineStore('personalization', {
enabled: !!data.enabled,
auto_generate_title: data.auto_generate_title !== false,
tool_intent_enabled: !!data.tool_intent_enabled,
silent_tool_disable: !!data.silent_tool_disable,
self_identify: data.self_identify || '',
user_name: data.user_name || '',
profession: data.profession || '',

View File

@ -265,9 +265,16 @@
background: var(--theme-surface-soft);
align-self: flex-start;
height: auto;
max-height: none;
overflow: visible;
flex-wrap: wrap;
max-height: 72vh;
overflow-y: auto;
overflow-x: hidden;
flex-wrap: nowrap;
-ms-overflow-style: none; /* IE/Edge */
scrollbar-width: none; /* Firefox */
}
.personal-page-tabs::-webkit-scrollbar {
width: 0;
height: 0;
}
.personal-tab-button {

View File

@ -98,6 +98,7 @@ class MainTerminal:
data_dir=str(self.data_dir)
)
self._announced_sub_agent_tasks = set()
self.silent_tool_disable = False # 是否静默工具禁用提示
# 聚焦文件管理
self.focused_files = {} # {path: content} 存储聚焦的文件内容
@ -439,6 +440,8 @@ class MainTerminal:
def _format_disabled_tool_notice(self) -> Optional[str]:
"""生成禁用工具提示信息 / Format disabled tool notice."""
if getattr(self, "silent_tool_disable", False):
return None
if not self.disabled_notice_tools:
return None
@ -2126,6 +2129,10 @@ class MainTerminal:
{"role": "system", "content": system_prompt}
]
workspace_system = self.context_manager._build_workspace_system_message(context)
if workspace_system:
messages.append({"role": "system", "content": workspace_system})
if self.tool_category_states.get("todo", True):
todo_prompt = self.load_prompt("todo_guidelines").strip()
if todo_prompt:

View File

@ -16,6 +16,8 @@ try:
TERMINAL_SANDBOX_CPUS,
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
TERMINAL_SANDBOX_MODE,
LINUX_SAFETY,
)
except ImportError:
import sys
@ -31,6 +33,8 @@ except ImportError:
TERMINAL_SANDBOX_CPUS,
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
TERMINAL_SANDBOX_MODE,
LINUX_SAFETY,
)
from utils.conversation_manager import ConversationManager
@ -68,6 +72,10 @@ class ContextManager:
self.load_annotations()
def _is_host_mode_without_safety(self) -> bool:
"""是否处于宿主机模式且未启用安全保护。"""
return (TERMINAL_SANDBOX_MODE or "").lower() == "host" and not LINUX_SAFETY
def set_web_terminal_callback(self, callback):
"""设置Web终端回调函数用于广播事件"""
self._web_terminal_callback = callback
@ -954,6 +962,9 @@ class ContextManager:
lines = []
project_name = Path(structure['path']).name
if self._is_host_mode_without_safety():
root_label = f"{structure['path']} (项目根)"
else:
container_root = (self.container_mount_path or "").strip() or "/workspace"
container_root = container_root.rstrip("/") or "/"
if container_root == "/":
@ -1116,6 +1127,45 @@ class ContextManager:
"is_overflow": sizes["total"] > MAX_CONTEXT_SIZE,
"usage_percent": (sizes["total"] / MAX_CONTEXT_SIZE) * 100
}
def _build_workspace_system_message(self, context: Dict) -> Optional[str]:
"""构建独立的工作区系统消息,根据运行模式动态展示环境与资源信息。"""
template = self.load_prompt("workspace_system")
if not template:
template = (
"## 工作区信息\n"
"- **运行环境**{runtime_environment}\n"
"- **资源限制**{resource_limit}\n"
"- **当前时间**{current_time}\n"
"- **项目结构**\n\n{file_tree}\n\n"
"- **长期记忆**{memory}"
)
is_host = self._is_host_mode_without_safety()
runtime_environment = (
"宿主机模式"
if is_host
else f"隔离容器中(挂载目录 {self.container_mount_path or '/workspace'}),宿主机路径已隐藏"
)
resource_limit = (
"宿主机模式无限制"
if is_host
else f"CPU {self.container_cpu_limit} 核,内存 {self.container_memory_limit},磁盘配额 {self.project_storage_limit}"
)
content = template.format(
runtime_environment=runtime_environment,
resource_limit=resource_limit,
container_path=self.container_mount_path or "/workspace",
container_cpus=self.container_cpu_limit,
container_memory=self.container_memory_limit,
project_storage=self.project_storage_limit,
current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
file_tree=context["project_info"]["file_tree"],
memory=context["memory"],
)
return content
def build_messages(self, context: Dict, user_input: str) -> List[Dict]:
"""构建消息列表(添加终端内容注入)"""
# 加载系统提示
@ -1141,6 +1191,10 @@ class ContextManager:
{"role": "system", "content": system_prompt}
]
workspace_system = self._build_workspace_system_message(context)
if workspace_system:
messages.append({"role": "system", "content": workspace_system})
# 添加对话历史
for conv in context["conversation"]:
if conv["role"] == "assistant":

View File

@ -18,6 +18,8 @@ try:
TERMINAL_SANDBOX_CPUS,
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
TERMINAL_SANDBOX_MODE,
LINUX_SAFETY,
)
from config.model_profiles import get_model_prompt_replacements
except ImportError:
@ -34,6 +36,8 @@ except ImportError:
TERMINAL_SANDBOX_CPUS,
TERMINAL_SANDBOX_MEMORY,
PROJECT_MAX_STORAGE_MB,
TERMINAL_SANDBOX_MODE,
LINUX_SAFETY,
)
from config.model_profiles import get_model_prompt_replacements
from utils.conversation_manager import ConversationManager
@ -70,6 +74,10 @@ class ContextManager:
self.load_annotations()
def _is_host_mode_without_safety(self) -> bool:
"""是否处于宿主机模式且未启用安全保护。"""
return (TERMINAL_SANDBOX_MODE or "").lower() == "host" and not LINUX_SAFETY
# ===========================================
# Token 累计文件工具
# ===========================================
@ -1109,6 +1117,9 @@ class ContextManager:
lines = []
project_name = Path(structure['path']).name
if self._is_host_mode_without_safety():
root_label = f"{structure['path']} (项目根)"
else:
container_root = (self.container_mount_path or "").strip() or "/workspace"
container_root = container_root.rstrip("/") or "/"
if container_root == "/":
@ -1371,6 +1382,45 @@ class ContextManager:
continue
return parts if parts else text
def _build_workspace_system_message(self, context: Dict) -> Optional[str]:
"""构建独立的工作区系统消息,根据运行模式动态展示环境与资源信息。"""
template = self.load_prompt("workspace_system")
if not template:
template = (
"## 工作区信息\n"
"- **运行环境**{runtime_environment}\n"
"- **资源限制**{resource_limit}\n"
"- **当前时间**{current_time}\n"
"- **项目结构**\n\n{file_tree}\n\n"
"- **长期记忆**{memory}"
)
is_host = self._is_host_mode_without_safety()
runtime_environment = (
"宿主机模式"
if is_host
else f"隔离容器中(挂载目录 {self.container_mount_path or '/workspace'}),宿主机路径已隐藏"
)
resource_limit = (
"宿主机模式无限制"
if is_host
else f"CPU {self.container_cpu_limit} 核,内存 {self.container_memory_limit},磁盘配额 {self.project_storage_limit}"
)
content = template.format(
runtime_environment=runtime_environment,
resource_limit=resource_limit,
container_path=self.container_mount_path or "/workspace",
container_cpus=self.container_cpu_limit,
container_memory=self.container_memory_limit,
project_storage=self.project_storage_limit,
current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
file_tree=context["project_info"]["file_tree"],
memory=context["memory"],
)
return content
def build_messages(self, context: Dict, user_input: str) -> List[Dict]:
"""构建消息列表(添加终端内容注入)"""
# 加载系统提示Qwen-VL 使用专用提示)
@ -1400,6 +1450,10 @@ class ContextManager:
{"role": "system", "content": system_prompt}
]
workspace_system = self._build_workspace_system_message(context)
if workspace_system:
messages.append({"role": "system", "content": workspace_system})
# 添加对话历史
for conv in context["conversation"]:
if conv["role"] == "assistant":