Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/agent-gateway/web/src/i18n/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,8 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.systemTools": "系统工具",
"settings.systemToolsDesc":
"查看 LiveAgent 在 Agent 模式下自动注册的内置工具,并为每个工具设置审批策略(放行 / 执行前询问 / 拒绝)。",
"settings.interactiveTimeout.title": "交互式应答超时",
"settings.interactiveTimeout.unit": "分钟",
"settings.builtinToolCategory.fs": "文件系统",
"settings.builtinToolCategory.process": "终端与进程",
"settings.builtinToolCategory.intelligence": "智能与记忆",
Expand Down Expand Up @@ -1339,7 +1341,7 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.builtinTool.ask_user_question.name": "用户提问",
"settings.builtinTool.ask_user_question.desc": "以选项卡片向你提问并等待选择",
"settings.builtinTool.ask_user_question.detail":
"模型在需要你决策时发起选择题(一次最多 4 个问题,每题 2-6 个选项且各题数量一致,推荐项排在首位)。卡片暂停执行等待作答,3 分钟内未作答自动按推荐项继续执行;桌面端与 WebUI 均可作答,点击停止可跳过。仅在对话场景注册。",
"模型在需要你决策时发起选择题(一次最多 4 个问题,每题 2-6 个选项且各题数量一致,推荐项排在首位)。卡片暂停执行等待作答,超时时间可用下方滑块调整,超时后未作答自动按推荐项继续执行;桌面端与 WebUI 均可作答,点击停止可跳过。仅在对话场景注册。",
"settings.builtinTool.cron_task_manager.name": "定时任务",
"settings.builtinTool.cron_task_manager.desc": "创建与管理定时自动任务",
"settings.builtinTool.cron_task_manager.detail":
Expand Down Expand Up @@ -3540,6 +3542,8 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.systemTools": "System Tools",
"settings.systemToolsDesc":
"View the built-in tools that LiveAgent registers automatically in Agent mode, and set an approval policy per tool (allow / ask before running / deny).",
"settings.interactiveTimeout.title": "Interactive answer timeout",
"settings.interactiveTimeout.unit": "min",
"settings.builtinToolCategory.fs": "File System",
"settings.builtinToolCategory.process": "Terminal & Processes",
"settings.builtinToolCategory.intelligence": "Intelligence & Memory",
Expand Down Expand Up @@ -3610,7 +3614,7 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.builtinTool.ask_user_question.desc":
"Ask you multiple-choice questions in a card and wait for your selections",
"settings.builtinTool.ask_user_question.detail":
"Lets the model ask you multiple-choice questions when a decision is yours to make (up to 4 questions per call, 2-6 options each with the same count across questions, recommended option shown first). Execution pauses on an interactive card until you answer — from the desktop or the WebUI; after 3 minutes without an answer the recommended options are auto-selected, and pressing Stop skips the question. Chat sessions only.",
"Lets the model ask you multiple-choice questions when a decision is yours to make (up to 4 questions per call, 2-6 options each with the same count across questions, recommended option shown first). Execution pauses on an interactive card until you answer — from the desktop or the WebUI; the answer window is adjustable with the slider below, and when it elapses without an answer the recommended options are auto-selected. Pressing Stop skips the question. Chat sessions only.",
"settings.builtinTool.cron_task_manager.name": "Scheduled Tasks",
"settings.builtinTool.cron_task_manager.desc": "Create and manage scheduled automations",
"settings.builtinTool.cron_task_manager.detail":
Expand Down
16 changes: 16 additions & 0 deletions crates/agent-gateway/web/src/lib/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ export type SystemProxyConfig = {
/** 工具审批策略:allow 直接执行、ask 执行前请求用户批准、deny 直接拒绝。 */
export type ToolPolicy = "allow" | "ask" | "deny";

/** 交互式应答(AskUserQuestion 提问卡 + 工具审批栏)的等待窗口,单位分钟。
* 正数 = 超时窗口,超时后按各交互既定姿态落定(提问自动选推荐项并继续、
* 审批按拒绝)。两处交互共用同一窗口。填很大的数(如 99999)≈ 永不超时。 */
export const INTERACTIVE_TIMEOUT_DEFAULT_MINUTES = 3;

export type SystemSettings = {
executionMode: ExecutionMode;
workdir: string;
Expand All @@ -245,6 +250,8 @@ export type SystemSettings = {
* 回写会丢掉桌面端设置的策略。策略的裁决在桌面端 resolveToolPolicy。
*/
toolPolicies?: Record<string, ToolPolicy>;
/** 交互式应答超时(分钟):正数=窗口,超长≈永不。与桌面端对齐原样透传。 */
interactiveTimeoutMinutes: number;
workspaceProjects: WorkspaceProject[];
activeWorkspaceProjectId?: string;
hiddenWorkspaceProjectPaths: string[];
Expand Down Expand Up @@ -1836,12 +1843,20 @@ export function normalizeSystemProxyConfig(input: unknown): SystemProxyConfig {
};
}

/** 归一化交互式应答超时(分钟):正数=窗口(超长≈永不),缺省/非法/非正回 3。 */
export function normalizeInteractiveTimeoutMinutes(input: unknown): number {
return typeof input === "number" && Number.isFinite(input) && input > 0
? input
: INTERACTIVE_TIMEOUT_DEFAULT_MINUTES;
}

export function normalizeSystemSettings(input: unknown): SystemSettings {
const obj = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
return {
executionMode: normalizeExecutionMode(obj.executionMode),
workdir: normalizeWorkdir(obj.workdir),
toolPolicies: normalizeToolPolicies(obj.toolPolicies),
interactiveTimeoutMinutes: normalizeInteractiveTimeoutMinutes(obj.interactiveTimeoutMinutes),
workspaceProjects: normalizeWorkspaceProjects(obj.workspaceProjects),
activeWorkspaceProjectId:
typeof obj.activeWorkspaceProjectId === "string" && obj.activeWorkspaceProjectId.trim()
Expand Down Expand Up @@ -2499,6 +2514,7 @@ export function getDefaultSettings(): AppSettings {
archivedWorkspaceProjectPaths: [],
workspaceResourceSettings: {},
systemProxy: getDefaultSystemProxyConfig(),
interactiveTimeoutMinutes: INTERACTIVE_TIMEOUT_DEFAULT_MINUTES,
},
customProviders,
mcp: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const SYSTEM_MISSING_WORKSPACE_PROJECT_PATHS_KEY: &str = "missingWorkspaceProjec
const SYSTEM_ARCHIVED_WORKSPACE_PROJECT_PATHS_KEY: &str = "archivedWorkspaceProjectPaths";
const SYSTEM_WORKSPACE_RESOURCE_SETTINGS_KEY: &str = "workspaceResourceSettings";
const SYSTEM_SYSTEM_PROXY_KEY: &str = "systemProxy";
// 与 toolPolicies 同理必须进保存白名单,否则前端改完滑块重启即回默认值。
const SYSTEM_INTERACTIVE_TIMEOUT_MINUTES_KEY: &str = "interactiveTimeoutMinutes";
const DEFAULT_WORKSPACE_PROJECT_ID: &str = "default-project";
const DEFAULT_WORKSPACE_PROJECT_NAME: &str = "Default Project";
pub(crate) const PROVIDER_API_KEY_UPDATES_FIELD: &str = "providerApiKeyUpdates";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ fn system_value_with_defaults(raw: Option<Value>, default_workdir: &str) -> Valu
SYSTEM_SYSTEM_PROXY_KEY.to_string(),
normalize_system_proxy_value(system.get(SYSTEM_SYSTEM_PROXY_KEY)),
);
system
.entry(SYSTEM_INTERACTIVE_TIMEOUT_MINUTES_KEY.to_string())
.or_insert_with(|| json!(3));

Value::Object(system)
}
Expand Down Expand Up @@ -507,6 +510,7 @@ fn save_system_with_default_workdir(
SYSTEM_ARCHIVED_WORKSPACE_PROJECT_PATHS_KEY,
SYSTEM_WORKSPACE_RESOURCE_SETTINGS_KEY,
SYSTEM_SYSTEM_PROXY_KEY,
SYSTEM_INTERACTIVE_TIMEOUT_MINUTES_KEY,
] {
let value = system.get(key).cloned().unwrap_or(Value::Null);
tx.execute(
Expand Down
32 changes: 31 additions & 1 deletion crates/agent-gui/src-tauri/src/commands/config/settings/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,14 +1121,15 @@ mod tests {
};
let loaded = load_system(&conn).expect("load system");

assert_eq!(row_count, 10);
assert_eq!(row_count, 11);
assert_eq!(
keys,
vec![
SYSTEM_ACTIVE_WORKSPACE_PROJECT_ID_KEY.to_string(),
SYSTEM_ARCHIVED_WORKSPACE_PROJECT_PATHS_KEY.to_string(),
SYSTEM_EXECUTION_MODE_KEY.to_string(),
SYSTEM_HIDDEN_WORKSPACE_PROJECT_PATHS_KEY.to_string(),
SYSTEM_INTERACTIVE_TIMEOUT_MINUTES_KEY.to_string(),
SYSTEM_MISSING_WORKSPACE_PROJECT_PATHS_KEY.to_string(),
SYSTEM_SYSTEM_PROXY_KEY.to_string(),
SYSTEM_TOOL_POLICIES_KEY.to_string(),
Expand All @@ -1147,6 +1148,7 @@ mod tests {
"archivedWorkspaceProjectPaths": [],
"workspaceResourceSettings": {},
"systemProxy": default_system_proxy_json(),
"interactiveTimeoutMinutes": 3,
"workdir": default_workdir.clone(),
"toolPolicies": { "Bash": "ask", "server:docs-mcp": "deny" },
"workspaceProjects": [
Expand Down Expand Up @@ -1191,6 +1193,31 @@ mod tests {
);
}

/// 交互式应答超时必须真正落库:该键此前不在保存白名单里,前端调完滑块
/// 重启即回默认值,功能等于没生效。
#[test]
fn save_system_round_trips_interactive_timeout_minutes() {
let mut conn = open_memory_db();
save_system_with_default_workdir(
&mut conn,
json!({
"executionMode": "tools",
"workdir": "/tmp/liveagent-default-project",
"interactiveTimeoutMinutes": 30,
}),
"/tmp/liveagent-default-project",
)
.expect("save system");

let loaded = load_system(&conn)
.expect("load system")
.expect("system settings");
assert_eq!(
loaded.get(SYSTEM_INTERACTIVE_TIMEOUT_MINUTES_KEY),
Some(&json!(30))
);
}

#[test]
fn save_system_normalizes_workspace_resource_settings() {
let now = std::time::SystemTime::now()
Expand Down Expand Up @@ -1401,6 +1428,7 @@ mod tests {
"archivedWorkspaceProjectPaths": [],
"workspaceResourceSettings": {},
"systemProxy": default_system_proxy_json(),
"interactiveTimeoutMinutes": 3,
"workdir": "/tmp/liveagent-default-project",
"toolPolicies": null,
"workspaceProjects": [
Expand Down Expand Up @@ -1453,6 +1481,7 @@ mod tests {
"archivedWorkspaceProjectPaths": [],
"workspaceResourceSettings": {},
"systemProxy": default_system_proxy_json(),
"interactiveTimeoutMinutes": 3,
"workdir": "/tmp/liveagent-default-project",
"toolPolicies": null,
"workspaceProjects": [
Expand Down Expand Up @@ -1487,6 +1516,7 @@ mod tests {
"archivedWorkspaceProjectPaths": [],
"workspaceResourceSettings": {},
"systemProxy": default_system_proxy_json(),
"interactiveTimeoutMinutes": 3,
"workdir": "/tmp/liveagent-default-project",
"workspaceProjects": [
{
Expand Down
12 changes: 12 additions & 0 deletions crates/agent-gui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
} from "./lib/settings/storage";
import { applyStoredGlobalShortcuts } from "./lib/shortcuts/globalShortcuts";
import { applyFontFamilies } from "./lib/system/fontFamily";
import { setAskUserQuestionTimeoutMs } from "./lib/tools/askUserQuestionTools";
import { setToolApprovalTimeoutMs } from "./lib/tools/toolApproval";
import { ChatPage } from "./pages/ChatPage";
import type { SectionId } from "./pages/settings/types";

Expand Down Expand Up @@ -379,6 +381,16 @@ export default function App() {
const getMcpSettings = useCallback(() => settingsRef.current.mcp, []);
const getToolPolicies = useCallback(() => settingsRef.current.system.toolPolicies, []);

// 把交互式应答超时设置(分钟)注入工具运行时窗口(毫秒):AskUserQuestion 与
// 工具审批共用同一窗口;永不超时用很大的分钟数表达。设置变更后新挂起的提问/
// 审批生效,已挂起的沿用旧窗口。模块级配置由工具侧 ensureAskUserQuestionDeadlineAt /
// requestToolApproval 读取,避免在 6+ 处工具预览调用点逐个传参。
useEffect(() => {
const ms = settings.system.interactiveTimeoutMinutes * 60_000;
setAskUserQuestionTimeoutMs(ms);
setToolApprovalTimeoutMs(ms);
}, [settings.system.interactiveTimeoutMinutes]);

const reloadPersistedSettings = useCallback(async () => {
await saveChainRef.current.catch(() => undefined);
const { settings: loaded, defaultWorkdir } = await loadPersistedSettingsWithDefaults();
Expand Down
8 changes: 6 additions & 2 deletions crates/agent-gui/src/i18n/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,8 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.systemTools": "系统工具",
"settings.systemToolsDesc":
"查看 LiveAgent 在 Agent 模式下自动注册的内置工具,并为每个工具设置审批策略(放行 / 执行前询问 / 拒绝)。",
"settings.interactiveTimeout.title": "交互式应答超时",
"settings.interactiveTimeout.unit": "分钟",
"settings.builtinToolCategory.fs": "文件系统",
"settings.builtinToolCategory.process": "终端与进程",
"settings.builtinToolCategory.intelligence": "智能与记忆",
Expand Down Expand Up @@ -1403,7 +1405,7 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.builtinTool.ask_user_question.name": "用户提问",
"settings.builtinTool.ask_user_question.desc": "以选项卡片向你提问并等待选择",
"settings.builtinTool.ask_user_question.detail":
"模型在需要你决策时发起选择题(一次最多 4 个问题,每题 2-6 个选项且各题数量一致,推荐项排在首位)。卡片暂停执行等待作答,3 分钟内未作答自动按推荐项继续执行;桌面端与 WebUI 均可作答,点击停止可跳过。仅在对话场景注册。",
"模型在需要你决策时发起选择题(一次最多 4 个问题,每题 2-6 个选项且各题数量一致,推荐项排在首位)。卡片暂停执行等待作答,超时时间可用下方滑块调整,超时后未作答自动按推荐项继续执行;桌面端与 WebUI 均可作答,点击停止可跳过。仅在对话场景注册。",
"settings.builtinTool.cron_task_manager.name": "定时任务",
"settings.builtinTool.cron_task_manager.desc": "创建与管理定时自动任务",
"settings.builtinTool.cron_task_manager.detail":
Expand Down Expand Up @@ -3672,6 +3674,8 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.systemTools": "System Tools",
"settings.systemToolsDesc":
"View the built-in tools that LiveAgent registers automatically in Agent mode, and set an approval policy per tool (allow / ask before running / deny).",
"settings.interactiveTimeout.title": "Interactive answer timeout",
"settings.interactiveTimeout.unit": "min",
"settings.builtinToolCategory.fs": "File System",
"settings.builtinToolCategory.process": "Terminal & Processes",
"settings.builtinToolCategory.intelligence": "Intelligence & Memory",
Expand Down Expand Up @@ -3742,7 +3746,7 @@ export const translations: Record<Locale, Record<string, string>> = {
"settings.builtinTool.ask_user_question.desc":
"Ask you multiple-choice questions in a card and wait for your selections",
"settings.builtinTool.ask_user_question.detail":
"Lets the model ask you multiple-choice questions when a decision is yours to make (up to 4 questions per call, 2-6 options each with the same count across questions, recommended option shown first). Execution pauses on an interactive card until you answer — from the desktop or the WebUI; after 3 minutes without an answer the recommended options are auto-selected, and pressing Stop skips the question. Chat sessions only.",
"Lets the model ask you multiple-choice questions when a decision is yours to make (up to 4 questions per call, 2-6 options each with the same count across questions, recommended option shown first). Execution pauses on an interactive card until you answer — from the desktop or the WebUI; the answer window is adjustable with the slider below, and when it elapses without an answer the recommended options are auto-selected. Pressing Stop skips the question. Chat sessions only.",
"settings.builtinTool.cron_task_manager.name": "Scheduled Tasks",
"settings.builtinTool.cron_task_manager.desc": "Create and manage scheduled automations",
"settings.builtinTool.cron_task_manager.detail":
Expand Down
16 changes: 16 additions & 0 deletions crates/agent-gui/src/lib/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ export type SystemProxyConfig = {
/** 工具审批策略:allow 直接执行、ask 执行前请求用户批准、deny 直接拒绝。 */
export type ToolPolicy = "allow" | "ask" | "deny";

/** 交互式应答(AskUserQuestion 提问卡 + 工具审批栏)的等待窗口,单位分钟。
* 正数 = 超时窗口,超时后按各交互既定姿态落定(提问自动选推荐项并继续、
* 审批按拒绝)。两处交互共用同一窗口。填很大的数(如 99999)≈ 永不超时。 */
export const INTERACTIVE_TIMEOUT_DEFAULT_MINUTES = 3;

export type SystemSettings = {
executionMode: ExecutionMode;
workdir: string;
Expand All @@ -264,6 +269,8 @@ export type SystemSettings = {
* 可选:旧快照缺失该字段时视为空表(全部走默认),保证零回归。
*/
toolPolicies?: Record<string, ToolPolicy>;
/** 交互式应答超时(分钟):正数=窗口,超长≈永不。缺省 3,保持历史行为。 */
interactiveTimeoutMinutes: number;
workspaceProjects: WorkspaceProject[];
activeWorkspaceProjectId?: string;
hiddenWorkspaceProjectPaths: string[];
Expand Down Expand Up @@ -1800,12 +1807,20 @@ export function normalizeSystemProxyConfig(input: unknown): SystemProxyConfig {
};
}

/** 归一化交互式应答超时(分钟):正数=窗口(超长≈永不),缺省/非法/非正回 3。 */
export function normalizeInteractiveTimeoutMinutes(input: unknown): number {
return typeof input === "number" && Number.isFinite(input) && input > 0
? input
: INTERACTIVE_TIMEOUT_DEFAULT_MINUTES;
}

export function normalizeSystemSettings(input: unknown): SystemSettings {
const obj = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
return {
executionMode: normalizeExecutionMode(obj.executionMode),
workdir: normalizeWorkdir(obj.workdir),
toolPolicies: normalizeToolPolicies(obj.toolPolicies),
interactiveTimeoutMinutes: normalizeInteractiveTimeoutMinutes(obj.interactiveTimeoutMinutes),
workspaceProjects: normalizeWorkspaceProjects(obj.workspaceProjects),
activeWorkspaceProjectId:
typeof obj.activeWorkspaceProjectId === "string" && obj.activeWorkspaceProjectId.trim()
Expand Down Expand Up @@ -2473,6 +2488,7 @@ export function getDefaultSettings(): AppSettings {
archivedWorkspaceProjectPaths: [],
workspaceResourceSettings: {},
systemProxy: getDefaultSystemProxyConfig(),
interactiveTimeoutMinutes: INTERACTIVE_TIMEOUT_DEFAULT_MINUTES,
},
customProviders,
mcp: {
Expand Down
Loading
Loading