feat: add hidden easter egg flood effect

This commit is contained in:
JOJO 2025-11-22 02:25:42 +08:00
parent 6e8321cf7e
commit afd1bb9d28
8 changed files with 502 additions and 15 deletions

View File

@ -41,6 +41,7 @@ from modules.todo_manager import TodoManager
from modules.sub_agent_manager import SubAgentManager from modules.sub_agent_manager import SubAgentManager
from modules.webpage_extractor import extract_webpage_content, tavily_extract from modules.webpage_extractor import extract_webpage_content, tavily_extract
from modules.ocr_client import OCRClient from modules.ocr_client import OCRClient
from modules.easter_egg_manager import EasterEggManager
from core.tool_config import TOOL_CATEGORIES from core.tool_config import TOOL_CATEGORIES
from utils.api_client import DeepSeekClient from utils.api_client import DeepSeekClient
from utils.context_manager import ContextManager from utils.context_manager import ContextManager
@ -84,6 +85,7 @@ class MainTerminal:
project_path=self.project_path, project_path=self.project_path,
data_dir=str(self.data_dir) data_dir=str(self.data_dir)
) )
self.easter_egg_manager = EasterEggManager()
self._announced_sub_agent_tasks = set() self._announced_sub_agent_tasks = set()
# 聚焦文件管理 # 聚焦文件管理
@ -95,8 +97,12 @@ class MainTerminal:
self.pending_modify_request = None # {"path": str} self.pending_modify_request = None # {"path": str}
# 工具启用状态 # 工具启用状态
self.tool_category_states = {key: True for key in TOOL_CATEGORIES} self.tool_category_states = {
key: category.default_enabled
for key, category in TOOL_CATEGORIES.items()
}
self.disabled_tools = set() self.disabled_tools = set()
self.disabled_notice_tools = set()
self._refresh_disabled_tools() self._refresh_disabled_tools()
# 新增:自动开始新对话 # 新增:自动开始新对话
@ -402,7 +408,7 @@ class MainTerminal:
snapshot.append({ snapshot.append({
"id": key, "id": key,
"label": category.label, "label": category.label,
"enabled": self.tool_category_states.get(key, True), "enabled": self.tool_category_states.get(key, category.default_enabled),
"tools": list(category.tools), "tools": list(category.tools),
}) })
return snapshot return snapshot
@ -410,18 +416,23 @@ class MainTerminal:
def _refresh_disabled_tools(self) -> None: def _refresh_disabled_tools(self) -> None:
"""刷新禁用工具列表 / Refresh disabled tool set.""" """刷新禁用工具列表 / Refresh disabled tool set."""
disabled = set() disabled = set()
notice = set()
for key, enabled in self.tool_category_states.items(): for key, enabled in self.tool_category_states.items():
if not enabled: if not enabled:
disabled.update(TOOL_CATEGORIES[key].tools) category = TOOL_CATEGORIES[key]
disabled.update(category.tools)
if not getattr(category, "silent_when_disabled", False):
notice.update(category.tools)
self.disabled_tools = disabled self.disabled_tools = disabled
self.disabled_notice_tools = notice
def _format_disabled_tool_notice(self) -> Optional[str]: def _format_disabled_tool_notice(self) -> Optional[str]:
"""生成禁用工具提示信息 / Format disabled tool notice.""" """生成禁用工具提示信息 / Format disabled tool notice."""
if not self.disabled_tools: if not self.disabled_notice_tools:
return None return None
lines = ["=== 工具可用性提醒 ==="] lines = ["=== 工具可用性提醒 ==="]
for tool_name in sorted(self.disabled_tools): for tool_name in sorted(self.disabled_notice_tools):
lines.append(f"{tool_name}:已被用户禁用") lines.append(f"{tool_name}:已被用户禁用")
lines.append("=== 提示结束 ===") lines.append("=== 提示结束 ===")
return "\n".join(lines) return "\n".join(lines)
@ -1456,6 +1467,23 @@ class MainTerminal:
"required": [] "required": []
} }
} }
},
{
"type": "function",
"function": {
"name": "trigger_easter_egg",
"description": "触发隐藏彩蛋,用于展示非功能性特效。需指定 effect 参数(例如 flood 表示灌水)。",
"parameters": {
"type": "object",
"properties": {
"effect": {
"type": "string",
"description": "彩蛋标识,当前支持 flood灌水"
}
},
"required": ["effect"]
}
}
} }
] ]
if self.disabled_tools: if self.disabled_tools:
@ -2050,6 +2078,9 @@ class MainTerminal:
agent_id=arguments.get("agent_id") agent_id=arguments.get("agent_id")
) )
elif tool_name == "trigger_easter_egg":
result = self.easter_egg_manager.trigger_effect(arguments.get("effect"))
else: else:
result = {"success": False, "error": f"未知工具: {tool_name}"} result = {"success": False, "error": f"未知工具: {tool_name}"}

