mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 04:10:00 +08:00
631 lines
37 KiB
JavaScript
631 lines
37 KiB
JavaScript
/**
|
||
* Tauri API 封装层
|
||
* Tauri 环境用 invoke,Web 模式走 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
|
||
}
|
||
}
|
||
|
||
// 配置保存后防抖重载 Gateway(3 秒内多次写入只触发一次重载)
|
||
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'),
|
||
}
|