Files
clawpanel/src/lib/tauri-api.js

631 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Tauri API 封装层
* Tauri 环境用 invokeWeb 模式走 dev-api 后端
*/
import { t } from './i18n.js'
export function isTauriRuntime() {
return !!window.__TAURI_INTERNALS__ || !!window.__TAURI__ || window.location?.hostname === 'tauri.localhost'
}
let _tauriListenFn = null
/**
* 安全订阅 Tauri 事件。Web 模式下返回 noop unsubscriber
* 避免动态 import `@tauri-apps/api/event` 时触碰
* `window.__TAURI_INTERNALS__.transformCallback` 引发
* "Cannot read properties of undefined" 报错issue #256
*
* 用法:
* const unlisten = await safeTauriListen('hermes-install-log', e => ...)
* unlisten() // 取消订阅
*/
export async function safeTauriListen(event, cb) {
if (!isTauriRuntime()) return () => {}
if (!_tauriListenFn) {
const mod = await import('@tauri-apps/api/event')
_tauriListenFn = mod.listen
}
return _tauriListenFn(event, cb)
}
// 仅在 Node.js 后端实现的命令Tauri Rust 不处理),强制走 webInvoke
const WEB_ONLY_CMDS = new Set([
'instance_list', 'instance_add', 'instance_remove', 'instance_set_active',
'instance_health_check', 'instance_health_all',
'docker_info', 'docker_list_containers', 'docker_create_container',
'docker_start_container', 'docker_stop_container', 'docker_restart_container',
'docker_remove_container', 'docker_pull_image', 'docker_pull_status',
'docker_list_images', 'docker_list_nodes', 'docker_add_node',
'docker_remove_node', 'docker_cluster_overview',
'get_deploy_mode',
])
let _invokeReady = null
async function getTauriInvoke() {
if (!isTauriRuntime()) return null
if (!_invokeReady) {
_invokeReady = import('@tauri-apps/api/core').then(m => m.invoke)
}
return _invokeReady
}
// 简单缓存:避免页面切换时重复请求后端
const _cache = new Map()
const _inflight = new Map() // in-flight 请求去重,防止缓存过期后同一命令并发 spawn 多个进程
const CACHE_TTL = 15000 // 15秒
// 网络请求日志(用于调试)
const _requestLogs = []
const MAX_LOGS = 100
function logRequest(cmd, args, duration, cached = false) {
const log = {
timestamp: Date.now(),
time: new Date().toLocaleTimeString('zh-CN', { hour12: false, fractionalSecondDigits: 3 }),
cmd,
args: JSON.stringify(args),
duration: duration ? `${duration}ms` : '-',
cached
}
_requestLogs.push(log)
if (_requestLogs.length > MAX_LOGS) {
_requestLogs.shift()
}
}
// 导出日志供调试页面使用
export function getRequestLogs() {
return _requestLogs.slice()
}
export function clearRequestLogs() {
_requestLogs.length = 0
}
function cachedInvoke(cmd, args = {}, ttl = CACHE_TTL) {
const key = cmd + JSON.stringify(args)
const cached = _cache.get(key)
if (cached && Date.now() - cached.ts < ttl) {
logRequest(cmd, args, 0, true)
return Promise.resolve(cached.val)
}
// in-flight 去重:同一个 key 的请求正在执行中,复用同一个 Promise
// 避免缓存过期瞬间多个调用者同时 spawn 进程ARM 设备上的 CPU 爆满根因)
if (_inflight.has(key)) {
return _inflight.get(key)
}
const p = invoke(cmd, args).then(val => {
_cache.set(key, { val, ts: Date.now() })
_inflight.delete(key)
return val
}).catch(err => {
_inflight.delete(key)
throw err
})
_inflight.set(key, p)
return p
}
// 清除指定命令的缓存(写操作后调用)
function invalidate(...cmds) {
if (!cmds.length) {
_cache.clear()
_inflight.clear()
return
}
for (const [k] of _cache) {
if (cmds.some(c => k.startsWith(c))) _cache.delete(k)
}
for (const [k] of _inflight) {
if (cmds.some(c => k.startsWith(c))) _inflight.delete(k)
}
}
// 导出 invalidate 供外部使用
export { invalidate }
async function invoke(cmd, args = {}) {
const start = Date.now()
const tauriInvoke = WEB_ONLY_CMDS.has(cmd) ? null : await getTauriInvoke()
if (tauriInvoke) {
const result = await tauriInvoke(cmd, args)
const duration = Date.now() - start
logRequest(cmd, args, duration, false)
return result
}
// Web 模式:调用 dev-api 后端(真实数据)
const result = await webInvoke(cmd, args)
const duration = Date.now() - start
logRequest(cmd, args, duration, false)
return result
}
// Web 模式:通过 Vite 开发服务器的 API 端点调用真实后端
async function webInvoke(cmd, args) {
const resp = await fetch(`/__api/${cmd}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args),
})
if (resp.status === 401) {
// Tauri 模式下不触发登录浮层Tauri 有自己的认证流程)
if (!isTauriRuntime() && window.__clawpanel_show_login) window.__clawpanel_show_login()
throw new Error(t('common.loginRequired'))
}
// 检测后端是否可用:如果返回的是 HTML非 JSON说明后端未运行
const ct = (resp.headers.get('content-type') || '').toLowerCase()
if (ct.includes('text/html') || ct.includes('text/plain')) {
throw new Error(t('common.backendWebModeRequired'))
}
if (!resp.ok) {
const data = await resp.json().catch(() => ({ error: `HTTP ${resp.status}` }))
throw new Error(data.error || `HTTP ${resp.status}`)
}
return resp.json()
}
async function webStreamInvoke(cmd, args, onEvent, options = {}) {
const resp = await fetch(`/__api/${cmd}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args || {}),
signal: options.signal,
})
if (resp.status === 401) {
if (!isTauriRuntime() && window.__clawpanel_show_login) window.__clawpanel_show_login()
throw new Error(t('common.loginRequired'))
}
if (!resp.ok) {
const data = await resp.json().catch(() => ({ error: `HTTP ${resp.status}` }))
throw new Error(data.error || `HTTP ${resp.status}`)
}
if (!resp.body) throw new Error('Streaming response is not supported by this browser')
const reader = resp.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split(/\r?\n/)
buffer = lines.pop() || ''
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed) continue
const event = JSON.parse(trimmed)
if (typeof onEvent === 'function') onEvent(event)
}
}
const tail = buffer.trim()
if (tail && typeof onEvent === 'function') onEvent(JSON.parse(tail))
} finally {
try { reader.releaseLock() } catch {}
}
}
// 后端连接状态
let _backendOnline = null // null=未检测, true=在线, false=离线
const _backendListeners = []
export function onBackendStatusChange(fn) {
_backendListeners.push(fn)
return () => { const i = _backendListeners.indexOf(fn); if (i >= 0) _backendListeners.splice(i, 1) }
}
export function isBackendOnline() { return _backendOnline }
function _setBackendOnline(v) {
if (_backendOnline !== v) {
_backendOnline = v
_backendListeners.forEach(fn => { try { fn(v) } catch {} })
}
}
// 后端健康检查
export async function checkBackendHealth() {
if (isTauriRuntime()) { _setBackendOnline(true); return true }
try {
const resp = await fetch('/__api/health', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
const ct = (resp.headers.get('content-type') || '').toLowerCase()
if (!resp.ok || !ct.includes('application/json')) {
_setBackendOnline(false)
return false
}
const data = await resp.json().catch(() => null)
const ok = !!data?.ok
_setBackendOnline(ok)
return ok
} catch {
_setBackendOnline(false)
return false
}
}
// 配置保存后防抖重载 Gateway3 秒内多次写入只触发一次重载)
let _reloadTimer = null
function _debouncedReloadGateway() {
clearTimeout(_reloadTimer)
_reloadTimer = setTimeout(() => { invoke('reload_gateway').catch(() => {}) }, 3000)
}
// 导出 API
export const api = {
// 服务管理(状态用短缓存,操作不缓存)
getServicesStatus: () => cachedInvoke('get_services_status', {}, 10000),
startService: (label) => { invalidate('get_services_status'); return invoke('start_service', { label }) },
stopService: (label) => { invalidate('get_services_status'); return invoke('stop_service', { label }) },
restartService: (label) => { invalidate('get_services_status'); return invoke('restart_service', { label }) },
claimGateway: () => { invalidate('get_services_status'); return invoke('claim_gateway') },
probeGatewayPort: () => invoke('probe_gateway_port'),
diagnoseGatewayConnection: () => invoke('diagnose_gateway_connection'),
guardianStatus: () => invoke('guardian_status'),
checkCiaoWindowsHideBug: () => invoke('check_ciao_windowshide_bug'),
// CLI 冲突检测与隔离PATH 中残留的非 standalone openclaw
scanOpenclawPathConflicts: () => invoke('scan_openclaw_path_conflicts'),
quarantineOpenclawPath: (path) => invoke('quarantine_openclaw_path', { path }),
quarantineOpenclawPathsBulk: (paths) => invoke('quarantine_openclaw_paths_bulk', { paths }),
listQuarantinedOpenclaw: () => invoke('list_quarantined_openclaw'),
restoreQuarantinedOpenclaw: (quarantinedPath) => invoke('restore_quarantined_openclaw', { quarantinedPath }),
// 配置(读缓存,写清缓存)
getVersionInfo: () => cachedInvoke('get_version_info', {}, 30000),
getStatusSummary: () => cachedInvoke('get_status_summary', {}, 60000),
readOpenclawConfig: () => cachedInvoke('read_openclaw_config'),
calibrateOpenclawConfig: (mode = 'inherit') => { invalidate('read_openclaw_config', 'check_installation', 'list_backups', 'get_services_status', 'get_status_summary'); return invoke('calibrate_openclaw_config', { mode }).then(r => { _debouncedReloadGateway(); return r }) },
writeOpenclawConfig: (config, opts = {}) => { invalidate('read_openclaw_config'); return invoke('write_openclaw_config', { config }).then(r => { if (opts.noReload !== true) _debouncedReloadGateway(); return r }) },
readMcpConfig: () => cachedInvoke('read_mcp_config'),
writeMcpConfig: (config) => { invalidate('read_mcp_config'); return invoke('write_mcp_config', { config }) },
reloadGateway: () => invoke('reload_gateway'),
restartGateway: () => invoke('restart_gateway'),
doctorCheck: () => invoke('doctor_check'),
doctorFix: () => invoke('doctor_fix'),
listOpenclawVersions: (source = 'chinese') => invoke('list_openclaw_versions', { source }),
// #Compat-4: 升级/卸载后 CLI 路径/版本/服务状态都可能变,一次性清掉相关前端缓存;
// Rust 端已经在命令内部调用 refresh_enhanced_path + invalidate_cli_detection_cache。
upgradeOpenclaw: (source = 'chinese', version = null, method = 'auto') => {
invalidate('check_installation', 'check_node', 'check_git', 'get_services_status', 'get_status_summary', 'get_version_info')
return invoke('upgrade_openclaw', { source, version, method })
},
uninstallOpenclaw: (cleanConfig = false) => {
invalidate('check_installation', 'check_node', 'check_git', 'get_services_status', 'get_status_summary', 'get_version_info')
return invoke('uninstall_openclaw', { cleanConfig })
},
installGateway: () => { invalidate('get_services_status', 'get_status_summary'); return invoke('install_gateway') },
uninstallGateway: () => { invalidate('get_services_status', 'get_status_summary'); return invoke('uninstall_gateway') },
getNpmRegistry: () => cachedInvoke('get_npm_registry', {}, 30000),
setNpmRegistry: (registry) => { invalidate('get_npm_registry'); return invoke('set_npm_registry', { registry }) },
testModel: (baseUrl, apiKey, modelId, apiType = null) => invoke('test_model', { baseUrl, apiKey, modelId, apiType }),
testModelVerbose: (baseUrl, apiKey, modelId, apiType = null) => invoke('test_model_verbose', { baseUrl, apiKey, modelId, apiType }),
listRemoteModels: (baseUrl, apiKey, apiType = null) => invoke('list_remote_models', { baseUrl, apiKey, apiType }),
scanModelClientConfigs: () => invoke('scan_model_client_configs'),
// Agent 管理
listAgents: () => cachedInvoke('list_agents'),
getAgentDetail: (id) => cachedInvoke('get_agent_detail', { id }, 5000),
listAgentFiles: (id) => cachedInvoke('list_agent_files', { id }, 5000),
readAgentFile: (id, name) => invoke('read_agent_file', { id, name }),
writeAgentFile: (id, name, content) => { invalidate('list_agent_files', 'read_agent_file'); return invoke('write_agent_file', { id, name, content }) },
getAgentWorkspaceInfo: (id) => cachedInvoke('get_agent_workspace_info', { id }, 5000),
listAgentWorkspaceEntries: (id, relativePath) => cachedInvoke('list_agent_workspace_entries', { id, relativePath: relativePath || null }, 5000),
readAgentWorkspaceFile: (id, relativePath) => cachedInvoke('read_agent_workspace_file', { id, relativePath }, 5000),
writeAgentWorkspaceFile: (id, relativePath, content) => {
invalidate('get_agent_workspace_info', 'list_agent_workspace_entries', 'read_agent_workspace_file', 'list_agent_files', 'read_agent_file')
return invoke('write_agent_workspace_file', { id, relativePath, content })
},
updateAgentConfig: (id, config) => { invalidate('list_agents', 'get_agent_detail'); return invoke('update_agent_config', { id, config }) },
addAgent: (name, model, workspace) => { invalidate('list_agents'); return invoke('add_agent', { name, model, workspace: workspace || null }) },
deleteAgent: (id) => { invalidate('list_agents', 'get_agent_detail'); return invoke('delete_agent', { id }) },
updateAgentIdentity: (id, name, emoji) => { invalidate('list_agents', 'get_agent_detail'); return invoke('update_agent_identity', { id, name, emoji }) },
updateAgentModel: (id, model) => { invalidate('list_agents', 'get_agent_detail'); return invoke('update_agent_model', { id, model }) },
backupAgent: (id) => invoke('backup_agent', { id }),
// 日志(短缓存)
readLogTail: (logName, lines = 100) => cachedInvoke('read_log_tail', { logName, lines }, 5000),
searchLog: (logName, query, maxResults = 50) => invoke('search_log', { logName, query, maxResults }),
// 记忆文件
listMemoryFiles: (category, agentId) => cachedInvoke('list_memory_files', { category, agentId: agentId || null }),
readMemoryFile: (path, agentId) => cachedInvoke('read_memory_file', { path, agentId: agentId || null }, 5000),
writeMemoryFile: (path, content, category, agentId) => { invalidate('list_memory_files', 'read_memory_file'); return invoke('write_memory_file', { path, content, category: category || 'memory', agentId: agentId || null }) },
deleteMemoryFile: (path, agentId) => { invalidate('list_memory_files'); return invoke('delete_memory_file', { path, agentId: agentId || null }) },
exportMemoryZip: (category, agentId) => invoke('export_memory_zip', { category, agentId: agentId || null }),
// 消息渠道管理
readPlatformConfig: (platform, accountId) => invoke('read_platform_config', { platform, accountId: accountId || null }),
saveMessagingPlatform: (platform, form, accountId, agentId) => { invalidate('list_configured_platforms', 'read_openclaw_config', 'read_platform_config'); return invoke('save_messaging_platform', { platform, form, accountId: accountId || null, agentId: agentId || null }) },
removeMessagingPlatform: (platform, accountId) => { invalidate('list_configured_platforms', 'read_openclaw_config', 'read_platform_config'); return invoke('remove_messaging_platform', { platform, accountId: accountId || null }) },
toggleMessagingPlatform: (platform, enabled) => { invalidate('list_configured_platforms', 'read_openclaw_config', 'read_platform_config'); return invoke('toggle_messaging_platform', { platform, enabled }) },
verifyBotToken: (platform, form) => invoke('verify_bot_token', { platform, form }),
diagnoseChannel: (platform, accountId) => invoke('diagnose_channel', { platform, accountId: accountId || null }),
repairQqbotChannelSetup: () => {
invalidate('list_configured_platforms', 'read_openclaw_config', 'read_platform_config')
return invoke('repair_qqbot_channel_setup')
},
listConfiguredPlatforms: () => cachedInvoke('list_configured_platforms', {}, 5000),
listAllPlugins: () => cachedInvoke('list_all_plugins', {}, 5000),
togglePlugin: (pluginId, enabled) => { invalidate('list_all_plugins'); return invoke('toggle_plugin', { pluginId, enabled }) },
installPlugin: (packageName) => { invalidate('list_all_plugins'); return invoke('install_plugin', { packageName }) },
getChannelPluginStatus: (pluginId) => invoke('get_channel_plugin_status', { pluginId }),
installQqbotPlugin: (version = null) => invoke('install_qqbot_plugin', { version }),
installChannelPlugin: (packageName, pluginId, version = null) => invoke('install_channel_plugin', { packageName, pluginId, version }),
runChannelAction: (platform, action, version = null) => invoke('run_channel_action', { platform, action, version }),
checkWeixinPluginStatus: () => invoke('check_weixin_plugin_status'),
// Agent 渠道绑定管理
getAgentBindings: (agentId) => invoke('get_agent_bindings', { agentId }),
listAllBindings: () => invoke('list_all_bindings'),
saveAgentBinding: (agentId, channel, accountId, bindingConfig) => { invalidate('read_openclaw_config', 'list_configured_platforms'); return invoke('save_agent_binding', { agentId, channel, accountId: accountId || null, bindingConfig: bindingConfig || {} }) },
deleteAgentBinding: (agentId, channel, accountId, bindingConfig) => { invalidate('read_openclaw_config', 'list_configured_platforms'); return invoke('delete_agent_binding', { agentId, channel, accountId: accountId || null, bindingConfig: bindingConfig || null }) },
deleteAgentAllBindings: (agentId) => { invalidate('read_openclaw_config', 'list_configured_platforms'); return invoke('delete_agent_all_bindings', { agentId }) },
// 面板配置 (clawpanel.json)
getOpenclawDir: () => invoke('get_openclaw_dir'),
// Tauri: 重启应用进程Web: 没有应用进程概念,刷新浏览器即可拿到新状态
relaunchApp: () => {
if (!isTauriRuntime()) {
try { window.location.reload() } catch {}
return Promise.resolve({ ok: true, mode: 'web-reload' })
}
return invoke('relaunch_app')
},
readPanelConfig: () => invoke('read_panel_config'),
writePanelConfig: (config) => { invalidate(); return invoke('write_panel_config', { config }).then(r => { invoke('invalidate_path_cache').catch(() => {}); return r }) },
testProxy: (url) => invoke('test_proxy', { url: url || null }),
// 安装/部署
checkInstallation: () => cachedInvoke('check_installation', {}, 60000),
initOpenclawConfig: () => { invalidate('check_installation'); return invoke('init_openclaw_config') },
checkNode: () => cachedInvoke('check_node', {}, 60000),
checkNodeAtPath: (nodeDir) => invoke('check_node_at_path', { nodeDir }),
checkOpenclawAtPath: (cliPath) => invoke('check_openclaw_at_path', { cliPath }),
scanNodePaths: () => invoke('scan_node_paths'),
scanOpenclawPaths: () => invoke('scan_openclaw_paths'),
saveCustomNodePath: (nodeDir) => invoke('save_custom_node_path', { nodeDir }).then(r => { invalidate('check_node', 'get_services_status'); invoke('invalidate_path_cache').catch(() => {}); return r }),
invalidatePathCache: () => invoke('invalidate_path_cache'),
checkGit: () => cachedInvoke('check_git', {}, 60000),
scanGitPaths: () => invoke('scan_git_paths'),
autoInstallGit: () => invoke('auto_install_git'),
configureGitHttps: () => invoke('configure_git_https'),
getDeployConfig: () => cachedInvoke('get_deploy_config'),
patchModelVision: () => invoke('patch_model_vision'),
checkPanelUpdate: () => invoke('check_panel_update'),
writeEnvFile: (path, config) => invoke('write_env_file', { path, config }),
// 备份管理
listBackups: () => cachedInvoke('list_backups'),
createBackup: () => { invalidate('list_backups'); return invoke('create_backup') },
restoreBackup: (name) => invoke('restore_backup', { name }),
deleteBackup: (name) => { invalidate('list_backups'); return invoke('delete_backup', { name }) },
// 设备密钥 + Gateway 握手
createConnectFrame: (nonce, gatewayToken, gatewayPassword) => invoke('create_connect_frame', { nonce, gatewayToken, gatewayPassword: gatewayPassword || null }),
// 设备配对
autoPairDevice: () => invoke('auto_pair_device'),
checkPairingStatus: () => invoke('check_pairing_status'),
pairingListChannel: (channel) => invoke('pairing_list_channel', { channel }),
pairingApproveChannel: (channel, code, notify = false) => invoke('pairing_approve_channel', { channel, code, notify }),
// AI 助手工具
assistantExec: (command, cwd) => invoke('assistant_exec', { command, cwd: cwd || null }),
assistantReadFile: (path) => invoke('assistant_read_file', { path }),
assistantWriteFile: (path, content) => invoke('assistant_write_file', { path, content }),
assistantListDir: (path) => invoke('assistant_list_dir', { path }),
assistantSystemInfo: () => invoke('assistant_system_info'),
assistantListProcesses: (filter) => invoke('assistant_list_processes', { filter: filter || null }),
assistantCheckPort: (port) => invoke('assistant_check_port', { port }),
assistantWebSearch: (query, maxResults) => invoke('assistant_web_search', { query, max_results: maxResults || 5 }),
assistantFetchUrl: (url) => invoke('assistant_fetch_url', { url }),
// Skills 管理
skillsList: (agentId) => invoke('skills_list', { agent_id: agentId || null }),
skillsInfo: (name, agentId) => invoke('skills_info', { name, agent_id: agentId || null }),
skillsCheck: () => invoke('skills_check'),
skillsInstallDep: (kind, spec) => invoke('skills_install_dep', { kind, spec }),
skillsUninstall: (name, agentId) => invoke('skills_uninstall', { name, agent_id: agentId || null }),
// SkillHub SDK内置 HTTP不依赖 CLI
skillhubSearch: (query, limit) => invoke('skillhub_search', { query, limit }),
skillhubIndex: () => invoke('skillhub_index'),
skillhubInstall: (slug, agentId) => invoke('skillhub_install', { slug, agent_id: agentId || null }),
// 实例管理
instanceList: () => cachedInvoke('instance_list', {}, 10000),
instanceAdd: (instance) => { invalidate('instance_list'); return invoke('instance_add', instance) },
instanceRemove: (id) => { invalidate('instance_list'); return invoke('instance_remove', { id }) },
instanceSetActive: (id) => { invalidate('instance_list'); _cache.clear(); return invoke('instance_set_active', { id }) },
instanceHealthCheck: (id) => invoke('instance_health_check', { id }),
instanceHealthAll: () => invoke('instance_health_all'),
// Docker 管理(当前由 Web/dev-api 提供)
dockerInfo: (nodeId) => invoke('docker_info', { nodeId: nodeId || null }),
dockerListContainers: (nodeId, all = true) => invoke('docker_list_containers', { nodeId: nodeId || null, all }),
dockerCreateContainer: (payload) => invoke('docker_create_container', payload || {}),
dockerStartContainer: (nodeId, containerId) => invoke('docker_start_container', { nodeId: nodeId || null, containerId }),
dockerStopContainer: (nodeId, containerId) => invoke('docker_stop_container', { nodeId: nodeId || null, containerId }),
dockerRestartContainer: (nodeId, containerId) => invoke('docker_restart_container', { nodeId: nodeId || null, containerId }),
dockerRemoveContainer: (nodeId, containerId, force = false) => invoke('docker_remove_container', { nodeId: nodeId || null, containerId, force }),
dockerPullImage: (payload) => invoke('docker_pull_image', payload || {}),
dockerPullStatus: (requestId) => invoke('docker_pull_status', { requestId }),
dockerListImages: (nodeId) => invoke('docker_list_images', { nodeId: nodeId || null }),
dockerListNodes: () => invoke('docker_list_nodes', {}),
dockerAddNode: (name, endpoint) => invoke('docker_add_node', { name, endpoint }),
dockerRemoveNode: (nodeId) => invoke('docker_remove_node', { nodeId }),
dockerClusterOverview: () => invoke('docker_cluster_overview', {}),
// 前端热更新
checkFrontendUpdate: () => invoke('check_frontend_update'),
downloadFrontendUpdate: (url, expectedHash, version) => invoke('download_frontend_update', { url, expectedHash: expectedHash || '', version: version || '' }),
rollbackFrontendUpdate: () => invoke('rollback_frontend_update'),
getUpdateStatus: () => invoke('get_update_status'),
// 数据目录 & 图片存储
ensureDataDir: () => invoke('assistant_ensure_data_dir'),
saveImage: (id, data) => invoke('assistant_save_image', { id, data }),
loadImage: (id) => invoke('assistant_load_image', { id }),
deleteImage: (id) => invoke('assistant_delete_image', { id }),
// Hermes Agent 管理
checkPython: () => cachedInvoke('check_python', {}, 60000),
checkHermes: () => cachedInvoke('check_hermes', {}, 30000),
installHermes: (method = 'uv-tool', extras = []) => invoke('install_hermes', { method, extras }),
configureHermes: (provider, apiKey, model, baseUrl) => invoke('configure_hermes', { provider, apiKey, model: model || null, baseUrl: baseUrl || null }),
hermesGatewayAction: (action) => invoke('hermes_gateway_action', { action }),
hermesHealthCheck: () => invoke('hermes_health_check'),
hermesCapabilities: () => invoke('hermes_capabilities'),
hermesApiProxy: (method, path, body, headers) => invoke('hermes_api_proxy', { method, path, body: body || null, headers: headers || null }),
hermesAgentRun: (input, sessionId, conversationHistory, instructions, attachments) => invoke('hermes_agent_run', { input, sessionId: sessionId || null, conversationHistory: conversationHistory || null, instructions: instructions || null, attachments: attachments && attachments.length ? attachments : null }),
hermesAgentRunStream: (input, sessionId, conversationHistory, instructions, onEvent, options) => webStreamInvoke('hermes_agent_run_stream', { input, sessionId: sessionId || null, conversationHistory: conversationHistory || null, instructions: instructions || null }, onEvent, options),
// Batch 1 §D + §C-bis: 真正中断 + Approval Flow用 run_id
hermesRunStop: (runId) => invoke('hermes_run_stop', { runId }),
hermesRunApproval: (runId, choice) => invoke('hermes_run_approval', { runId, choice }),
// Batch 2 §I: 流恢复 — 查 run 状态
hermesRunStatus: (runId) => invoke('hermes_run_status', { runId }),
// Batch 1 §E: 会话消息导出(走 dashboard /api/sessions/{id}/messages
hermesSessionExport: (sessionId) => invoke('hermes_session_export', { sessionId }),
// Batch 2 §H 基础设施: 通用 Dashboard 9119 HTTP 代理
hermesDashboardApi: (method, path, body, headers) => invoke('hermes_dashboard_api_proxy', {
method, path,
body: body == null ? null : (typeof body === 'string' ? body : JSON.stringify(body)),
headers: headers || null,
}),
// Batch 2 §G: 多 Gateway 看板
hermesMultiGatewayList: () => invoke('hermes_multi_gateway_list'),
hermesMultiGatewayAdd: (name, profile) => invoke('hermes_multi_gateway_add', { name, profile }),
hermesMultiGatewayRemove: (name) => invoke('hermes_multi_gateway_remove', { name }),
hermesMultiGatewayStart: (name) => invoke('hermes_multi_gateway_start', { name }),
hermesMultiGatewayStop: (name) => invoke('hermes_multi_gateway_stop', { name }),
// Batch 3 §L: 文件管理器(限定在 hermes_home 子树内)
hermesFsList: (path = '') => invoke('hermes_fs_list', { path }),
hermesFsRead: (path) => invoke('hermes_fs_read', { path }),
hermesFsWrite: (path, content) => invoke('hermes_fs_write', { path, content }),
hermesReadConfig: () => invoke('hermes_read_config'),
hermesReadConfigFull: () => invoke('hermes_read_config_full'),
hermesChannelConfigRead: () => invoke('hermes_channel_config_read'),
hermesChannelConfigSave: (platform, form) => invoke('hermes_channel_config_save', { platform, form }),
hermesSessionRuntimeConfigRead: () => invoke('hermes_session_runtime_config_read'),
hermesSessionRuntimeConfigSave: (form) => invoke('hermes_session_runtime_config_save', { form }),
hermesCompressionConfigRead: () => invoke('hermes_compression_config_read'),
hermesCompressionConfigSave: (form) => invoke('hermes_compression_config_save', { form }),
hermesPromptCachingConfigRead: () => invoke('hermes_prompt_caching_config_read'),
hermesPromptCachingConfigSave: (form) => invoke('hermes_prompt_caching_config_save', { form }),
hermesOpenrouterCacheConfigRead: () => invoke('hermes_openrouter_cache_config_read'),
hermesOpenrouterCacheConfigSave: (form) => invoke('hermes_openrouter_cache_config_save', { form }),
hermesProviderRoutingConfigRead: () => invoke('hermes_provider_routing_config_read'),
hermesProviderRoutingConfigSave: (form) => invoke('hermes_provider_routing_config_save', { form }),
hermesAuxiliaryConfigRead: () => invoke('hermes_auxiliary_config_read'),
hermesAuxiliaryConfigSave: (form) => invoke('hermes_auxiliary_config_save', { form }),
hermesToolLoopGuardrailsConfigRead: () => invoke('hermes_tool_loop_guardrails_config_read'),
hermesToolLoopGuardrailsConfigSave: (form) => invoke('hermes_tool_loop_guardrails_config_save', { form }),
hermesMemoryConfigRead: () => invoke('hermes_memory_config_read'),
hermesMemoryConfigSave: (form) => invoke('hermes_memory_config_save', { form }),
hermesSkillsConfigRead: () => invoke('hermes_skills_config_read'),
hermesSkillsConfigSave: (form) => invoke('hermes_skills_config_save', { form }),
hermesCuratorConfigRead: () => invoke('hermes_curator_config_read'),
hermesCuratorConfigSave: (form) => invoke('hermes_curator_config_save', { form }),
hermesQuickCommandsConfigRead: () => invoke('hermes_quick_commands_config_read'),
hermesQuickCommandsConfigSave: (form) => invoke('hermes_quick_commands_config_save', { form }),
hermesModelConfigRead: () => invoke('hermes_model_config_read'),
hermesModelConfigSave: (form) => invoke('hermes_model_config_save', { form }),
hermesModelAliasesConfigRead: () => invoke('hermes_model_aliases_config_read'),
hermesModelAliasesConfigSave: (form) => invoke('hermes_model_aliases_config_save', { form }),
hermesHooksConfigRead: () => invoke('hermes_hooks_config_read'),
hermesHooksConfigSave: (form) => invoke('hermes_hooks_config_save', { form }),
hermesProviderOverridesConfigRead: () => invoke('hermes_provider_overrides_config_read'),
hermesProviderOverridesConfigSave: (form) => invoke('hermes_provider_overrides_config_save', { form }),
hermesMcpServersConfigRead: () => invoke('hermes_mcp_servers_config_read'),
hermesMcpServersConfigSave: (form) => invoke('hermes_mcp_servers_config_save', { form }),
hermesAgentToolsetsConfigRead: () => invoke('hermes_agent_toolsets_config_read'),
hermesAgentToolsetsConfigSave: (form) => invoke('hermes_agent_toolsets_config_save', { form }),
hermesPlatformToolsetsConfigRead: () => invoke('hermes_platform_toolsets_config_read'),
hermesPlatformToolsetsConfigSave: (form) => invoke('hermes_platform_toolsets_config_save', { form }),
hermesAgentRuntimeConfigRead: () => invoke('hermes_agent_runtime_config_read'),
hermesAgentRuntimeConfigSave: (form) => invoke('hermes_agent_runtime_config_save', { form }),
hermesUnauthorizedDmConfigRead: () => invoke('hermes_unauthorized_dm_config_read'),
hermesUnauthorizedDmConfigSave: (form) => invoke('hermes_unauthorized_dm_config_save', { form }),
hermesSecurityConfigRead: () => invoke('hermes_security_config_read'),
hermesSecurityConfigSave: (form) => invoke('hermes_security_config_save', { form }),
hermesDisplayConfigRead: () => invoke('hermes_display_config_read'),
hermesDisplayConfigSave: (form) => invoke('hermes_display_config_save', { form }),
hermesKanbanConfigRead: () => invoke('hermes_kanban_config_read'),
hermesKanbanConfigSave: (form) => invoke('hermes_kanban_config_save', { form }),
hermesHumanDelayConfigRead: () => invoke('hermes_human_delay_config_read'),
hermesHumanDelayConfigSave: (form) => invoke('hermes_human_delay_config_save', { form }),
hermesStreamingConfigRead: () => invoke('hermes_streaming_config_read'),
hermesStreamingConfigSave: (form) => invoke('hermes_streaming_config_save', { form }),
hermesExecutionLimitsConfigRead: () => invoke('hermes_execution_limits_config_read'),
hermesExecutionLimitsConfigSave: (form) => invoke('hermes_execution_limits_config_save', { form }),
hermesIoSafetyConfigRead: () => invoke('hermes_io_safety_config_read'),
hermesIoSafetyConfigSave: (form) => invoke('hermes_io_safety_config_save', { form }),
hermesCheckpointsConfigRead: () => invoke('hermes_checkpoints_config_read'),
hermesCheckpointsConfigSave: (form) => invoke('hermes_checkpoints_config_save', { form }),
hermesCronConfigRead: () => invoke('hermes_cron_config_read'),
hermesCronConfigSave: (form) => invoke('hermes_cron_config_save', { form }),
hermesSessionsMaintenanceConfigRead: () => invoke('hermes_sessions_maintenance_config_read'),
hermesSessionsMaintenanceConfigSave: (form) => invoke('hermes_sessions_maintenance_config_save', { form }),
hermesLoggingConfigRead: () => invoke('hermes_logging_config_read'),
hermesLoggingConfigSave: (form) => invoke('hermes_logging_config_save', { form }),
hermesApprovalsConfigRead: () => invoke('hermes_approvals_config_read'),
hermesApprovalsConfigSave: (form) => invoke('hermes_approvals_config_save', { form }),
hermesPrivacyConfigRead: () => invoke('hermes_privacy_config_read'),
hermesPrivacyConfigSave: (form) => invoke('hermes_privacy_config_save', { form }),
hermesBrowserConfigRead: () => invoke('hermes_browser_config_read'),
hermesBrowserConfigSave: (form) => invoke('hermes_browser_config_save', { form }),
hermesSttConfigRead: () => invoke('hermes_stt_config_read'),
hermesSttConfigSave: (form) => invoke('hermes_stt_config_save', { form }),
hermesTerminalConfigRead: () => invoke('hermes_terminal_config_read'),
hermesTerminalConfigSave: (form) => invoke('hermes_terminal_config_save', { form }),
hermesLazyDepsFeatures: () => cachedInvoke('hermes_lazy_deps_features', {}, 600000),
hermesLazyDepsStatus: (features) => invoke('hermes_lazy_deps_status', { features }),
hermesLazyDepsEnsure: (feature) => invoke('hermes_lazy_deps_ensure', { feature }),
hermesFetchModels: (baseUrl, apiKey, apiType, provider) => invoke('hermes_fetch_models', { baseUrl, apiKey, apiType: apiType || null, provider: provider || null }),
hermesUpdateModel: (model, provider) => invoke('hermes_update_model', { model, provider: provider || null }),
hermesListProviders: () => cachedInvoke('hermes_list_providers', {}, 600000),
hermesEnvReadUnmanaged: () => invoke('hermes_env_read_unmanaged'),
hermesEnvSet: (key, value) => invoke('hermes_env_set', { key, value }),
hermesEnvDelete: (key) => invoke('hermes_env_delete', { key }),
hermesEnvReveal: (key) => invoke('hermes_env_reveal', { key }),
hermesConfigRawRead: () => invoke('hermes_config_raw_read'),
hermesConfigRawWrite: (yamlText) => invoke('hermes_config_raw_write', { yamlText }),
hermesDetectEnvironments: () => invoke('hermes_detect_environments'),
hermesSetGatewayUrl: (url) => invoke('hermes_set_gateway_url', { url: url || null }),
updateHermes: () => invoke('update_hermes'),
uninstallHermes: (cleanConfig = false) => invoke('uninstall_hermes', { cleanConfig }),
// Hermes Sessions / Logs / Skills / Memory
hermesSessionsList: (source, limit, profile) => invoke('hermes_sessions_list', { source: source || null, limit: limit || null, profile: profile || null }),
hermesSessionsSummaryList: (source, limit, profile) => invoke('hermes_sessions_summary_list', { source: source || null, limit: limit || null, profile: profile || null }),
hermesUsageAnalytics: (days, profile) => invoke('hermes_usage_analytics', { days: days || 30, profile: profile || null }),
hermesSessionDetail: (sessionId, profile) => invoke('hermes_session_detail', { sessionId, profile: profile || null }),
hermesSessionDelete: (sessionId, profile) => invoke('hermes_session_delete', { sessionId, profile: profile || null }),
hermesSessionRename: (sessionId, title, profile) => invoke('hermes_session_rename', { sessionId, title, profile: profile || null }),
hermesProfilesList: () => invoke('hermes_profiles_list'),
hermesProfileUse: (name) => invoke('hermes_profile_use', { name }),
hermesLogsList: () => invoke('hermes_logs_list'),
hermesLogsRead: (name, lines, level) => invoke('hermes_logs_read', { name, lines: lines || 200, level: level || null }),
hermesLogsDownload: (name, saveToDisk = isTauriRuntime()) => invoke('hermes_logs_download', { name, saveToDisk }),
hermesDashboardThemes: () => invoke('hermes_dashboard_themes'),
hermesDashboardThemeSet: (name) => invoke('hermes_dashboard_theme_set', { name }),
hermesDashboardPlugins: () => invoke('hermes_dashboard_plugins'),
hermesDashboardPluginsRescan: () => invoke('hermes_dashboard_plugins_rescan'),
hermesDashboardProbe: () => invoke('hermes_dashboard_probe'),
hermesDashboardStart: () => invoke('hermes_dashboard_start'),
hermesDashboardStop: () => invoke('hermes_dashboard_stop'),
hermesToolsetsList: () => invoke('hermes_toolsets_list'),
hermesCronJobsList: () => invoke('hermes_cron_jobs_list'),
hermesSkillsList: () => invoke('hermes_skills_list'),
hermesSkillDetail: (filePath) => invoke('hermes_skill_detail', { filePath }),
hermesSkillToggle: (name, enabled) => invoke('hermes_skill_toggle', { name, enabled }),
hermesSkillFiles: (category, skill) => invoke('hermes_skill_files', { category, skill }),
hermesSkillWrite: (filePath, content) => invoke('hermes_skill_write', { filePath, content }),
hermesMemoryRead: (type) => invoke('hermes_memory_read', { type: type || 'memory' }),
hermesMemoryWrite: (type, content) => invoke('hermes_memory_write', { type: type || 'memory', content }),
hermesMemoryReadAll: () => invoke('hermes_memory_read_all'),
}