View File

@ -9,9 +9,17 @@ from typing import Dict, List
class ToolCategory: class ToolCategory:
"""工具类别的结构化定义。""" """工具类别的结构化定义。"""
def __init__(self, label: str, tools: List[str]): def __init__(
self,
label: str,
tools: List[str],
default_enabled: bool = True,
silent_when_disabled: bool = False,
):
self.label = label self.label = label
self.tools = tools self.tools = tools
self.default_enabled = default_enabled
self.silent_when_disabled = silent_when_disabled
TOOL_CATEGORIES: Dict[str, ToolCategory] = { TOOL_CATEGORIES: Dict[str, ToolCategory] = {
@ -60,4 +68,10 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
label="子智能体", label="子智能体",
tools=["create_sub_agent", "wait_sub_agent", "close_sub_agent"], tools=["create_sub_agent", "wait_sub_agent", "close_sub_agent"],
), ),
"easter_egg": ToolCategory(
label="彩蛋实验",
tools=["trigger_easter_egg"],
default_enabled=False,
silent_when_disabled=True,
),
} }

View File

@ -0,0 +1,68 @@
"""彩蛋触发管理器。
负责根据工具参数返回彩蛋元数据供前端渲染对应特效
"""
from __future__ import annotations
from typing import Dict, List
class EasterEggManager:
"""管理隐藏彩蛋效果的触发逻辑。"""
def __init__(self) -> None:
# 目前仅有一个“灌水”特效,后续可在此扩展
self.effects: Dict[str, Dict[str, object]] = {
"flood": {
"label": "灌水",
"aliases": ["flood", "water", "wave", "灌水", "注水"],
"message": "淡蓝色水面从底部缓缓上涨,并带有柔和波纹。",
"duration_seconds": 45,
"intensity_range": (0.87, 0.93),
"notes": "特效为半透明覆盖层,不会阻挡交互。",
}
}
def trigger_effect(self, effect: str) -> Dict[str, object]:
"""
根据传入的 effect 名称查找彩蛋
Args:
effect: 彩蛋标识或别名
Returns:
dict: 包含触发状态与前端所需的特效参数
"""
effect_key = (effect or "").strip().lower()
if not effect_key:
return self._build_error("缺少 effect 参数")
for effect_id, metadata in self.effects.items():
aliases = metadata.get("aliases", [])
if effect_key == effect_id or effect_key in aliases:
payload = {
"success": True,
"effect": effect_id,
"display_name": metadata.get("label", effect_id),
"message": metadata.get("message"),
"duration_seconds": metadata.get("duration_seconds", 30),
"intensity_range": metadata.get("intensity_range"),
"notes": metadata.get("notes"),
}
return payload
return self._build_error(f"未知彩蛋: {effect_key}")
def _build_error(self, message: str) -> Dict[str, object]:
"""返回格式化的错误信息。"""
return {
"success": False,
"error": message,
"available_effects": self.available_effects,
}
@property
def available_effects(self) -> List[str]:
"""返回可用彩蛋 ID 列表。"""
return list(self.effects.keys())

View File

