Compare commits
1 Commits
main
...
feature/fr
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f4cf1078a |
@ -9,8 +9,16 @@ AGENT_THINKING_API_BASE_URL=
|
|||||||
AGENT_THINKING_API_KEY=
|
AGENT_THINKING_API_KEY=
|
||||||
AGENT_THINKING_MODEL_ID=
|
AGENT_THINKING_MODEL_ID=
|
||||||
|
|
||||||
|
# 对话标题生成模型(可选,留空则回退到基础推理模型)
|
||||||
|
AGENT_TITLE_API_BASE_URL=
|
||||||
|
AGENT_TITLE_API_KEY=
|
||||||
|
AGENT_TITLE_MODEL_ID=
|
||||||
|
|
||||||
# 第三方搜索(可选)
|
# 第三方搜索(可选)
|
||||||
AGENT_TAVILY_API_KEY=
|
AGENT_TAVILY_API_KEY=
|
||||||
|
AGENT_TAVILY_API_KEY_2=
|
||||||
|
AGENT_TAVILY_API_KEY_BACKUP=
|
||||||
|
# 使用哪个 Tavily 环境变量请在 config/search.py 里改 TAVILY_API_KEY_ENV_NAME
|
||||||
|
|
||||||
# 每轮最大响应 token(可选)
|
# 每轮最大响应 token(可选)
|
||||||
AGENT_DEFAULT_RESPONSE_MAX_TOKENS=32768
|
AGENT_DEFAULT_RESPONSE_MAX_TOKENS=32768
|
||||||
|
|||||||
@ -35,6 +35,7 @@ def _load_dotenv():
|
|||||||
_load_dotenv()
|
_load_dotenv()
|
||||||
|
|
||||||
from . import api as _api
|
from . import api as _api
|
||||||
|
from . import search as _search
|
||||||
from . import paths as _paths
|
from . import paths as _paths
|
||||||
from . import limits as _limits
|
from . import limits as _limits
|
||||||
from . import terminal as _terminal
|
from . import terminal as _terminal
|
||||||
@ -51,6 +52,7 @@ from . import custom_tools as _custom_tools
|
|||||||
from . import mcp as _mcp
|
from . import mcp as _mcp
|
||||||
|
|
||||||
from .api import *
|
from .api import *
|
||||||
|
from .search import *
|
||||||
from .paths import *
|
from .paths import *
|
||||||
from .limits import *
|
from .limits import *
|
||||||
from .terminal import *
|
from .terminal import *
|
||||||
@ -67,7 +69,7 @@ from .custom_tools import *
|
|||||||
from .mcp import *
|
from .mcp import *
|
||||||
|
|
||||||
__all__ = []
|
__all__ = []
|
||||||
for module in (_api, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp):
|
for module in (_api, _search, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp):
|
||||||
__all__ += getattr(module, "__all__", [])
|
__all__ += getattr(module, "__all__", [])
|
||||||
|
|
||||||
del _api, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp
|
del _api, _search, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp
|
||||||
|
|||||||
@ -19,8 +19,12 @@ THINKING_API_BASE_URL = _env("AGENT_THINKING_API_BASE_URL", API_BASE_URL)
|
|||||||
THINKING_API_KEY = _env("AGENT_THINKING_API_KEY", API_KEY)
|
THINKING_API_KEY = _env("AGENT_THINKING_API_KEY", API_KEY)
|
||||||
THINKING_MODEL_ID = _env("AGENT_THINKING_MODEL_ID", MODEL_ID)
|
THINKING_MODEL_ID = _env("AGENT_THINKING_MODEL_ID", MODEL_ID)
|
||||||
|
|
||||||
# Tavily 搜索(可选)
|
from .search import TAVILY_API_KEY
|
||||||
TAVILY_API_KEY = _env("AGENT_TAVILY_API_KEY", "")
|
|
||||||
|
# 对话标题生成模型(可选,留空则回退到基础快速模型)
|
||||||
|
TITLE_API_BASE_URL = _env("AGENT_TITLE_API_BASE_URL", API_BASE_URL)
|
||||||
|
TITLE_API_KEY = _env("AGENT_TITLE_API_KEY", API_KEY)
|
||||||
|
TITLE_MODEL_ID = _env("AGENT_TITLE_MODEL_ID", MODEL_ID)
|
||||||
|
|
||||||
# 默认响应 token 限制
|
# 默认响应 token 限制
|
||||||
DEFAULT_RESPONSE_MAX_TOKENS = int(os.environ.get("AGENT_DEFAULT_RESPONSE_MAX_TOKENS", "32768"))
|
DEFAULT_RESPONSE_MAX_TOKENS = int(os.environ.get("AGENT_DEFAULT_RESPONSE_MAX_TOKENS", "32768"))
|
||||||
@ -29,9 +33,11 @@ __all__ = [
|
|||||||
"API_BASE_URL",
|
"API_BASE_URL",
|
||||||
"API_KEY",
|
"API_KEY",
|
||||||
"MODEL_ID",
|
"MODEL_ID",
|
||||||
"TAVILY_API_KEY",
|
|
||||||
"DEFAULT_RESPONSE_MAX_TOKENS",
|
"DEFAULT_RESPONSE_MAX_TOKENS",
|
||||||
"THINKING_API_BASE_URL",
|
"THINKING_API_BASE_URL",
|
||||||
"THINKING_API_KEY",
|
"THINKING_API_KEY",
|
||||||
"THINKING_MODEL_ID",
|
"THINKING_MODEL_ID",
|
||||||
|
"TITLE_API_BASE_URL",
|
||||||
|
"TITLE_API_KEY",
|
||||||
|
"TITLE_MODEL_ID",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
"max_output_tokens": 32768,
|
"max_output_tokens": 32768,
|
||||||
"thinkmode_status": {
|
"thinkmode_status": {
|
||||||
"type": "param_toggle",
|
"type": "param_toggle",
|
||||||
"model_id": "kimi-k2.5",
|
"model_id": "kimi-k2.6",
|
||||||
"fast_extra_parameter": {
|
"fast_extra_parameter": {
|
||||||
"thinking": {
|
"thinking": {
|
||||||
"type": "disabled"
|
"type": "disabled"
|
||||||
|
|||||||
17
config/search.py
Normal file
17
config/search.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""搜索相关配置。"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 选择 Tavily 使用哪个环境变量中的密钥。
|
||||||
|
# 默认保持兼容:仍使用 AGENT_TAVILY_API_KEY。
|
||||||
|
# 你可以改成例如:AGENT_TAVILY_API_KEY_2 / AGENT_TAVILY_API_KEY_BACKUP
|
||||||
|
TAVILY_API_KEY_ENV_NAME = "AGENT_TAVILY_API_KEY_2"
|
||||||
|
|
||||||
|
# 实际生效的 Tavily 密钥
|
||||||
|
TAVILY_API_KEY = os.environ.get(TAVILY_API_KEY_ENV_NAME, "")
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"TAVILY_API_KEY_ENV_NAME",
|
||||||
|
"TAVILY_API_KEY",
|
||||||
|
]
|
||||||
|
|
||||||
@ -81,27 +81,8 @@ def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: st
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 无运行任务时,直接写入当前会话并广播前端显示(来源固定为 notify)。
|
# 无运行任务时静默处理:不写入对话,不向前端插入通知消息。
|
||||||
try:
|
return
|
||||||
if getattr(terminal, "context_manager", None):
|
|
||||||
terminal.context_manager.add_conversation("user", notice, metadata=metadata)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
socketio.emit(
|
|
||||||
"user_message",
|
|
||||||
{
|
|
||||||
"message": notice,
|
|
||||||
"runtime_guidance": True,
|
|
||||||
"runtime_guidance_original": notice,
|
|
||||||
"runtime_mode_notice": True,
|
|
||||||
"message_source": "notify",
|
|
||||||
"conversation_id": getattr(getattr(terminal, "context_manager", None), "current_conversation_id", None),
|
|
||||||
},
|
|
||||||
room=f"user_{username}",
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@chat_bp.route('/api/thinking-mode', methods=['POST'])
|
@chat_bp.route('/api/thinking-mode', methods=['POST'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from config import TITLE_API_BASE_URL, TITLE_API_KEY, TITLE_MODEL_ID
|
||||||
from core.web_terminal import WebTerminal
|
from core.web_terminal import WebTerminal
|
||||||
from utils.api_client import DeepSeekClient
|
from utils.api_client import DeepSeekClient
|
||||||
|
|
||||||
@ -19,6 +20,15 @@ async def _generate_title_async(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
client = DeepSeekClient(thinking_mode=False, web_mode=True)
|
client = DeepSeekClient(thinking_mode=False, web_mode=True)
|
||||||
|
# 对话标题生成使用独立模型配置(可在 .env 中单独指定)
|
||||||
|
client.fast_api_config = {
|
||||||
|
"base_url": TITLE_API_BASE_URL,
|
||||||
|
"api_key": TITLE_API_KEY,
|
||||||
|
"model_id": TITLE_MODEL_ID,
|
||||||
|
}
|
||||||
|
client.api_base_url = TITLE_API_BASE_URL
|
||||||
|
client.api_key = TITLE_API_KEY
|
||||||
|
client.model_id = TITLE_MODEL_ID
|
||||||
try:
|
try:
|
||||||
prompt_text = Path(title_prompt_path).read_text(encoding="utf-8")
|
prompt_text = Path(title_prompt_path).read_text(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@ -264,6 +264,7 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
|||||||
|
|
||||||
# 执行每个工具
|
# 执行每个工具
|
||||||
pending_runtime_mode_notices: List[str] = []
|
pending_runtime_mode_notices: List[str] = []
|
||||||
|
last_completed_tool_call_id: Optional[str] = None
|
||||||
for tool_call in tool_calls:
|
for tool_call in tool_calls:
|
||||||
# 检查停止标志
|
# 检查停止标志
|
||||||
client_stop_info = get_stop_flag(client_sid, username)
|
client_stop_info = get_stop_flag(client_sid, username)
|
||||||
@ -1168,6 +1169,7 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
|||||||
"name": function_name,
|
"name": function_name,
|
||||||
"content": tool_message_content
|
"content": tool_message_content
|
||||||
})
|
})
|
||||||
|
last_completed_tool_call_id = tool_call_id
|
||||||
|
|
||||||
if not pending_runtime_mode_notices:
|
if not pending_runtime_mode_notices:
|
||||||
try:
|
try:
|
||||||
@ -1176,16 +1178,40 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_log(f"[RuntimeMode] 应用挂起模式变更失败: {exc}")
|
debug_log(f"[RuntimeMode] 应用挂起模式变更失败: {exc}")
|
||||||
|
|
||||||
debug_log(f"[SubAgent] after tool={function_name} call_id={tool_call_id} -> poll updates")
|
|
||||||
await process_sub_agent_updates(messages=messages, inline=True, after_tool_call_id=tool_call_id, web_terminal=web_terminal, sender=sender, debug_log=debug_log, maybe_mark_failure_from_message=maybe_mark_failure_from_message)
|
|
||||||
debug_log(f"[BgCmdDebug] after tool={function_name} call_id={tool_call_id} -> poll background command updates (inline)")
|
|
||||||
await process_background_command_updates(messages=messages, inline=True, after_tool_call_id=tool_call_id, web_terminal=web_terminal, sender=sender, debug_log=debug_log, maybe_mark_failure_from_message=maybe_mark_failure_from_message)
|
|
||||||
|
|
||||||
await asyncio.sleep(0.2)
|
await asyncio.sleep(0.2)
|
||||||
|
|
||||||
if tool_failed:
|
if tool_failed:
|
||||||
mark_force_thinking(web_terminal, reason=f"{function_name}_failed")
|
mark_force_thinking(web_terminal, reason=f"{function_name}_failed")
|
||||||
|
|
||||||
|
# 子智能体/后台指令完成通知:必须等待同一轮全部 tool_call 都完成后再注入,
|
||||||
|
# 避免在 assistant.tool_calls 与未完成的 tool 结果之间插入 system 消息导致 API 报错。
|
||||||
|
if last_completed_tool_call_id:
|
||||||
|
debug_log(
|
||||||
|
f"[SubAgent] after all tools finished -> poll updates inline after_tool_call_id={last_completed_tool_call_id}"
|
||||||
|
)
|
||||||
|
await process_sub_agent_updates(
|
||||||
|
messages=messages,
|
||||||
|
inline=True,
|
||||||
|
after_tool_call_id=last_completed_tool_call_id,
|
||||||
|
web_terminal=web_terminal,
|
||||||
|
sender=sender,
|
||||||
|
debug_log=debug_log,
|
||||||
|
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||||
|
)
|
||||||
|
debug_log(
|
||||||
|
"[BgCmdDebug] after all tools finished -> poll background command updates "
|
||||||
|
f"inline after_tool_call_id={last_completed_tool_call_id}"
|
||||||
|
)
|
||||||
|
await process_background_command_updates(
|
||||||
|
messages=messages,
|
||||||
|
inline=True,
|
||||||
|
after_tool_call_id=last_completed_tool_call_id,
|
||||||
|
web_terminal=web_terminal,
|
||||||
|
sender=sender,
|
||||||
|
debug_log=debug_log,
|
||||||
|
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||||
|
)
|
||||||
|
|
||||||
# 运行期模式通知:必须等待同一轮全部 tool_call 都完成后再注入,
|
# 运行期模式通知:必须等待同一轮全部 tool_call 都完成后再注入,
|
||||||
# 避免在 assistant.tool_calls 与对应 tool 消息之间插入 system 消息导致 API 报错。
|
# 避免在 assistant.tool_calls 与对应 tool 消息之间插入 system 消息导致 API 报错。
|
||||||
if pending_runtime_mode_notices:
|
if pending_runtime_mode_notices:
|
||||||
|
|||||||
@ -160,12 +160,36 @@ export const resourceMethods = {
|
|||||||
this.executionModeDirectUntil = status.execution_mode.direct_until ?? null;
|
this.executionModeDirectUntil = status.execution_mode.direct_until ?? null;
|
||||||
this.executionModeEnabled =
|
this.executionModeEnabled =
|
||||||
typeof status.execution_mode_enabled === 'boolean' ? status.execution_mode_enabled : true;
|
typeof status.execution_mode_enabled === 'boolean' ? status.execution_mode_enabled : true;
|
||||||
|
if (typeof this.scheduleExecutionModeExpirySync === 'function') {
|
||||||
|
this.scheduleExecutionModeExpirySync(this.executionModeDirectUntil);
|
||||||
|
}
|
||||||
const currentMode = this.currentExecutionMode;
|
const currentMode = this.currentExecutionMode;
|
||||||
|
const parseDirectUntilMs = (value: any): number => {
|
||||||
|
if (!value) return 0;
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value > 1e12 ? value : value * 1000;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parsed = Date.parse(value);
|
||||||
|
return Number.isNaN(parsed) ? 0 : parsed;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
const prevDirectUntilMs = parseDirectUntilMs(prevDirectUntil);
|
||||||
|
const fallbackLikelyByExpiry = !!prevDirectUntilMs && Date.now() >= prevDirectUntilMs - 1200;
|
||||||
const directExpiredToSandbox =
|
const directExpiredToSandbox =
|
||||||
prevExecutionMode === 'direct' &&
|
prevExecutionMode === 'direct' && currentMode === 'sandbox' && fallbackLikelyByExpiry;
|
||||||
currentMode === 'sandbox' &&
|
|
||||||
!!prevDirectUntil;
|
|
||||||
if (directExpiredToSandbox) {
|
if (directExpiredToSandbox) {
|
||||||
|
if (this.currentPermissionMode === 'unrestricted') {
|
||||||
|
if (typeof pendingModes.permission_mode === 'string' && pendingModes.permission_mode) {
|
||||||
|
this.currentPermissionMode = pendingModes.permission_mode;
|
||||||
|
} else {
|
||||||
|
this.currentPermissionMode = 'approval';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof this.fetchPermissionMode === 'function') {
|
||||||
|
Promise.resolve(this.fetchPermissionMode()).catch(() => {});
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (this.executionModeAutoFallbackToastId) {
|
if (this.executionModeAutoFallbackToastId) {
|
||||||
this.uiDismissToast(this.executionModeAutoFallbackToastId);
|
this.uiDismissToast(this.executionModeAutoFallbackToastId);
|
||||||
@ -173,7 +197,7 @@ export const resourceMethods = {
|
|||||||
} catch (_err) {}
|
} catch (_err) {}
|
||||||
this.executionModeAutoFallbackToastId = this.uiPushToast({
|
this.executionModeAutoFallbackToastId = this.uiPushToast({
|
||||||
title: '执行环境已自动回退',
|
title: '执行环境已自动回退',
|
||||||
message: '完全访问权限已到期,当前已切换为沙箱执行环境。',
|
message: '完全访问权限已到期,已自动切回受限权限并切换为沙箱执行环境。',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
duration: null
|
duration: null
|
||||||
});
|
});
|
||||||
|
|||||||
@ -84,12 +84,7 @@ function isRuntimeModeNoticePayload(data: any): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const meta = data.metadata || {};
|
const meta = data.metadata || {};
|
||||||
return !!(
|
return !!(data.runtime_mode_notice || meta.runtime_mode_notice);
|
||||||
data.runtime_guidance ||
|
|
||||||
meta.runtime_guidance ||
|
|
||||||
data.runtime_mode_notice ||
|
|
||||||
meta.runtime_mode_notice
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveUserMessageSource(data: any): string {
|
function resolveUserMessageSource(data: any): string {
|
||||||
@ -1582,13 +1577,17 @@ export const taskPollingMethods = {
|
|||||||
this.taskInProgress = true;
|
this.taskInProgress = true;
|
||||||
this.streamingMessage = false;
|
this.streamingMessage = false;
|
||||||
this.stopRequested = false;
|
this.stopRequested = false;
|
||||||
} else if (isRuntimeModeNotice) {
|
|
||||||
this.chatAddUserMessage(message, [], [], [], 'notify');
|
|
||||||
} else {
|
} else {
|
||||||
debugLog('[TaskPolling] 跳过自动代发 user_message 渲染', {
|
const source = resolveUserMessageSource(data);
|
||||||
messagePreview: message.slice(0, 80),
|
// 仅在对话运行期间展示 runtime_mode_notice;其余自动消息按来源正常显示,
|
||||||
data
|
// 保持与后端历史重建一致(guidance/notify/sub_agent/background_command)。
|
||||||
});
|
if (isRuntimeModeNotice && !(this.taskInProgress || this.streamingMessage)) {
|
||||||
|
debugLog('[TaskPolling] 空闲态跳过 runtime_mode_notice 用户通知', {
|
||||||
|
messagePreview: message.slice(0, 80)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.chatAddUserMessage(message, [], [], [], source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (data?.sub_agent_notice) {
|
if (data?.sub_agent_notice) {
|
||||||
if (typeof data?.has_running_sub_agents === 'boolean') {
|
if (typeof data?.has_running_sub_agents === 'boolean') {
|
||||||
|
|||||||
@ -1204,6 +1204,7 @@ export const uiMethods = {
|
|||||||
this.executionModeDirectUntil = state.direct_until ?? null;
|
this.executionModeDirectUntil = state.direct_until ?? null;
|
||||||
this.executionModeTtlSeconds =
|
this.executionModeTtlSeconds =
|
||||||
typeof state.ttl_seconds === 'number' ? state.ttl_seconds : this.executionModeTtlSeconds;
|
typeof state.ttl_seconds === 'number' ? state.ttl_seconds : this.executionModeTtlSeconds;
|
||||||
|
this.scheduleExecutionModeExpirySync(this.executionModeDirectUntil);
|
||||||
this.uiPushToast({
|
this.uiPushToast({
|
||||||
title: '执行环境已更新',
|
title: '执行环境已更新',
|
||||||
message: payload?.message || '已立即生效',
|
message: payload?.message || '已立即生效',
|
||||||
@ -1245,6 +1246,8 @@ export const uiMethods = {
|
|||||||
|
|
||||||
async fetchExecutionMode() {
|
async fetchExecutionMode() {
|
||||||
try {
|
try {
|
||||||
|
const prevExecutionMode = this.currentExecutionMode;
|
||||||
|
const prevDirectUntil = this.executionModeDirectUntil;
|
||||||
const response = await fetch('/api/execution-mode');
|
const response = await fetch('/api/execution-mode');
|
||||||
const payload = await response.json().catch(() => ({}));
|
const payload = await response.json().catch(() => ({}));
|
||||||
if (!response.ok || !payload?.success) {
|
if (!response.ok || !payload?.success) {
|
||||||
@ -1260,11 +1263,78 @@ export const uiMethods = {
|
|||||||
if (typeof state.ttl_seconds === 'number') {
|
if (typeof state.ttl_seconds === 'number') {
|
||||||
this.executionModeTtlSeconds = state.ttl_seconds;
|
this.executionModeTtlSeconds = state.ttl_seconds;
|
||||||
}
|
}
|
||||||
|
this.scheduleExecutionModeExpirySync(this.executionModeDirectUntil);
|
||||||
|
const parseDirectUntilMs = (value) => {
|
||||||
|
if (!value) return 0;
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value > 1e12 ? value : value * 1000;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parsed = Date.parse(value);
|
||||||
|
return Number.isNaN(parsed) ? 0 : parsed;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
const prevDirectUntilMs = parseDirectUntilMs(prevDirectUntil);
|
||||||
|
const fallbackLikelyByExpiry = !!prevDirectUntilMs && Date.now() >= prevDirectUntilMs - 1200;
|
||||||
|
const directExpiredToSandbox =
|
||||||
|
prevExecutionMode === 'direct' &&
|
||||||
|
this.currentExecutionMode === 'sandbox' &&
|
||||||
|
fallbackLikelyByExpiry;
|
||||||
|
if (directExpiredToSandbox) {
|
||||||
|
if (this.currentPermissionMode === 'unrestricted') {
|
||||||
|
this.currentPermissionMode = 'approval';
|
||||||
|
}
|
||||||
|
Promise.resolve(this.fetchPermissionMode()).catch(() => {});
|
||||||
|
try {
|
||||||
|
if (this.executionModeAutoFallbackToastId) {
|
||||||
|
this.uiDismissToast(this.executionModeAutoFallbackToastId);
|
||||||
|
}
|
||||||
|
} catch (_err) {}
|
||||||
|
this.executionModeAutoFallbackToastId = this.uiPushToast({
|
||||||
|
title: '执行环境已自动回退',
|
||||||
|
message: '完全访问权限已到期,已自动切回受限权限并切换为沙箱执行环境。',
|
||||||
|
type: 'warning',
|
||||||
|
duration: null
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearExecutionModeExpirySyncTimer() {
|
||||||
|
if (this.executionModeExpirySyncTimer) {
|
||||||
|
clearTimeout(this.executionModeExpirySyncTimer);
|
||||||
|
this.executionModeExpirySyncTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleExecutionModeExpirySync(directUntil) {
|
||||||
|
this.clearExecutionModeExpirySyncTimer();
|
||||||
|
if (!this.executionModeEnabled || this.currentExecutionMode !== 'direct' || !directUntil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let untilMs = 0;
|
||||||
|
if (typeof directUntil === 'number') {
|
||||||
|
untilMs = directUntil > 1e12 ? directUntil : directUntil * 1000;
|
||||||
|
} else if (typeof directUntil === 'string') {
|
||||||
|
const parsed = Date.parse(directUntil);
|
||||||
|
if (!Number.isNaN(parsed)) {
|
||||||
|
untilMs = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!untilMs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const delayMs = Math.max(250, untilMs - Date.now() + 250);
|
||||||
|
this.executionModeExpirySyncTimer = window.setTimeout(async () => {
|
||||||
|
this.executionModeExpirySyncTimer = null;
|
||||||
|
await this.fetchExecutionMode();
|
||||||
|
await this.fetchPermissionMode();
|
||||||
|
}, delayMs);
|
||||||
|
},
|
||||||
|
|
||||||
async openPathAuthorizationDialog() {
|
async openPathAuthorizationDialog() {
|
||||||
if (!this.executionModeEnabled) return;
|
if (!this.executionModeEnabled) return;
|
||||||
this.pathAuthorizationDialogOpen = true;
|
this.pathAuthorizationDialogOpen = true;
|
||||||
|
|||||||
@ -104,6 +104,7 @@ export function dataState() {
|
|||||||
executionModeDirectUntil: null,
|
executionModeDirectUntil: null,
|
||||||
executionModeAutoFallbackToastId: null,
|
executionModeAutoFallbackToastId: null,
|
||||||
executionModeTtlSeconds: 600,
|
executionModeTtlSeconds: 600,
|
||||||
|
executionModeExpirySyncTimer: null,
|
||||||
pathAuthorizationDialogOpen: false,
|
pathAuthorizationDialogOpen: false,
|
||||||
pathAuthorizationMode: 'writable',
|
pathAuthorizationMode: 'writable',
|
||||||
pathAuthorizationWritableDraft: '',
|
pathAuthorizationWritableDraft: '',
|
||||||
|
|||||||
@ -338,11 +338,32 @@ function normalizeShowTagsPlugin() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wrapMarkdownTablesPlugin() {
|
||||||
|
return (tree: any) => {
|
||||||
|
visit(tree, 'element', (node: any, index: number | undefined, parent: any) => {
|
||||||
|
if (!node || node.tagName !== 'table' || !parent || typeof index !== 'number') return;
|
||||||
|
if (parent?.tagName === 'div' && Array.isArray(parent?.properties?.className)) {
|
||||||
|
const classes = parent.properties.className as string[];
|
||||||
|
if (classes.includes('md-table-scroll')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent.children[index] = {
|
||||||
|
type: 'element',
|
||||||
|
tagName: 'div',
|
||||||
|
properties: { className: ['md-table-scroll'], 'data-md-table-scroll': '1' },
|
||||||
|
children: [node]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const sanitizedSchema: Record<string, any> = {
|
const sanitizedSchema: Record<string, any> = {
|
||||||
...defaultSchema,
|
...defaultSchema,
|
||||||
tagNames: [...(defaultSchema.tagNames || []), 'show_html', 'show_image', 'show-html', 'show-image'],
|
tagNames: [...(defaultSchema.tagNames || []), 'show_html', 'show_image', 'show-html', 'show-image'],
|
||||||
attributes: {
|
attributes: {
|
||||||
...(defaultSchema.attributes || {}),
|
...(defaultSchema.attributes || {}),
|
||||||
|
div: [...((defaultSchema.attributes || {}).div || []), 'className', 'data-md-table-scroll'],
|
||||||
show_html: ['ratio', 'js', 'data-encoded', 'data-partial'],
|
show_html: ['ratio', 'js', 'data-encoded', 'data-partial'],
|
||||||
show_image: ['src', 'alt', 'title', 'width', 'height'],
|
show_image: ['src', 'alt', 'title', 'width', 'height'],
|
||||||
'show-html': ['ratio', 'js', 'data-encoded', 'data-partial'],
|
'show-html': ['ratio', 'js', 'data-encoded', 'data-partial'],
|
||||||
@ -357,6 +378,7 @@ const markdownProcessor = unified()
|
|||||||
.use(remarkRehype, { allowDangerousHtml: true })
|
.use(remarkRehype, { allowDangerousHtml: true })
|
||||||
.use(rehypeRaw)
|
.use(rehypeRaw)
|
||||||
.use(normalizeShowTagsPlugin)
|
.use(normalizeShowTagsPlugin)
|
||||||
|
.use(wrapMarkdownTablesPlugin)
|
||||||
.use(rehypeSanitize, sanitizedSchema)
|
.use(rehypeSanitize, sanitizedSchema)
|
||||||
.use(rehypeStringify, { allowDangerousHtml: false });
|
.use(rehypeStringify, { allowDangerousHtml: false });
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,7 @@
|
|||||||
.messages-area {
|
.messages-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-anchor: none;
|
overflow-anchor: none;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
padding-top: 30px;
|
padding-top: 30px;
|
||||||
@ -412,6 +413,7 @@
|
|||||||
|
|
||||||
.message-block {
|
.message-block {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-block.message-block--sub-agent-notice {
|
.message-block.message-block--sub-agent-notice {
|
||||||
@ -1084,6 +1086,9 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
|||||||
|
|
||||||
.text-output .text-content {
|
.text-output .text-content {
|
||||||
padding: 0 20px 0 15px;
|
padding: 0 20px 0 15px;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-output pre {
|
.text-output pre {
|
||||||
@ -1093,7 +1098,8 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
|||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
scrollbar-gutter: stable both-edges;
|
scrollbar-gutter: stable;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-output pre code {
|
.text-output pre code {
|
||||||
@ -1106,15 +1112,58 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-output .text-content table {
|
.text-output .text-content table {
|
||||||
width: 100%;
|
width: max-content;
|
||||||
|
min-width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin: 16px 0;
|
margin: 0;
|
||||||
background: var(--theme-surface-soft);
|
background: var(--theme-surface-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output .text-content .md-table-scroll,
|
||||||
|
.text-output .text-content [data-md-table-scroll='1'] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 16px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
border: 1px solid var(--claude-border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
background: var(--theme-surface-soft);
|
||||||
box-shadow: var(--theme-shadow-soft);
|
box-shadow: var(--theme-shadow-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-output .text-content .md-table-scroll > table,
|
||||||
|
.text-output .text-content [data-md-table-scroll='1'] > table {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output .text-content .md-table-scroll::-webkit-scrollbar,
|
||||||
|
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output .text-content .md-table-scroll::-webkit-scrollbar-track,
|
||||||
|
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar-track {
|
||||||
|
background: color-mix(in srgb, var(--theme-surface-muted, #d1d5db) 70%, transparent);
|
||||||
|
border-radius: 999px;
|
||||||
|
margin: 0 8px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output .text-content .md-table-scroll::-webkit-scrollbar-thumb,
|
||||||
|
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar-thumb {
|
||||||
|
background: color-mix(in srgb, var(--claude-text-secondary, #6b7280) 55%, transparent);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output .text-content .md-table-scroll,
|
||||||
|
.text-output .text-content [data-md-table-scroll='1'] {
|
||||||
|
scrollbar-color: color-mix(in srgb, var(--claude-text-secondary, #6b7280) 55%, transparent)
|
||||||
|
color-mix(in srgb, var(--theme-surface-muted, #d1d5db) 70%, transparent);
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
.text-output .text-content thead {
|
.text-output .text-content thead {
|
||||||
background: var(--theme-chip-bg);
|
background: var(--theme-chip-bg);
|
||||||
}
|
}
|
||||||
@ -1251,7 +1300,8 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
|||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
scrollbar-gutter: stable both-edges;
|
scrollbar-gutter: stable;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block-wrapper pre code {
|
.code-block-wrapper pre code {
|
||||||
|
|||||||
@ -396,10 +396,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.todo-panel {
|
.todo-panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
padding: 16px 20px 24px;
|
padding: 16px 20px 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-agent-panel {
|
.sub-agent-panel {
|
||||||
@ -412,6 +415,22 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 左侧四个工作面板统一支持纵向滚动 */
|
||||||
|
.file-tree,
|
||||||
|
.todo-panel,
|
||||||
|
.sub-agent-panel {
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-tree::-webkit-scrollbar,
|
||||||
|
.todo-panel::-webkit-scrollbar,
|
||||||
|
.sub-agent-panel::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sub-agent-cards {
|
.sub-agent-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user