@ -103,7 +103,8 @@ const TOOL_ICON_MAP = Object.freeze({
unfocus_file: 'eye', unfocus_file: 'eye',
update_memory: 'brain', update_memory: 'brain',
wait_sub_agent: 'clock', wait_sub_agent: 'clock',
web_search: 'search' web_search: 'search',
trigger_easter_egg: 'sparkles'
}); });
const TOOL_CATEGORY_ICON_MAP = Object.freeze({ const TOOL_CATEGORY_ICON_MAP = Object.freeze({
@ -114,9 +115,25 @@ const TOOL_CATEGORY_ICON_MAP = Object.freeze({
terminal_command: 'terminal', terminal_command: 'terminal',
memory: 'brain', memory: 'brain',
todo: 'stickyNote', todo: 'stickyNote',
sub_agent: 'bot' sub_agent: 'bot',
easter_egg: 'sparkles'
}); });
function randRange(min, max) {
return Math.random() * (max - min) + min;
}
function randInt(min, max) {
return Math.floor(randRange(min, max + 1));
}
function pickOne(arr) {
if (!Array.isArray(arr) || arr.length === 0) {
return null;
}
return arr[Math.floor(Math.random() * arr.length)];
}
function injectScriptSequentially(urls, onSuccess, onFailure) { function injectScriptSequentially(urls, onSuccess, onFailure) {
let index = 0; let index = 0;
const tryLoad = () => { const tryLoad = () => {
@ -303,6 +320,13 @@ async function bootstrapApp() {
todoList: null, todoList: null,
icons: ICONS, icons: ICONS,
toolCategoryIcons: TOOL_CATEGORY_ICON_MAP, toolCategoryIcons: TOOL_CATEGORY_ICON_MAP,
easterEgg: {
active: false,
effect: null,
cleanupTimer: null,
styleNode: null,
retreating: false
},
// 右键菜单相关 // 右键菜单相关
contextMenu: { contextMenu: {
@ -390,6 +414,8 @@ async function bootstrapApp() {
clearInterval(this.subAgentPollTimer); clearInterval(this.subAgentPollTimer);
this.subAgentPollTimer = null; this.subAgentPollTimer = null;
} }
this.destroyEasterEggEffect();
this.finishEasterEggCleanup();
}, },
watch: { watch: {
@ -1075,6 +1101,9 @@ async function bootstrapApp() {
if (data.result !== undefined) { if (data.result !== undefined) {
targetAction.tool.result = data.result; targetAction.tool.result = data.result;
} }
if (targetAction.tool && targetAction.tool.name === 'trigger_easter_egg' && data.result !== undefined) {
this.handleEasterEggPayload(data.result);
}
if (data.message !== undefined) { if (data.message !== undefined) {
targetAction.tool.message = data.message; targetAction.tool.message = data.message;
} }
@ -2516,7 +2545,7 @@ async function bootstrapApp() {
if (!this.quickMenuOpen) { if (!this.quickMenuOpen) {
this.quickMenuOpen = true; this.quickMenuOpen = true;
} }
this.loadToolSettings(); this.loadToolSettings(true);
} }
}, },
@ -3233,6 +3262,234 @@ async function bootstrapApp() {
document.body.style.cursor = ''; document.body.style.cursor = '';
}, },
handleEasterEggPayload(payload) {
if (!payload) {
return;
}
let parsed = payload;
if (typeof payload === 'string') {
try {
parsed = JSON.parse(payload);
} catch (error) {
console.warn('无法解析彩蛋结果:', payload);
return;
}
}
if (!parsed || typeof parsed !== 'object') {
return;
}
if (!parsed.success) {
if (parsed.error) {
console.warn('彩蛋触发失败:', parsed.error);
}
this.destroyEasterEggEffect();
return;
}
const effectName = (parsed.effect || '').toLowerCase();
if (effectName === 'flood') {
this.startFloodEffect(parsed);
}
},
startFloodEffect(payload = {}) {
this.clearFloodAnimations();
this.teardownEasterEggStyle();
this.easterEgg.active = true;
this.easterEgg.effect = 'flood';
this.easterEgg.retreating = false;
if (this.easterEgg.cleanupTimer) {
clearTimeout(this.easterEgg.cleanupTimer);
this.easterEgg.cleanupTimer = null;
}
this.$nextTick(() => {
this.runFloodAnimation(payload);
});
const durationSeconds = Math.max(8, Number(payload.duration_seconds) || 45);
this.easterEgg.cleanupTimer = setTimeout(() => {
this.destroyEasterEggEffect();
}, durationSeconds * 1000);
},
runFloodAnimation(payload = {}) {
const container = this.$refs.easterEggWaterContainer;
if (!container) {
return;
}
const waves = container.querySelectorAll('.wave');
if (!waves.length) {
return;
}
const styleEl = document.createElement('style');
styleEl.setAttribute('data-easter-egg', 'flood');
document.head.appendChild(styleEl);
this.easterEgg.styleNode = styleEl;
const sheet = styleEl.sheet;
if (!sheet) {
return;
}
container.style.animation = 'none';
container.style.height = '0%';
void container.offsetHeight;
const range = Array.isArray(payload.intensity_range) && payload.intensity_range.length === 2
? payload.intensity_range
: [0.85, 0.92];
const minHeight = Math.min(range[0], range[1]);
const maxHeight = Math.max(range[0], range[1]);
const targetHeight = randRange(minHeight * 100, maxHeight * 100);
const riseDuration = randRange(30, 40);
const easing = pickOne([
'ease-in-out',
'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
'cubic-bezier(0.42, 0, 0.58, 1)'
]) || 'ease-in-out';
const riseName = `easter_egg_rise_${Date.now()}`;
sheet.insertRule(`@keyframes ${riseName} { 0% { height: 0%; } 100% { height: ${targetHeight}%; } }`, sheet.cssRules.length);
container.style.animation = `${riseName} ${riseDuration}s ${easing} forwards`;
const directionSets = [
[1, 1, -1],
[1, -1, -1],
[-1, 1, 1],
[-1, -1, 1]
];
const directions = pickOne(directionSets) || [1, -1, 1];
const colors = [
'rgba(135, 206, 250, 0.35)',
'rgba(100, 181, 246, 0.45)',
'rgba(33, 150, 243, 0.4)'
];
waves.forEach((wave, index) => {
const svgData = this.buildFloodWaveShape(index);
const color = colors[index % colors.length];
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svgData.width} 1000" preserveAspectRatio="none"><path d="${svgData.path}" fill="${color}"/></svg>`;
const encoded = encodeURIComponent(svg);
wave.style.backgroundImage = `url("data:image/svg+xml,${encoded}")`;
wave.style.backgroundSize = `${svgData.waveWidth}px 100%`;
const animationName = `easter_egg_wave_${index}_${Date.now()}`;
const startPosition = randInt(-200, 200);
const distance = svgData.waveWidth * (directions[index] || 1);
const duration = index === 0 ? randRange(16, 22) : index === 1 ? randRange(11, 16) : randRange(7, 12);
const delay = randRange(0, 1.5);
sheet.insertRule(`@keyframes ${animationName} { 0% { background-position-x: ${startPosition}px; } 100% { background-position-x: ${startPosition + distance}px; } }`, sheet.cssRules.length);
wave.style.animation = `${animationName} ${duration}s linear infinite`;
wave.style.animationDelay = `${delay}s`;
wave.style.backgroundPositionX = `${startPosition}px`;
});
},
buildFloodWaveShape(layerIndex) {
const baseHeight = 180 + layerIndex * 12;
const cycles = 4;
let path = `M0,${baseHeight}`;
let currentX = 0;
let previousAmplitude = randRange(40, 80);
for (let i = 0; i < cycles; i++) {
const waveLength = randRange(700, 900);
const minAmp = Math.max(20, previousAmplitude - 20);
const maxAmp = Math.min(90, previousAmplitude + 20);
const amplitude = randRange(minAmp, maxAmp);
previousAmplitude = amplitude;
const halfWave = waveLength / 2;
const peakX = currentX + halfWave / 2;
path += ` Q${peakX},${baseHeight - amplitude} ${currentX + halfWave},${baseHeight}`;
const troughX = currentX + halfWave + halfWave / 2;
path += ` Q${troughX},${baseHeight + amplitude} ${currentX + waveLength},${baseHeight}`;
currentX += waveLength;
}
path += ` L${currentX},1000 L0,1000 Z`;
return {
path,
width: currentX,
waveWidth: currentX / cycles
};
},
clearFloodAnimations() {
const container = this.$refs.easterEggWaterContainer;
if (!container) {
return;
}
container.style.animation = 'none';
container.style.transition = 'none';
container.style.height = '0%';
const waves = container.querySelectorAll('.wave');
waves.forEach((wave) => {
wave.style.animation = 'none';
wave.style.backgroundImage = '';
wave.style.backgroundSize = '';
wave.style.backgroundPositionX = '0px';
});
},
teardownEasterEggStyle() {
if (this.easterEgg.styleNode && this.easterEgg.styleNode.parentNode) {
this.easterEgg.styleNode.parentNode.removeChild(this.easterEgg.styleNode);
}
this.easterEgg.styleNode = null;
},
destroyEasterEggEffect() {
if (this.easterEgg.cleanupTimer) {
clearTimeout(this.easterEgg.cleanupTimer);
this.easterEgg.cleanupTimer = null;
}
if (this.easterEgg.effect === 'flood' && this.easterEgg.active && !this.easterEgg.retreating) {
this.startFloodRetreat();
return;
}
if (this.easterEgg.effect === 'flood' && this.easterEgg.retreating) {
return;
}
this.finishEasterEggCleanup();
},
startFloodRetreat() {
const container = this.$refs.easterEggWaterContainer;
if (!container) {
this.finishEasterEggCleanup();
return;
}
this.easterEgg.retreating = true;
const measuredHeight = container.offsetHeight || container.clientHeight;
const computedHeight = window.getComputedStyle(container).height;
const currentHeight = measuredHeight
? `${measuredHeight}px`
: (computedHeight && computedHeight !== 'auto' ? computedHeight : '0px');
container.style.animation = 'none';
container.style.transition = 'none';
container.style.height = currentHeight;
void container.offsetHeight;
const retreatDuration = 8;
container.style.transition = `height ${retreatDuration}s ease-in-out`;
requestAnimationFrame(() => {
container.style.height = '0px';
});
this.easterEgg.cleanupTimer = setTimeout(() => {
container.style.transition = 'none';
this.clearFloodAnimations();
this.teardownEasterEggStyle();
this.easterEgg.active = false;
this.easterEgg.effect = null;
this.easterEgg.retreating = false;
}, retreatDuration * 1000);
},
finishEasterEggCleanup() {
this.clearFloodAnimations();
this.teardownEasterEggStyle();
this.easterEgg.active = false;
this.easterEgg.effect = null;
this.easterEgg.retreating = false;
},
// 格式化token显示修复NaN问题 // 格式化token显示修复NaN问题
formatTokenCount(tokens) { formatTokenCount(tokens) {
// 确保tokens是数字防止NaN // 确保tokens是数字防止NaN

View File

@ -823,6 +823,18 @@
</div> </div>
</transition> </transition>
</template> </template>
<div class="easter-egg-overlay"
v-show="easterEgg.active"
:class="{ active: easterEgg.active }"
aria-hidden="true">
<div class="easter-egg-water" ref="easterEggWater">
<div class="easter-egg-water-container" ref="easterEggWaterContainer">
<div class="wave wave1"></div>
<div class="wave wave2"></div>
<div class="wave wave3"></div>
</div>
</div>
</div>
<div class="context-menu" <div class="context-menu"
v-if="contextMenu.visible" v-if="contextMenu.visible"
:style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }" :style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }"

View File

@ -2612,3 +2612,59 @@ o-files {
.personal-page-fade-leave-to { .personal-page-fade-leave-to {
opacity: 0; opacity: 0;
} }
/* 彩蛋灌水特效 */
.easter-egg-overlay {
position: fixed;
inset: 0;
pointer-events: none;
display: flex;
align-items: flex-end;
justify-content: center;
opacity: 0;
transition: opacity 0.6s ease;
z-index: 80;
}
.easter-egg-overlay.active {
opacity: 1;
}
.easter-egg-water {
position: relative;
width: 100%;
height: 100%;
}
.easter-egg-water-container {
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 0%;
overflow: visible;
pointer-events: none;
}
.easter-egg-water-container .wave {
position: absolute;
bottom: 0;
left: 0;
width: 200%;
height: 100%;
background-repeat: repeat-x;
background-position: bottom;
opacity: 0.9;
}
.easter-egg-water-container .wave.wave1 {
filter: blur(0.5px);
}
.easter-egg-water-container .wave.wave2 {
filter: blur(1px);
}
.easter-egg-water-container .wave.wave3 {
filter: blur(1.5px);
}

View File

@ -41,6 +41,7 @@ from modules.todo_manager import TodoManager
from modules.sub_agent_manager import SubAgentManager from modules.sub_agent_manager import SubAgentManager
from modules.webpage_extractor import extract_webpage_content, tavily_extract from modules.webpage_extractor import extract_webpage_content, tavily_extract
from modules.ocr_client import OCRClient from modules.ocr_client import OCRClient
from modules.easter_egg_manager import EasterEggManager
from core.tool_config import TOOL_CATEGORIES from core.tool_config import TOOL_CATEGORIES
from utils.api_client import DeepSeekClient from utils.api_client import DeepSeekClient
from utils.context_manager import ContextManager from utils.context_manager import ContextManager
@ -85,6 +86,7 @@ class MainTerminal:
project_path=self.project_path, project_path=self.project_path,
data_dir=str(self.data_dir) data_dir=str(self.data_dir)
) )
self.easter_egg_manager = EasterEggManager()
self._announced_sub_agent_tasks = set() self._announced_sub_agent_tasks = set()
# 聚焦文件管理 # 聚焦文件管理
@ -96,8 +98,12 @@ class MainTerminal:
self.pending_modify_request = None # {"path": str} self.pending_modify_request = None # {"path": str}
# 工具启用状态 # 工具启用状态
self.tool_category_states = {key: True for key in TOOL_CATEGORIES} self.tool_category_states = {
key: category.default_enabled
for key, category in TOOL_CATEGORIES.items()
}
self.disabled_tools = set() self.disabled_tools = set()
self.disabled_notice_tools = set()
self._refresh_disabled_tools() self._refresh_disabled_tools()
# 新增:自动开始新对话 # 新增:自动开始新对话
@ -403,7 +409,7 @@ class MainTerminal:
snapshot.append({ snapshot.append({
"id": key, "id": key,
"label": category.label, "label": category.label,
"enabled": self.tool_category_states.get(key, True), "enabled": self.tool_category_states.get(key, category.default_enabled),
"tools": list(category.tools), "tools": list(category.tools),
}) })
return snapshot return snapshot
@ -411,18 +417,23 @@ class MainTerminal:
def _refresh_disabled_tools(self) -> None: def _refresh_disabled_tools(self) -> None:
"""刷新禁用工具列表 / Refresh disabled tool set.""" """刷新禁用工具列表 / Refresh disabled tool set."""
disabled = set() disabled = set()
notice = set()
for key, enabled in self.tool_category_states.items(): for key, enabled in self.tool_category_states.items():
if not enabled: if not enabled:
disabled.update(TOOL_CATEGORIES[key].tools) category = TOOL_CATEGORIES[key]
disabled.update(category.tools)
if not getattr(category, "silent_when_disabled", False):
notice.update(category.tools)
self.disabled_tools = disabled self.disabled_tools = disabled
self.disabled_notice_tools = notice
def _format_disabled_tool_notice(self) -> Optional[str]: def _format_disabled_tool_notice(self) -> Optional[str]:
"""生成禁用工具提示信息 / Format disabled tool notice.""" """生成禁用工具提示信息 / Format disabled tool notice."""
if not self.disabled_tools: if not self.disabled_notice_tools:
return None return None
lines = ["=== 工具可用性提醒 ==="] lines = ["=== 工具可用性提醒 ==="]
for tool_name in sorted(self.disabled_tools): for tool_name in sorted(self.disabled_notice_tools):
lines.append(f"{tool_name}:已被用户禁用") lines.append(f"{tool_name}:已被用户禁用")
lines.append("=== 提示结束 ===") lines.append("=== 提示结束 ===")
return "\n".join(lines) return "\n".join(lines)
@ -1428,6 +1439,23 @@ class MainTerminal:
"required": [] "required": []
} }
} }
},
{
"type": "function",
"function": {
"name": "trigger_easter_egg",
"description": "触发隐藏彩蛋,用于展示非功能性特效。需指定 effect 参数(例如 flood 表示灌水)。",
"parameters": {
"type": "object",
"properties": {
"effect": {
"type": "string",
"description": "彩蛋标识,当前支持 flood灌水"
}
},
"required": ["effect"]
}
}
} }
] ]
if self.disabled_tools: if self.disabled_tools:
@ -2007,6 +2035,9 @@ class MainTerminal:
timeout_seconds=arguments.get("timeout_seconds") timeout_seconds=arguments.get("timeout_seconds")
) )
elif tool_name == "trigger_easter_egg":
result = self.easter_egg_manager.trigger_effect(arguments.get("effect"))
else: else:
result = {"success": False, "error": f"未知工具: {tool_name}"} result = {"success": False, "error": f"未知工具: {tool_name}"}

View File

@ -9,9 +9,17 @@ from typing import Dict, List
class ToolCategory: class ToolCategory:
"""工具类别的结构化定义。""" """工具类别的结构化定义。"""
def __init__(self, label: str, tools: List[str]): def __init__(
self,
label: str,
tools: List[str],
default_enabled: bool = True,
silent_when_disabled: bool = False,
):
self.label = label self.label = label
self.tools = tools self.tools = tools
self.default_enabled = default_enabled
self.silent_when_disabled = silent_when_disabled
TOOL_CATEGORIES: Dict[str, ToolCategory] = { TOOL_CATEGORIES: Dict[str, ToolCategory] = {
@ -56,4 +64,14 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
label="待办事项", label="待办事项",
tools=["todo_create", "todo_update_task", "todo_finish", "todo_finish_confirm"], tools=["todo_create", "todo_update_task", "todo_finish", "todo_finish_confirm"],
), ),
"sub_agent": ToolCategory(
label="子智能体",
tools=["create_sub_agent", "wait_sub_agent", "close_sub_agent"],
),
"easter_egg": ToolCategory(
label="彩蛋实验",
tools=["trigger_easter_egg"],
default_enabled=False,
silent_when_disabled=True,
),
} }