From 8a6c55d94ae06edd7cbb3c8c942dd72e3af84d71 Mon Sep 17 00:00:00 2001 From: toot <[email protected]> Date: Wed, 16 Sep 2026 12:24:49 +0800 Subject: [PATCH] chore: initial commit (sources + docs, ignore build artifacts and secrets) --- .gitignore | 68 + controller/.gitignore | 44 + controller/README.md | 22 + controller/app.go | 280 +++ controller/build.bat | 3 + controller/build.ps1 | 19 + controller/config/controller-config.json | 5 + controller/docs/BLUEPRINT.md | 126 ++ controller/docs/CONFIGURATION.md | 19 + controller/docs/STATUS.md | 57 + .../docs/steps/01-项目骨架与运行基线.md | 9 + .../docs/steps/02-共享协议与安全边界.md | 9 + controller/docs/steps/03-存储与领域模型.md | 9 + controller/docs/steps/04-通信与Agent注册.md | 9 + controller/docs/steps/05-任务调度与编排.md | 9 + controller/docs/steps/06-Wails2管理界面.md | 9 + controller/docs/steps/07-迁移兼容与可靠性.md | 9 + controller/docs/steps/08-测试发布与运维.md | 9 + controller/docs/steps/09-单Agent任务闭环.md | 26 + .../docs/steps/10-Wails2迁移与信息架构.md | 20 + .../docs/steps/11-Agent管理与配对生命周期.md | 26 + .../docs/steps/12-任务取消超时与重试.md | 39 + .../docs/steps/13-集中配置与远程运维.md | 10 + .../docs/steps/14-MCP受控桌面工具闭环.md | 19 + controller/docs/steps/15-Agent规模化管理.md | 19 + controller/docs/steps/16-审计日志基础设施.md | 14 + .../docs/steps/17-多Agent并发任务中心.md | 25 + controller/docs/steps/18-自然多Agent指派.md | 27 + controller/docs/steps/19-图片附件任务闭环.md | 13 + controller/docs/steps/20-默认完全访问Agent.md | 15 + controller/docs/steps/README.md | 23 + controller/frontend/index.html | 13 + controller/frontend/package-lock.json | 1883 +++++++++++++++++ controller/frontend/package.json | 22 + controller/frontend/package.json.md5 | 1 + controller/frontend/src/App.css | 29 + controller/frontend/src/App.tsx | 573 +++++ controller/frontend/src/assets/fonts/OFL.txt | 93 + .../fonts/nunito-v16-latin-regular.woff2 | Bin 0 -> 18972 bytes .../src/assets/images/logo-universal.png | Bin 0 -> 139695 bytes controller/frontend/src/main.tsx | 14 + controller/frontend/src/style.css | 2 + controller/frontend/src/vite-env.d.ts | 1 + controller/frontend/tsconfig.json | 31 + controller/frontend/tsconfig.node.json | 11 + controller/frontend/vite.config.ts | 7 + controller/frontend/wailsjs/go/main/App.d.ts | 67 + controller/frontend/wailsjs/go/main/App.js | 131 ++ controller/frontend/wailsjs/go/models.ts | 694 ++++++ .../frontend/wailsjs/runtime/package.json | 24 + .../frontend/wailsjs/runtime/runtime.d.ts | 330 +++ .../frontend/wailsjs/runtime/runtime.js | 298 +++ controller/go.mod | 48 + controller/go.sum | 125 ++ .../internal/controlplane/agent_directory.go | 260 +++ .../internal/controlplane/attachments.go | 99 + controller/internal/controlplane/audit.go | 221 ++ .../internal/controlplane/controlplane.go | 1140 ++++++++++ .../controlplane/controlplane_test.go | 608 ++++++ controller/internal/controlplane/llm.go | 226 ++ controller/internal/controlplane/mcp.go | 99 + controller/internal/controlplane/mentions.go | 268 +++ .../controlplane/secret_store_windows.go | 48 + controller/internal/controlplane/skills.go | 240 +++ .../internal/controlplane/skills_test.go | 43 + controller/main.go | 37 + controller/wails.json | 13 + mulit_agent/.gitignore | 57 + mulit_agent/MultiClawAgent.spec | 48 + mulit_agent/README.md | 22 + mulit_agent/app.py | 10 + mulit_agent/build.bat | 3 + mulit_agent/build.ps1 | 62 + mulit_agent/config/agent-config.example.json | 7 + mulit_agent/docs/BLUEPRINT.md | 122 ++ mulit_agent/docs/CONFIGURATION.md | 23 + mulit_agent/docs/STATUS.md | 39 + .../docs/steps/01-项目骨架与运行模式.md | 9 + mulit_agent/docs/steps/02-协议会话与安全.md | 12 + .../docs/steps/03-本地状态与任务生命周期.md | 9 + .../docs/steps/04-模型执行与流式结果.md | 9 + .../docs/steps/05-技能插件工具与隔离.md | 9 + mulit_agent/docs/steps/06-PySide6运行界面.md | 12 + .../docs/steps/07-兼容迁移与故障恢复.md | 9 + mulit_agent/docs/steps/08-测试打包与交付.md | 9 + mulit_agent/docs/steps/09-单Agent任务闭环.md | 28 + .../docs/steps/10-任务取消超时与重试.md | 28 + .../docs/steps/11-OpenAI兼容LLM配置与执行.md | 15 + .../docs/steps/12-总控配置应用与守护服务.md | 9 + mulit_agent/docs/steps/13-图片附件模型闭环.md | 9 + .../docs/steps/14-默认完全访问执行器.md | 18 + mulit_agent/docs/steps/README.md | 17 + mulit_agent/pyproject.toml | 39 + mulit_agent/src/mulit_agent_app/__init__.py | 3 + .../mulit_agent_app/application/__init__.py | 1 + .../application/agent_service.py | 223 ++ .../application/task_executor.py | 259 +++ mulit_agent/src/mulit_agent_app/config.py | 137 ++ .../src/mulit_agent_app/domain/__init__.py | 1 + .../src/mulit_agent_app/domain/attachments.py | 21 + .../src/mulit_agent_app/domain/models.py | 11 + mulit_agent/src/mulit_agent_app/events.py | 23 + .../infrastructure/__init__.py | 1 + .../infrastructure/controller_session.py | 371 ++++ .../infrastructure/credential_store.py | 36 + .../infrastructure/database.py | 160 ++ .../infrastructure/desktop_filesystem_tool.py | 135 ++ .../infrastructure/full_access_tool.py | 115 + .../infrastructure/logging_setup.py | 15 + .../openai_compatible_provider.py | 146 ++ .../infrastructure/provider_factory.py | 202 ++ mulit_agent/src/mulit_agent_app/main.py | 43 + .../mulit_agent_app/presentation/__init__.py | 1 + .../presentation/connect_window.py | 109 + .../src/mulit_agent_app/presentation/tray.py | 64 + mulit_agent/tests/test_agent_service_mcp.py | 70 + .../tests/test_agent_service_skills.py | 56 + mulit_agent/tests/test_config.py | 67 + mulit_agent/tests/test_controller_session.py | 46 + .../tests/test_desktop_filesystem_tool.py | 64 + mulit_agent/tests/test_full_access_tool.py | 34 + mulit_agent/tests/test_image_attachments.py | 94 + .../tests/test_openai_compatible_provider.py | 107 + mulit_agent/tests/test_task_executor.py | 229 ++ 124 files changed, 12168 insertions(+) create mode 100644 .gitignore create mode 100644 controller/.gitignore create mode 100644 controller/README.md create mode 100644 controller/app.go create mode 100644 controller/build.bat create mode 100644 controller/build.ps1 create mode 100644 controller/config/controller-config.json create mode 100644 controller/docs/BLUEPRINT.md create mode 100644 controller/docs/CONFIGURATION.md create mode 100644 controller/docs/STATUS.md create mode 100644 controller/docs/steps/01-项目骨架与运行基线.md create mode 100644 controller/docs/steps/02-共享协议与安全边界.md create mode 100644 controller/docs/steps/03-存储与领域模型.md create mode 100644 controller/docs/steps/04-通信与Agent注册.md create mode 100644 controller/docs/steps/05-任务调度与编排.md create mode 100644 controller/docs/steps/06-Wails2管理界面.md create mode 100644 controller/docs/steps/07-迁移兼容与可靠性.md create mode 100644 controller/docs/steps/08-测试发布与运维.md create mode 100644 controller/docs/steps/09-单Agent任务闭环.md create mode 100644 controller/docs/steps/10-Wails2迁移与信息架构.md create mode 100644 controller/docs/steps/11-Agent管理与配对生命周期.md create mode 100644 controller/docs/steps/12-任务取消超时与重试.md create mode 100644 controller/docs/steps/13-集中配置与远程运维.md create mode 100644 controller/docs/steps/14-MCP受控桌面工具闭环.md create mode 100644 controller/docs/steps/15-Agent规模化管理.md create mode 100644 controller/docs/steps/16-审计日志基础设施.md create mode 100644 controller/docs/steps/17-多Agent并发任务中心.md create mode 100644 controller/docs/steps/18-自然多Agent指派.md create mode 100644 controller/docs/steps/19-图片附件任务闭环.md create mode 100644 controller/docs/steps/20-默认完全访问Agent.md create mode 100644 controller/docs/steps/README.md create mode 100644 controller/frontend/index.html create mode 100644 controller/frontend/package-lock.json create mode 100644 controller/frontend/package.json create mode 100644 controller/frontend/package.json.md5 create mode 100644 controller/frontend/src/App.css create mode 100644 controller/frontend/src/App.tsx create mode 100644 controller/frontend/src/assets/fonts/OFL.txt create mode 100644 controller/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 create mode 100644 controller/frontend/src/assets/images/logo-universal.png create mode 100644 controller/frontend/src/main.tsx create mode 100644 controller/frontend/src/style.css create mode 100644 controller/frontend/src/vite-env.d.ts create mode 100644 controller/frontend/tsconfig.json create mode 100644 controller/frontend/tsconfig.node.json create mode 100644 controller/frontend/vite.config.ts create mode 100644 controller/frontend/wailsjs/go/main/App.d.ts create mode 100644 controller/frontend/wailsjs/go/main/App.js create mode 100644 controller/frontend/wailsjs/go/models.ts create mode 100644 controller/frontend/wailsjs/runtime/package.json create mode 100644 controller/frontend/wailsjs/runtime/runtime.d.ts create mode 100644 controller/frontend/wailsjs/runtime/runtime.js create mode 100644 controller/go.mod create mode 100644 controller/go.sum create mode 100644 controller/internal/controlplane/agent_directory.go create mode 100644 controller/internal/controlplane/attachments.go create mode 100644 controller/internal/controlplane/audit.go create mode 100644 controller/internal/controlplane/controlplane.go create mode 100644 controller/internal/controlplane/controlplane_test.go create mode 100644 controller/internal/controlplane/llm.go create mode 100644 controller/internal/controlplane/mcp.go create mode 100644 controller/internal/controlplane/mentions.go create mode 100644 controller/internal/controlplane/secret_store_windows.go create mode 100644 controller/internal/controlplane/skills.go create mode 100644 controller/internal/controlplane/skills_test.go create mode 100644 controller/main.go create mode 100644 controller/wails.json create mode 100644 mulit_agent/.gitignore create mode 100644 mulit_agent/MultiClawAgent.spec create mode 100644 mulit_agent/README.md create mode 100644 mulit_agent/app.py create mode 100644 mulit_agent/build.bat create mode 100644 mulit_agent/build.ps1 create mode 100644 mulit_agent/config/agent-config.example.json create mode 100644 mulit_agent/docs/BLUEPRINT.md create mode 100644 mulit_agent/docs/CONFIGURATION.md create mode 100644 mulit_agent/docs/STATUS.md create mode 100644 mulit_agent/docs/steps/01-项目骨架与运行模式.md create mode 100644 mulit_agent/docs/steps/02-协议会话与安全.md create mode 100644 mulit_agent/docs/steps/03-本地状态与任务生命周期.md create mode 100644 mulit_agent/docs/steps/04-模型执行与流式结果.md create mode 100644 mulit_agent/docs/steps/05-技能插件工具与隔离.md create mode 100644 mulit_agent/docs/steps/06-PySide6运行界面.md create mode 100644 mulit_agent/docs/steps/07-兼容迁移与故障恢复.md create mode 100644 mulit_agent/docs/steps/08-测试打包与交付.md create mode 100644 mulit_agent/docs/steps/09-单Agent任务闭环.md create mode 100644 mulit_agent/docs/steps/10-任务取消超时与重试.md create mode 100644 mulit_agent/docs/steps/11-OpenAI兼容LLM配置与执行.md create mode 100644 mulit_agent/docs/steps/12-总控配置应用与守护服务.md create mode 100644 mulit_agent/docs/steps/13-图片附件模型闭环.md create mode 100644 mulit_agent/docs/steps/14-默认完全访问执行器.md create mode 100644 mulit_agent/docs/steps/README.md create mode 100644 mulit_agent/pyproject.toml create mode 100644 mulit_agent/src/mulit_agent_app/__init__.py create mode 100644 mulit_agent/src/mulit_agent_app/application/__init__.py create mode 100644 mulit_agent/src/mulit_agent_app/application/agent_service.py create mode 100644 mulit_agent/src/mulit_agent_app/application/task_executor.py create mode 100644 mulit_agent/src/mulit_agent_app/config.py create mode 100644 mulit_agent/src/mulit_agent_app/domain/__init__.py create mode 100644 mulit_agent/src/mulit_agent_app/domain/attachments.py create mode 100644 mulit_agent/src/mulit_agent_app/domain/models.py create mode 100644 mulit_agent/src/mulit_agent_app/events.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/__init__.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/controller_session.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/credential_store.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/database.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/desktop_filesystem_tool.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/full_access_tool.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/logging_setup.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/openai_compatible_provider.py create mode 100644 mulit_agent/src/mulit_agent_app/infrastructure/provider_factory.py create mode 100644 mulit_agent/src/mulit_agent_app/main.py create mode 100644 mulit_agent/src/mulit_agent_app/presentation/__init__.py create mode 100644 mulit_agent/src/mulit_agent_app/presentation/connect_window.py create mode 100644 mulit_agent/src/mulit_agent_app/presentation/tray.py create mode 100644 mulit_agent/tests/test_agent_service_mcp.py create mode 100644 mulit_agent/tests/test_agent_service_skills.py create mode 100644 mulit_agent/tests/test_config.py create mode 100644 mulit_agent/tests/test_controller_session.py create mode 100644 mulit_agent/tests/test_desktop_filesystem_tool.py create mode 100644 mulit_agent/tests/test_full_access_tool.py create mode 100644 mulit_agent/tests/test_image_attachments.py create mode 100644 mulit_agent/tests/test_openai_compatible_provider.py create mode 100644 mulit_agent/tests/test_task_executor.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..898e9a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Top-level .gitignore for mulit-agent monorepo +# Covers both Go (controller/) and Python (mulit_agent/) subprojects. + +# --- Secrets / local config / runtime state --- +.env +.env.* +!.env.example +*.pem +*.key +*.p12 +*.pfx +*.sqlite +*.sqlite3 +*.db + +# Local config files that may contain secrets / machine-specific values +controller/config/controller-config.local.json +mulit_agent/config/agent-config.json + +# Runtime data, logs, caches +data/ +logs/ +*.log +*.pid + +# Disabled sub-repository backup (was mulit_agent/.git, kept aside for reference) +mulit_agent/.git.disabled/ + +# --- OS / IDE --- +.DS_Store +Thumbs.db +Desktop.ini +.idea/ +.vscode/ +*.code-workspace +*.swp +*.swo + +# --- Build artifacts shared across subprojects --- +# (Per-language rules are duplicated in sub-project .gitignore for safety, +# but having them here protects against accidental repo-root builds.) +bin/ +obj/ +out/ +release/ +*.exe +*.dll +*.so +*.dylib +*.pyd +*.pyc +__pycache__/ +*.egg-info/ +*.egg +*.whl +node_modules/ +frontend/dist/ +frontend/build/ +frontend/node_modules/ + +# Coverage / test caches +.coverage +.coverage.* +htmlcov/ +coverage.xml +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ \ No newline at end of file diff --git a/controller/.gitignore b/controller/.gitignore new file mode 100644 index 0000000..bc05131 --- /dev/null +++ b/controller/.gitignore @@ -0,0 +1,44 @@ +# Go / Wails2 build artifacts for the controller +build/ +bin/ +out/ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +vendor/ + +# Wails / frontend bundles +frontend/dist/ +frontend/node_modules/ +frontend/build/ + +# Local config (the committed controller-config.json is the example/default; +# any machine-local override should be in controller-config.local.json) +config/controller-config.local.json + +# Secrets / runtime state +*.pem +*.key +*.p12 +*.pfx +*.sqlite +*.sqlite3 +*.db +.env +.env.* +!.env.example +data/ +logs/ +*.log + +# IDE / OS +.idea/ +.vscode/ +*.code-workspace +.DS_Store +Thumbs.db +Desktop.ini \ No newline at end of file diff --git a/controller/README.md b/controller/README.md new file mode 100644 index 0000000..4fab207 --- /dev/null +++ b/controller/README.md @@ -0,0 +1,22 @@ +# Controller + +独立的 Wails2 总控程序。运行时由 Go 控制平面与 React/TypeScript 管理界面组成;它不依赖本仓库中的 `multi-claw-dashboard`、`multi-claw-agent`、Python、FastAPI 或旧 MultiClaw 数据。 + +## 本地启动 + +```powershell +$wails = Join-Path (go env GOPATH) "bin\wails.exe" +& $wails dev +``` + +从根目录执行 `build.ps1` 即可构建正式总控。应用数据默认位于当前 Windows 用户配置目录下的 `MultiClawControllerWails`,测试/隔离运行可使用 `MULTICLAW_CONTROLLER_DATA_DIR` 覆盖。 + +首次启动会在该数据目录生成仅属于本总控的 TLS 证书和 SQLite 数据库,并启动 WSS 配对服务(默认端口 `8443`)。总控界面可生成 10 分钟有效、仅可使用一次的配对码;将显示的 WSS 地址和配对码填入子 Agent 即可完成首次安全配对。 + +界面中的“概览”页显示当前电脑 IP;配对码只在“安全与配对”页生成,避免把安全操作放在首页。 + +监听地址与端口由 `config/controller-config.json` 控制。若子 Agent 位于其他机器,请仅在受信任内网中放行该端口,且不要将其映射到公网。 + +## 编译 Windows 应用 + +双击 `build.bat`,或在 PowerShell 执行 `./build.ps1`。编译结果为 `build/bin/MultiClawController.exe`;它拥有自己的运行时,不依赖现有 MultiClaw 环境。 diff --git a/controller/app.go b/controller/app.go new file mode 100644 index 0000000..0ad5f76 --- /dev/null +++ b/controller/app.go @@ -0,0 +1,280 @@ +package main + +import ( + "context" + "os" + "path/filepath" + + "MultiClawController/internal/controlplane" +) + +// App is the deliberately narrow bridge between the Wails frontend and the control plane. +type App struct { + ctx context.Context + control *controlplane.ControlPlane + startErr error +} + +func NewApp() *App { return &App{} } + +func (a *App) startup(ctx context.Context) { + a.ctx = ctx + a.control, a.startErr = controlplane.Open(controllerConfigPath()) + if a.startErr == nil { + a.startErr = a.control.StartAgentServer() + } +} + +func (a *App) shutdown(context.Context) { + if a.control != nil { + a.control.Close() + } +} + +// Snapshot returns dashboard data. It is safe to expose to the local WebView. +func (a *App) Snapshot() (controlplane.Snapshot, error) { + if a.startErr != nil { + return controlplane.Snapshot{}, a.startErr + } + return a.control.Snapshot() +} + +// CreatePairingCode returns a short-lived secret. The frontend never persists it. +func (a *App) CreatePairingCode() (string, error) { + if a.startErr != nil { + return "", a.startErr + } + return a.control.CreatePairingCode() +} + +func (a *App) OnlineAgents() ([]controlplane.Agent, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.OnlineAgents() +} + +func (a *App) Agents() ([]controlplane.Agent, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.Agents() +} + +// AgentDirectory is the paged, server-filtered Agent list for the management page. +func (a *App) AgentDirectory(filter controlplane.AgentDirectoryFilter) (controlplane.AgentDirectoryPage, error) { + if a.startErr != nil { + return controlplane.AgentDirectoryPage{}, a.startErr + } + return a.control.AgentDirectory(filter) +} + +// AgentOperationalDetails returns bounded recent operational context for one Agent. +func (a *App) AgentOperationalDetails(agentID string) (controlplane.AgentOperationalDetail, error) { + if a.startErr != nil { + return controlplane.AgentOperationalDetail{}, a.startErr + } + return a.control.AgentOperationalDetails(agentID) +} + +func (a *App) RenameAgent(agentID, name string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.RenameAgent(agentID, name) +} + +func (a *App) RevokeAgent(agentID string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.RevokeAgent(agentID) +} + +func (a *App) CreateTask(agentID, instruction string, timeoutSeconds int) (string, error) { + if a.startErr != nil { + return "", a.startErr + } + return a.control.CreateTask(agentID, instruction, timeoutSeconds) +} + +func (a *App) CreateTaskWithImages(agentID, instruction string, timeoutSeconds int, images []controlplane.TaskImageInput) (string, error) { + if a.startErr != nil { + return "", a.startErr + } + return a.control.CreateTaskWithImages(agentID, instruction, timeoutSeconds, images) +} + +// CreateTasks creates independently-addressed tasks from the multi-Agent task board. +func (a *App) CreateTasks(requests []controlplane.TaskRequest) ([]controlplane.TaskDispatchResult, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.CreateTasks(requests) +} + +// DispatchMentions expands newline-separated public @AgentName assignments into +// independently addressed tasks. +func (a *App) DispatchMentions(message string, timeoutSeconds int) (controlplane.MentionDispatchResponse, error) { + if a.startErr != nil { + return controlplane.MentionDispatchResponse{}, a.startErr + } + return a.control.DispatchMentions(message, timeoutSeconds) +} + +func (a *App) DispatchMentionsWithImages(message string, timeoutSeconds int, images []controlplane.TaskImageInput) (controlplane.MentionDispatchResponse, error) { + if a.startErr != nil { + return controlplane.MentionDispatchResponse{}, a.startErr + } + return a.control.DispatchMentionsWithImages(message, timeoutSeconds, images) +} + +func (a *App) CancelTask(taskID string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.CancelTask(taskID) +} + +func (a *App) RetryTask(taskID string) (string, error) { + if a.startErr != nil { + return "", a.startErr + } + return a.control.RetryTask(taskID) +} + +func (a *App) RecentTasks() ([]controlplane.Task, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.RecentTasks() +} + +func (a *App) ClearTaskHistory() (int, error) { + if a.startErr != nil { + return 0, a.startErr + } + return a.control.ClearTaskHistory() +} + +func (a *App) TaskDetails(taskID string) (controlplane.TaskDetail, error) { + if a.startErr != nil { + return controlplane.TaskDetail{}, a.startErr + } + return a.control.TaskDetails(taskID) +} + +func (a *App) LLMProfiles() ([]controlplane.LLMProfile, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.LLMProfiles() +} + +func (a *App) SaveLLMProfile(input controlplane.LLMProfileInput) (controlplane.LLMProfile, error) { + if a.startErr != nil { + return controlplane.LLMProfile{}, a.startErr + } + return a.control.SaveLLMProfile(input) +} + +func (a *App) DeployLLMProfile(profileID string, agentIDs []string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.DeployLLMProfile(profileID, agentIDs) +} + +func (a *App) AgentLLMStatuses() ([]controlplane.AgentLLMStatus, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.AgentLLMStatuses() +} + +func (a *App) Skills() ([]controlplane.Skill, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.Skills() +} + +func (a *App) SkillDocument(skillID string) (controlplane.SkillDocument, error) { + if a.startErr != nil { + return controlplane.SkillDocument{}, a.startErr + } + return a.control.SkillDocument(skillID) +} + +func (a *App) SaveSkill(input controlplane.SkillInput) (controlplane.Skill, error) { + if a.startErr != nil { + return controlplane.Skill{}, a.startErr + } + return a.control.SaveSkill(input) +} + +func (a *App) DeploySkill(skillID string, agentIDs []string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.DeploySkill(skillID, agentIDs) +} + +func (a *App) SetAgentSkills(agentID string, skillIDs []string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.SetAgentSkills(agentID, skillIDs) +} + +func (a *App) AgentSkillStatuses() ([]controlplane.AgentSkillStatus, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.AgentSkillStatuses() +} + +func (a *App) EnableDesktopFilesystem(agentIDs []string) error { + if a.startErr != nil { + return a.startErr + } + return a.control.EnableDesktopFilesystem(agentIDs) +} + +func (a *App) AgentDeploymentStatuses() ([]controlplane.AgentDeploymentStatus, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.AgentDeploymentStatuses() +} + +func (a *App) RecentAuditEvents() ([]controlplane.AuditEvent, error) { + if a.startErr != nil { + return nil, a.startErr + } + return a.control.RecentAuditEvents() +} + +func (a *App) AuditTrail(filter controlplane.AuditFilter) (controlplane.AuditPage, error) { + if a.startErr != nil { + return controlplane.AuditPage{}, a.startErr + } + return a.control.AuditTrail(filter) +} + +func controllerConfigPath() string { + if configured := os.Getenv("MULTICLAW_CONTROLLER_CONFIG_PATH"); configured != "" { + return configured + } + if executable, err := os.Executable(); err == nil { + candidate := filepath.Join(filepath.Dir(executable), "config", "controller-config.json") + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + workingDirectory, err := os.Getwd() + if err != nil { + return "config/controller-config.json" + } + return filepath.Join(workingDirectory, "config", "controller-config.json") +} diff --git a/controller/build.bat b/controller/build.bat new file mode 100644 index 0000000..34fa0b8 --- /dev/null +++ b/controller/build.bat @@ -0,0 +1,3 @@ +@echo off +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0build.ps1" +if errorlevel 1 pause diff --git a/controller/build.ps1 b/controller/build.ps1 new file mode 100644 index 0000000..0cb9209 --- /dev/null +++ b/controller/build.ps1 @@ -0,0 +1,19 @@ +$ErrorActionPreference = "Stop" + +$projectRoot = $PSScriptRoot +$wailsCommand = Join-Path (go env GOPATH) "bin\wails.exe" +Set-Location -LiteralPath $projectRoot + +if (-not (Test-Path -LiteralPath $wailsCommand)) { + throw "未找到 Wails CLI。请先执行:go install github.com/wailsapp/wails/v2/cmd/wails@v2.15.0" +} + +& $wailsCommand build -clean -o "MultiClawController.exe" +if ($LASTEXITCODE -ne 0) { + throw "Wails 编译失败。请先退出正在运行的 MultiClawController,然后重试。" +} + +$configTarget = Join-Path $projectRoot "build\bin\config" +Copy-Item -LiteralPath (Join-Path $projectRoot "config") -Destination $configTarget -Recurse -Force + +Write-Host "编译完成:$(Join-Path $projectRoot 'build\bin\MultiClawController.exe')" diff --git a/controller/config/controller-config.json b/controller/config/controller-config.json new file mode 100644 index 0000000..bd8763b --- /dev/null +++ b/controller/config/controller-config.json @@ -0,0 +1,5 @@ +{ + "config_version": 1, + "agent_host": "0.0.0.0", + "agent_port": 8443 +} diff --git a/controller/docs/BLUEPRINT.md b/controller/docs/BLUEPRINT.md new file mode 100644 index 0000000..bd6867d --- /dev/null +++ b/controller/docs/BLUEPRINT.md @@ -0,0 +1,126 @@ +# Controller(总控端)重构蓝图 + +## 1. 目标与边界 + +`controller` 是多智能体系统的**唯一控制平面**,以独立的 **Wails2(Go + React/TypeScript)** 桌面程序运行。它面向管理员和调度员,负责保存全局状态、编排任务、管理子 Agent 的生命周期和展示运行态;它不执行模型推理、工具调用或插件业务。 + +### 独立交付约束 + +`controller` 必须是可单独安装、启动和升级的程序:拥有自己的源码包、`pyproject.toml`、依赖锁定文件、数据目录、配置和安装包。运行时不得导入、调用或要求 `multi-claw-dashboard`、`multi-claw-agent`、Node.js、FastAPI、React、现有 MultiClaw 数据库、配置文件或启动脚本。现有 MultiClaw 只能作为迁移期间的行为参考或一次性数据导入来源,不能成为前置运行环境。 + +控制端发行目录中的 `config/controller-config.json` 保存非敏感、可迁移的运行设置(例如 Agent 服务监听地址和端口)。启动时优先加载该文件;后续设置界面的保存动作必须原子回写同一文件。TLS 私钥、数据库和配对凭据不写入该配置文件,而保留在当前 Windows 用户的数据目录中。 + +本次重构的目标是将现有 Dashboard/Express 所承载的管理与编排能力迁移为 Wails2 总控端,并与 Tkinter/Python 的 `mulit_agent` 形成可单独发布、单独升级、可远程连接的两个程序。 + +总控端负责: + +- Agent 注册、认证、在线状态与能力目录。 +- 任务创建、@Agent 路由、并行/串行/DAG 编排、超时和取消。 +- 汇总子 Agent 的结果,并保存任务会话、审计记录和可恢复的编排状态。 +- 全局配置、密钥引用、技能/插件分发策略、委派编排和运行监控。 +- 提供 React/TypeScript 管理界面、通知与操作入口。 + +总控端不负责: + +- 调用 LLM、执行 Shell/浏览器/文件等工具,或直接加载 Agent 插件。 +- 直接读写任一 Agent 的本地数据库、配置或工作目录。 +- 在 GUI 线程中执行网络 I/O、调度或数据库长事务。 + +### 集中配置原则 + +除首次配对所需的总控地址、配对码和显示名称外,子 Agent 不提供可编辑的 LLM、Skill、MCP、定时任务或执行策略配置。总控保存这些配置并按 Agent 或 Agent 组生成带版本号的配置快照;每次变更均下发、确认、审计,并允许查看每个 Agent 的已应用版本。 + +LLM Key 仅由总控录入和保管,使用已配对的 TLS/WSS 会话下发给指定 Agent,Agent 只将其写入本机系统凭据库。Key 不进入可迁移 JSON、不显示给 Agent 用户、不进入任务事件或日志。Skills 与 MCP 工具以已校验的包、哈希、权限清单和目标范围下发,而非直接远程执行任意文件。 + +远程重启依赖目标机器已安装的 Agent 守护服务:守护服务负责启动、停止和重启执行进程;总控仅发送受认证、可审计的生命周期命令。已停止的普通托盘程序不能自行被远程唤醒。 + +## 2. 目标架构 + +```text +┌─────────────────────────────────────────────────────────────────────┐ +│ controller(Wails2:Go + React/TypeScript) │ +│ Presentation: React 页面 / ViewModel / 通知 │ +│ Application : Go TaskService / Orchestrator / AgentService │ +│ Domain : Task、Workflow、Agent、Policy、Audit │ +│ Infrastructure: SQLite、TLS/WSS、密钥库、日志 │ +└─────────────────────────┬───────────────────────────────────────────┘ + │ 版本化 HTTPS + WebSocket,HMAC/令牌认证 + ▼ + ┌─────────────────────┐ + │ mulit_agent(多个) │ + │ PySide6 子 Agent │ + └─────────────────────┘ +``` + +推荐目录结构: + +```text +controller/ +├── app.go # 受限的 Wails 前端绑定 +├── main.go # Wails 进程入口与退出编排 +├── go.mod +├── internal/ # Go 控制平面、SQLite、TLS/WSS +├── frontend/ # React/TypeScript 管理界面 +├── config/ # 可迁移的非敏感配置 +├── build.ps1 # Windows 发布构建入口 +├── tests/ +└── docs/ +``` + +UI 使用 React 页面、组件与 ViewModel,至少包含控制台、Agent、模板、技能、插件、工作流、定时任务、密钥、委派、审计日志与设置页面。记忆、帮助、用户管理和登录页面不迁移。前端只能调用按用例划分的 Wails 绑定并渲染数据;不得直接访问数据库、TLS 私钥、Agent 凭据或本机工具。 + +## 3. 核心功能设计 + +### 3.1 Agent 管理 + +`AgentService` 管理注册、撤销、心跳、能力同步、配置下发和启动/停止请求。每个 Agent 由不可变 `agent_id` 标识,显示名称可以修改。状态机为:`registered → connecting → online → degraded/offline → revoked`。 + +总控端只通过传输接口发出动作;本地进程启动、Docker 控制或远程重启都应实现为可替换的 `AgentLifecyclePort`,避免 UI 和平台命令耦合。 + +### 3.2 任务与编排 + +任务包含 `task_id`、会话 ID、发起人、输入、附件引用、目标 Agent、策略、截止时间和幂等键。`Orchestrator` 以持久化状态机执行: + +`created → queued → dispatching → running → synthesizing → completed/failed/cancelled`。 + +支持单 Agent、并行、串行、DAG 和私有指派。DAG 节点仅引用上游的已脱敏结果,不隐式共享完整会话。取消、重试和断线恢复必须基于 `task_id` 与尝试次数幂等处理。 + +### 3.3 存储与安全 + +控制端使用本地 SQLite 保存 Agent、任务、工作流、事件、审计与 UI 偏好;敏感令牌不以明文写入业务表,而由系统凭据库或经主密钥加密的密钥仓库保存。数据库迁移必须有版本号、备份和回滚说明。 + +总控在本机环境中以单一管理员身份运行,不提供旧 FastAPI Dashboard 的登录账户、用户或角色管理。所有创建、下发、取消、升级和授权动作仍须记入不可修改的审计事件;日志中不得写入 API Key、任务附件原文或完整提示词。 + +### 3.4 实时通信 + +通信层采用 HTTPS 请求(注册、命令、查询)和 WebSocket(心跳、状态、流式输出、事件)。Go 控制平面提供 TLS/WSS 监听服务;React 前端只能经 Wails 绑定读取状态或发起受校验的用例。 + +接口以 `/api/v1` 开头并带 `schema_version`。每个命令和事件都携带 `message_id`、`correlation_id`、UTC `sent_at`;收件方去重并返回确认,未确认消息可安全重发。 + +## 4. 与子 Agent 的接口契约 + +以下契约同时是两个程序的共同边界,最终应抽取为共享、版本化的 Python 包(例如 `multiclaw_protocol`),不能靠复制 Pydantic/字典定义维持一致。 + +该协议包必须是第三方独立依赖(有独立版本和发布物),或随两个安装包各自内置;它不得依赖本仓库现有 MultiClaw 的任何模块。两个程序除该协议契约和网络通信外不共享运行时状态、数据库或文件目录。 + +| 类别 | 总控端动作 | 子 Agent 回应/事件 | +| --- | --- | --- | +| 连接 | 注册、发放凭据、心跳确认 | `agent.hello`、`agent.heartbeat`、能力清单 | +| 任务 | 下发、取消、查询、重试 | 接收确认、阶段进度、流式片段、最终结果 | +| 配置 | 获取/更新配置、技能/插件策略 | 校验结果、应用结果、需重启标记 | +| 生命周期 | 启动、停止、健康检查、升级请求 | 状态变化、健康指标、错误事件 | + +任务下发的最小载荷:`task_id`、`correlation_id`、`attempt`、`instruction`、`context_refs`、`deadline_at`、`policy`、`reply_to`。最终结果最小载荷:`task_id`、`status`、`output`、`artifacts`、`usage`、`error`、`started_at`、`finished_at`。所有时间使用 ISO 8601 UTC,所有状态枚举由协议包定义。 + +## 5. Wails2 并发与安全规范 + +- React/WebView 只处理渲染和用户交互;不能同步执行网络、数据库或编排工作。 +- Go 控制平面使用受控 goroutine 处理 WSS 会话;每条 WebSocket 连接的写入必须串行化。 +- Wails 绑定只暴露 `Snapshot`、配对码生成、在线 Agent 查询、任务创建和任务查询等窄用例;不绑定通用数据库、文件、Shell 或凭据能力。 +- 窗口关闭时依次停止新调度、关闭 WSS、持久化状态并释放数据库连接。 + +## 6. 迁移原则与验收 + +采用绞杀式迁移:先建立协议和只读监控,再迁移单 Agent 调度,最后迁移工作流、配置与运维能力。旧 Dashboard 与 FastAPI Agent 在过渡期可通过兼容适配器接入;任何阶段均不允许两个控制端同时向同一 Agent 发出可变更命令。 + +首个可发布版本验收条件:可注册并持续显示一个远程 Agent;可下发、取消并恢复单 Agent 任务;可展示流式输出与最终结果;重启总控后可恢复历史和未完成任务;断网、重复消息、非法凭据和 Agent 超时均有可见、可审计的处理结果。 diff --git a/controller/docs/CONFIGURATION.md b/controller/docs/CONFIGURATION.md new file mode 100644 index 0000000..2a6ae0a --- /dev/null +++ b/controller/docs/CONFIGURATION.md @@ -0,0 +1,19 @@ +# Controller 配置与迁移 + +## 可迁移配置 + +发行目录下的 `config/controller-config.json` 是可复制到另一台电脑的非敏感配置。当前支持: + +```json +{ + "config_version": 1, + "agent_host": "0.0.0.0", + "agent_port": 8443 +} +``` + +Wails 总控从发行目录和开发目录中的 `config/controller-config.json` 加载该文件。可用 `MULTICLAW_CONTROLLER_CONFIG_PATH` 指向一份隔离配置。未来设置页保存后必须原子写回同一文件。 + +## 不可迁移的安全状态 + +当前 Windows 用户配置目录中的 `MultiClawControllerWails` 保存 TLS 证书、私钥、SQLite 数据库和已配对 Agent 凭据,不能与程序目录一起复制。可用 `MULTICLAW_CONTROLLER_DATA_DIR` 创建测试或隔离数据目录。将总控迁到另一台电脑后,它会建立新的控制端身份,所有 Agent 需使用新的一次性配对码重新配对。 diff --git a/controller/docs/STATUS.md b/controller/docs/STATUS.md new file mode 100644 index 0000000..19ed5eb --- /dev/null +++ b/controller/docs/STATUS.md @@ -0,0 +1,57 @@ +# Controller 当前开发进度 + +更新时间:2026-09-15 + +## 已完成并验证 + +- 图片附件任务闭环:批量 `@` 与单独指派均可粘贴/选择受限图片;多条有效批量任务必须以“图片 N”逐行路由,同一行多个目标只共享该编号图片。附件经认证 WSS 下发、本地持久化供重试复用,任务详情仅显示安全缩略图和数量;清除结束任务同时清除附件。未配置视觉能力的模型会安全拒绝,不会丢图或降级为文本。 + +- 新版 Agent 默认完全访问:总控仍只通过已认证 WSS 下发任务,但 Agent 可在其所在 Windows 电脑实际操作文件、程序、网络和桌面应用。任务详情会把实际调用过本机执行工具的结果标为“完全访问”;结果、日志与审计摘要持续脱敏,不保存 API Key、密码、凭据、图片 Base64 或内部推理。 + +- 独立 Wails2(Go + React/TypeScript)总控工程、独立数据目录、TLS/WSS 服务和 Windows 编译脚本。 +- 一次性配对码、证书指纹校验、Agent 认证、心跳、离线判定、名称修改和撤销配对。 +- 多 Agent 独立文本任务下发、状态回传、取消、超时和重试;任务记录持久化到总控本地数据库。一次最多可下发 20 条不同目标的任务;单条失败不会阻塞其它任务。 +- 新任务默认使用 5 分钟无进展超时,而非绝对总时长;Agent 回传接收或执行进展会刷新下一次超时点。任务页可按需设为 15、30 分钟或 1 小时。 +- 总控基础界面:主导航依次提供控制台、智能体、批量指派、单独指派及密钥与配对;控制台包含按机器汇总的实时任务状态和任务历史,两种批量下发方式集中在“批量指派”。 +- 总控 LLM 配置页:内置 OpenAI、Anthropic、Gemini、DeepSeek、OpenRouter、Groq、Mistral、xAI、Together、硅基流动、Moonshot、MiniMax、Ollama、LM Studio、vLLM 预设,并支持任意 OpenAI 兼容的中转站或本地模型。 +- LLM API Key 使用 Windows DPAPI 加密后保存于总控本机;列表只显示“已保护保存”,不会回显 Key。 +- 总控可将指定 LLM 配置下发给指定 Agent;在线 Agent 立即接收,离线 Agent 会在下次认证连接时补齐。总控显示等待、已发送、已应用或失败的回执状态。 +- Skills 已采用 Markdown 仓库、哈希校验和按 Agent 下发;子端只受控安装,不执行 Skill 中的脚本。 +- 旧版桌面文件 MCP 状态机保留为兼容记录;它不再限制新版 Agent 的默认本机权限。插件与 MCP 页面会明确显示“本机完全访问:默认启用”。 +- Agent 管理页会汇总显示 LLM、Skill 与 MCP 的部署/应用状态;本地审计日志已覆盖 MCP 下发、回执和工具结果,不保存 API Key、配对凭据或完整任务提示词。 +- Agent 返回的任务输出会在接收端去除 `...`(含未闭合标签),避免内部推理显示到任务结果或审计中。 +- 最近任务卡片和详情会保留并显示执行 Agent 名称;可一键清除已结束任务及其任务时间线,进行中的任务与审计日志会保留。 +- Skills 页面提供可搜索的只读 Markdown 浏览器:按名称或版本即时过滤,逐行显示名称、版本和更新时间,按需加载正文;预览会脱敏 API Key、密码、令牌、账号等敏感字段,不执行任何 Markdown 内容。 +- Agent 管理页已升级为服务端分页目录:支持名称搜索,以及在线/离线、MCP、LLM、Skill 状态组合筛选。Agent 详情提供最近任务、最近安全审计、部署历史、当前失败原因和既有 Skills/MCP 配置;列表与详情中的失败文本会脱敏敏感字段。 +- 认证确认会在 LLM、Skill、MCP 配置快照之前发送,避免重连时部署快照抢先到达;认证后的持久化配置仍会异步补发。 +- 审计日志已升级为统一、可筛选的本地基础设施:记录时间、操作者、目标 Agent、资源类型、动作、结果和安全摘要;支持按 Agent、资源类型、结果、时间范围服务端分页筛选。现有配对、Agent、LLM、Skill、MCP、任务链路均已覆盖;安全摘要不保存 API Key、配对凭据、完整任务提示词或内部推理,清除任务历史也不会删除审计。 +- 子 Agent 的独立任务执行已通过并发回归:同一台机器收到多条任务时会分别进入后台执行;多台机器可同时处理各自任务。日常使用应按机器资源自行控制同机任务数量。 +- “批量指派”和“单独指派”是彼此独立的任务入口:单独指派会明确选择一台在线 Agent;批量指派只提供公共 `@` 输入,不保留重复的多条任务草稿。`@` 支持点选或键盘选择 Agent,并按换行拆成不同任务;同一行的多个 Agent 会各自收到完全相同的任务。未知、重名、离线和未指定的目标均不会误发。 +- 任务内容会拒绝 API Key、配对凭据、账号密码及 `` 内部推理,避免进入界面、任务记录、日志、结果或审计摘要。 +- 旧 FastAPI Dashboard 的导航信息架构已迁入:模板、技能、插件、工作流、定时任务、密钥、委派、审计日志、设置等页面均已有入口;尚未迁移的页面显示“正在开发”。 +- 用户、记忆、帮助及旧登录/角色模型已按当前产品决策移除。 + +## 最近编译 + +- `build/bin/MultiClawController.exe` 已于 2026-09-15 重新编译(含独立的批量指派页面、单独指派与 5 分钟无进展续期)。 + +## 接下来按旧版能力迁移的顺序 + +1. 优先补齐离线任务队列、重连补传和子 Agent 重启恢复,增强多机任务闭环的可靠性。 +2. 需要复用任务时再实现模板、定时任务、工作流和委派,均由总控持久化和触发。 +3. 将 MCP 扩展为带版本、权限清单、哈希、回滚和多工具状态的受控仓库;插件包保留独立入口。 +4. 最后实现 Agent 守护服务,提供远程启动、停止、重启和升级。 + +## 已规划,尚未实现 + +- 总控定时任务、委派规则与工作流执行器。 +- Skills、插件与 MCP 工具的签名、版本回滚和完整包生命周期。 +- 定时任务、模板、工作流、委派和远程运维的审计扩展。 +- Agent 守护服务及其远程启动、停止、重启、升级能力。 + +## 进度判断 + +- **可测试基础闭环**(配对、在线、任务、取消、重试):约 **70%**。 +- **最终集中管理目标**(统一 LLM/Skill/MCP/定时器/远程运维):约 **35%**。 + +当前策略:优先迁移旧版有价值的总控功能和页面,再把每项能力通过 WSS 下发给独立子 Agent;不迁移旧 FastAPI、Node.js、旧 Python Agent 运行环境,也不恢复用户、登录、记忆、帮助或 Tailscale 发现。 diff --git a/controller/docs/steps/01-项目骨架与运行基线.md b/controller/docs/steps/01-项目骨架与运行基线.md new file mode 100644 index 0000000..58bfb3f --- /dev/null +++ b/controller/docs/steps/01-项目骨架与运行基线.md @@ -0,0 +1,9 @@ +# 01:项目骨架与运行基线 + +1. 在 `controller` 建立蓝图所列分层目录、应用入口、依赖锁定和测试目录;所有模块可从入口启动。 +2. 安装并固定 Go、Wails2、React/TypeScript、SQLite 驱动和测试依赖;在目标 Windows 环境验证 WebView2 运行时。 +3. 建立 `AppConfig`,区分开发/测试/生产配置,支持数据目录、控制端 ID、监听地址、日志级别和传输超时;发行目录的 `config/controller-config.json` 优先加载并由设置页原子回写,禁止把密钥放进该文件、仓库或默认配置。 +4. 创建最小 Wails `App`、React 管理界面、异常提示和优雅退出骨架;Go 控制平面通过窄 Wails 绑定接入。 +5. 配置 lint、类型检查、单元测试和打包命令,提供一条本地启动命令。 + +验收:空壳程序可打开并正常退出;配置错误会给出可理解的信息;测试、格式检查和类型检查可在干净环境运行。 diff --git a/controller/docs/steps/02-共享协议与安全边界.md b/controller/docs/steps/02-共享协议与安全边界.md new file mode 100644 index 0000000..ffa8fc8 --- /dev/null +++ b/controller/docs/steps/02-共享协议与安全边界.md @@ -0,0 +1,9 @@ +# 02:共享协议与安全边界 + +1. 新建独立的共享协议包,定义 Pydantic 消息模型、状态枚举、错误码和 `/api/v1` 路由常量;供 `controller` 和 `mulit_agent` 共同依赖。 +2. 实现注册、心跳、能力、任务、取消、进度、流片段、结果和配置消息;每种消息都含版本、消息 ID、关联 ID 和 UTC 时间。 +3. 为消息建立 JSON Schema 和正反例测试;未知字段、错误版本和无效状态转换必须拒绝并返回标准错误。 +4. 实现 Agent 凭据签发、HMAC/短期令牌校验、请求时间窗、重放去重与凭据撤销;密钥仅经系统凭据库或加密存储读取。 +5. 明确附件、上下文和日志的大小限制、脱敏规则及权限校验点。 + +验收:两端可用同一协议包序列化和反序列化全部基础消息;篡改、过期、重放和越权请求都有自动化测试。 diff --git a/controller/docs/steps/03-存储与领域模型.md b/controller/docs/steps/03-存储与领域模型.md new file mode 100644 index 0000000..c326a99 --- /dev/null +++ b/controller/docs/steps/03-存储与领域模型.md @@ -0,0 +1,9 @@ +# 03:存储与领域模型 + +1. 设计并创建版本化迁移:Agent、能力快照、任务、任务节点、工作流、事件、审计、用户和 UI 偏好。 +2. 定义领域实体和值对象,使用显式状态转换函数约束 Agent、任务和工作流状态,禁止在界面层直接拼写状态字符串。 +3. 实现 Repository 和事务边界;任务状态、事件与审计记录必须同事务落库。 +4. 实现启动恢复:将中断任务标记为待确认,向在线 Agent 查询最终状态后再决定恢复、重试或失败。 +5. 提供备份、迁移前检查和失败回滚策略;记录数据保留期限。 + +验收:新库和升级库均可迁移;重启后任务和审计可查询;模拟中途退出不会产生孤儿节点或矛盾状态。 diff --git a/controller/docs/steps/04-通信与Agent注册.md b/controller/docs/steps/04-通信与Agent注册.md new file mode 100644 index 0000000..edf306e --- /dev/null +++ b/controller/docs/steps/04-通信与Agent注册.md @@ -0,0 +1,9 @@ +# 04:通信与 Agent 注册 + +1. 实现 `CommunicationService`:HTTP 客户端、WebSocket 客户端、连接状态、超时、退避重连、确认和去重。 +2. 实现注册/撤销、凭据签发、`agent.hello`、心跳和能力同步用例;将在线判定和最后活动时间持久化。 +3. 定义 `AgentLifecyclePort`,提供本地、Docker、远程等适配器接口;首版可先实现远程连接适配器。 +4. 将网络事件转换为 Go 应用层状态,React 前端通过定时查询或受控事件订阅更新 ViewModel;网络 goroutine 不得直接访问 WebView。 +5. 加入连接诊断页所需的最近错误、延迟、协议版本和重连次数数据。 + +验收:可在不重启 UI 的情况下注册、断开、重连和撤销测试 Agent;重复心跳和乱序事件不会错误覆盖较新的状态。 diff --git a/controller/docs/steps/05-任务调度与编排.md b/controller/docs/steps/05-任务调度与编排.md new file mode 100644 index 0000000..5e5f1c9 --- /dev/null +++ b/controller/docs/steps/05-任务调度与编排.md @@ -0,0 +1,9 @@ +# 05:任务调度与编排 + +1. 实现创建、验证、排队、派发、取消、重试和查询任务的应用服务,所有请求使用幂等键。 +2. 先完成单 Agent 指派和流式事件归档,再增加并行任务和结果汇总;汇总模型调用必须是可替换策略。 +3. 引入 DAG 工作流模型,验证无环、依赖已满足、输入映射合法和失败传播规则。 +4. 实现超时、断线、Agent 拒绝、部分成功和控制端重启恢复路径;明确每种情况是否重试及最大次数。 +5. 在任务详情中提供事件时间线、节点状态、输出、错误和人工取消入口。 + +验收:单任务、并行任务和三节点 DAG 在模拟传输层中可重复执行;取消与重试不会重复执行同一次尝试。 diff --git a/controller/docs/steps/06-Wails2管理界面.md b/controller/docs/steps/06-Wails2管理界面.md new file mode 100644 index 0000000..7bf1f85 --- /dev/null +++ b/controller/docs/steps/06-Wails2管理界面.md @@ -0,0 +1,9 @@ +# 06:Wails2 管理界面 + +1. 完成应用外壳、导航、主题、全局错误提示和可访问性基础;文本、日期和状态颜色统一由展示层映射。 +2. 实现概览页:在线数、运行任务、失败告警和最近事件;使用定时刷新加事件推送,避免阻塞 UI。 +3. 实现 Agent 列表和详情:注册、撤销、连接诊断、能力、健康信息和配置下发入口。 +4. 实现任务页面:创建、选择 Agent、任务流、取消、重试、结果和会话历史。 +5. 实现工作流、设置和审计页;危险操作必须二次确认并显示影响对象。 + +验收:在 100 个模拟 Agent、持续流式事件下界面仍可交互;所有关键动作都有加载、成功和失败状态。 diff --git a/controller/docs/steps/07-迁移兼容与可靠性.md b/controller/docs/steps/07-迁移兼容与可靠性.md new file mode 100644 index 0000000..353258a --- /dev/null +++ b/controller/docs/steps/07-迁移兼容与可靠性.md @@ -0,0 +1,9 @@ +# 07:迁移兼容与可靠性 + +1. 盘点现有 Dashboard 的 Agent、任务、编排、工作流、配置和审计数据,编写字段映射与不可迁移项清单。 +2. 为旧 FastAPI Agent 实现协议兼容适配器或网关;功能开关确保每个 Agent 同时只接收一个控制端的变更命令。 +3. 制定导出、备份、导入、校验、灰度切换和回退流程,并在副本数据库演练。 +4. 对网络分区、重复投递、进程崩溃、慢 Agent 和版本不兼容做故障注入测试。 +5. 增加运行指标:连接率、派发延迟、任务耗时、失败率、重试率和 UI 错误率。 + +验收:可将代表性旧数据导入新端并核对数量与关键状态;失败切换可在不丢失任务记录的前提下回退。 diff --git a/controller/docs/steps/08-测试发布与运维.md b/controller/docs/steps/08-测试发布与运维.md new file mode 100644 index 0000000..6983fd6 --- /dev/null +++ b/controller/docs/steps/08-测试发布与运维.md @@ -0,0 +1,9 @@ +# 08:测试发布与运维 + +1. 建立 Go 领域单测、协议契约测试、WSS 集成测试、React 构建测试和端到端演练;关键状态机覆盖正常与异常路径。 +2. 配置 CI:依赖安装、静态检查、测试、协议兼容矩阵和 Windows 打包验证。 +3. 使用 PyInstaller 或同等方案打包 Windows 安装物,确定数据目录、升级策略、签名和卸载时的数据保留策略。 +4. 编写管理员手册、故障排查、备份恢复、密钥轮换、日志收集和版本兼容说明。 +5. 以试点 Agent 完成发布演练,设定回滚阈值和上线后监控窗口。 + +验收:从空白 Windows 环境可安装、连接测试 Agent、执行任务并升级;发布包附带校验、版本和回滚说明。 diff --git a/controller/docs/steps/09-单Agent任务闭环.md b/controller/docs/steps/09-单Agent任务闭环.md new file mode 100644 index 0000000..91434d2 --- /dev/null +++ b/controller/docs/steps/09-单Agent任务闭环.md @@ -0,0 +1,26 @@ +# 09:单 Agent 任务闭环 + +## 本阶段目标 + +让总控可以选择一个已认证且在线的子 Agent,下发单条文本任务,并持续展示 `queued → accepted → running → completed/failed` 的状态和最终结果。 + +## 已实现范围 + +1. 控制端 SQLite 新增 `task_runs` 与 `task_events`,保存任务、状态变更、输出和错误。 +2. 仅向当前 WSS 会话中已认证的在线 Agent 下发 `task_dispatch` 消息;消息携带协议版本、任务 ID、文本指令和 UTC 时间。 +3. 控制端只接受来自该任务目标 Agent 的 `task_event`,并忽略终态任务的重复事件。 +4. Wails React 总控界面提供在线 Agent 选择、任务输入、下发按钮,以及最近任务的状态与结果显示;Go 只向前端暴露这些受限用例。 +5. 当 WSS 连接在下发瞬间断开时,任务会标记为失败而非伪装为已执行。 + +## 本阶段刻意限制 + +- 这是单 Agent、文本任务闭环;尚不支持取消、重试、工作流、附件或多 Agent 编排。 +- 子 Agent 目前运行安全的内置演示执行器,不会调用 LLM、Shell、浏览器、文件或任何高风险工具。 +- 模型 Provider 的 API 密钥和工具授权策略将在后续阶段单独设计,不能为了演示而写入任务数据库或配置文件。 + +## 验收步骤 + +1. 启动总控,完成一个 Agent 的配对并确认其显示为在线。 +2. 在“单 Agent 任务”区域选择该 Agent,输入任意文本并下发。 +3. 确认任务依次显示为 `accepted`、`running`、`completed`,且出现内置演示执行器的结果。 +4. 重启总控,确认最近任务仍显示在任务记录中。 diff --git a/controller/docs/steps/10-Wails2迁移与信息架构.md b/controller/docs/steps/10-Wails2迁移与信息架构.md new file mode 100644 index 0000000..e710020 --- /dev/null +++ b/controller/docs/steps/10-Wails2迁移与信息架构.md @@ -0,0 +1,20 @@ +# 10:Wails2 迁移与信息架构 + +## 已完成迁移 + +1. `controller` 根目录已切换为 Wails2 工程:Go 负责控制平面,React/TypeScript 负责界面。 +2. 旧 PySide6 总控入口、项目定义、源码与测试已从工作根目录移除;旧发布产物仅保留在隐藏归档目录,不能被默认构建入口使用。 +3. `build.ps1` 和 `build.bat` 默认构建 Wails 发布包 `build/bin/MultiClawController.exe`。 +4. 当前 Wails 控制平面已覆盖首次配对、TLS 指纹校验、WSS 心跳、单 Agent 任务下发和最终结果回传。 + +## 当前界面架构 + +- **概览**:总控状态、在线 Agent、运行任务、当前电脑 IP 和 Agent 连接地址;不展示配对码。 +- **任务**:选择在线 Agent、下发文本任务、查看持久化的最近任务结果。 +- **安全与配对**:沿用旧 Dashboard 的“密钥独立页面”原则,生成仅可使用一次、十分钟有效的配对码。该配对码不同于旧系统的长期 API Key,包含 TLS 指纹且不写入界面持久化状态。 + +## 验收 + +1. 启动 `build/bin/MultiClawController.exe`,确认概览显示本机 IP。 +2. 在“安全与配对”生成代码,并在子 Agent 使用该 IP/连接地址完成配对。 +3. 在“任务”页面向在线 Agent 下发文本任务,确认状态与结果回传。 diff --git a/controller/docs/steps/11-Agent管理与配对生命周期.md b/controller/docs/steps/11-Agent管理与配对生命周期.md new file mode 100644 index 0000000..0bdcd59 --- /dev/null +++ b/controller/docs/steps/11-Agent管理与配对生命周期.md @@ -0,0 +1,26 @@ +# 11:Agent 管理与配对生命周期 + +## 本阶段目标 + +让 Wails 总控能管理多个子 Agent,而不仅是显示在线数量。 + +## 已实现范围 + +1. 新增独立 **Agent** 页面,列出当前在线和离线的 Agent。 +2. 每个 Agent 显示不可变 ID、显示名称、状态和最后心跳时间。 +3. 允许修改显示名称,不改变 Agent 身份或凭据。 +4. “撤销配对”会立即关闭当前 WSS 会话并删除配对记录;Agent 再次连接时会被拒绝,必须重新配对。 +5. “添加 / 重新配对”只跳转到独立的“安全与配对”页面,不在 Agent 列表或概览页泄露配对码。 + +## 安全边界 + +- 前端只有 Agent 查询、改名和撤销三个受限绑定,不可读到凭据哈希、TLS 私钥或数据库原始内容。 +- 撤销会移除 Agent 配对记录,但不删除已有任务与审计历史。 +- 已移除的 Agent 不能通过旧凭据认证。 + +## 验收 + +1. 完成一个 Agent 的配对后,确认其出现在 Agent 页面并显示在线。 +2. 修改名称并刷新,确认名称被持久化。 +3. 点击“撤销配对”并确认,Agent 卡片立即消失且连接断开。 +4. 重启 Agent,确认旧凭据被拒绝;生成新配对码后可作为新的 Agent 加入。 diff --git a/controller/docs/steps/12-任务取消超时与重试.md b/controller/docs/steps/12-任务取消超时与重试.md new file mode 100644 index 0000000..c3dacc8 --- /dev/null +++ b/controller/docs/steps/12-任务取消超时与重试.md @@ -0,0 +1,39 @@ +# 12:任务取消、超时与重试 + +## 目标 + +让总控对单 Agent 任务拥有最小、可审计的控制闭环:下发时设定超时;执行中可以取消;失败、取消或超时后可以创建一次新的尝试。原始任务记录始终保留。 + +## 已实现 + +1. `task_runs` 增加 `deadline_at`、`timeout_seconds`、`attempt` 与 `retry_of` 字段;旧数据库通过增量迁移保持可打开。 +2. 下发的 `task_dispatch` 携带 ISO 8601 的“无进展截止时间”和尝试次数。Agent 回传 `accepted` 或 `running` 等有效进展后,总控会以该任务的超时窗口重新计算截止时间。 +3. 总控在界面轮询快照/任务列表时检查连续无进展的任务:将状态置为 `timed_out`、保存事件,并向已连接的 Agent 发送 `task_cancel`。 +4. 新任务默认使用 5 分钟无进展超时;任务页可选择 5、15、30 分钟或 1 小时。活动任务可取消。 +5. 只有 `failed`、`cancelled`、`timed_out` 的任务显示“重试”。重试会生成新的任务 ID 和新的尝试次数,并以 `retry_of` 指回原始任务,绝不覆写旧记录。 + +## 协议约定 + +取消消息: + +```json +{ + "type": "task_cancel", + "schema_version": 1, + "task_id": "...", + "reason": "由总控取消" +} +``` + +Agent 必须以普通 `task_event` 回报最终状态 `cancelled` 或 `timed_out`;总控不把“取消指令已发出”误当作 Agent 已停止。 + +## 验收 + +1. 下发任务后确认 Agent 回传“已接收”或“执行中”,任务详情中的“下次无进展超时”应向后刷新。 +2. 在演示任务的执行窗口内点击“取消”,任务应最终显示“已取消”。 +3. 对已取消或已超时任务点击“重试”,列表应出现新的任务 ID 与“第 2 次”,旧记录仍存在。 +4. 已完成任务不应提供重试操作。 + +## 当前边界与下一步 + +当前 Agent 在连接中断时只在内存中暂存待发送事件;进程重启后的事件补传、任务幂等恢复和断线重连回放尚未实现。这些属于下一阶段的可靠性工作,不能作为本步骤已完成的能力对外承诺。 diff --git a/controller/docs/steps/13-集中配置与远程运维.md b/controller/docs/steps/13-集中配置与远程运维.md new file mode 100644 index 0000000..a9817ab --- /dev/null +++ b/controller/docs/steps/13-集中配置与远程运维.md @@ -0,0 +1,10 @@ +# 13:集中配置与远程运维 + +1. 已完成:在总控建立 Provider、模型和 API Key 的配置档数据模型;支持向指定 Agent 下发。Provider 预设覆盖主流云端服务商和常见本地运行时,并允许自定义 OpenAI 兼容地址。 +2. 已完成:API Key 通过 Windows DPAPI 加密后存入总控本地数据库,业务查询和界面均不读取或回显明文;仅在已认证 TLS/WSS 会话内发送给目标 Agent。 +3. 已完成基础版:实现带版本的 config_snapshot / config_ack 协议。Agent 校验 LLM 配置、写入本机凭据库、原子切换运行器,并回传已应用或失败原因;离线 Agent 认证连接后自动补齐。后续补充签名、目标标签组和幂等 ID。 +4. 将总控定时器持久化并由总控触发任务下发;Agent 不保存独立 cron。 +5. 为 Skill/MCP 建立包仓库、哈希校验、权限清单、目标部署状态和回滚版本;Agent 只接受经总控签名的包。 +6. 为每台目标机器提供独立安装的 Agent 守护服务;总控可发送启动、停止、重启与升级命令,并记录确认与失败原因。 + +验收:管理员只在总控完成一次配置,即可推送给多个指定 Agent;离线 Agent 重连后补齐配置;任意配置、部署和重启动作均可追踪。 diff --git a/controller/docs/steps/14-MCP受控桌面工具闭环.md b/controller/docs/steps/14-MCP受控桌面工具闭环.md new file mode 100644 index 0000000..995e8ca --- /dev/null +++ b/controller/docs/steps/14-MCP受控桌面工具闭环.md @@ -0,0 +1,19 @@ +# 14:MCP 受控桌面工具闭环 + +## 已完成范围 + +1. 总控可为指定 Agent 启用内置 `desktop-filesystem` MCP;分配和单调递增的部署版本持久化在总控数据库,状态为 `pending`、`sent`、`applied` 或 `failed`。 +2. 在线 Agent 会立即收到带部署版本的认证 WSS 快照;离线 Agent 在认证重连后自动重收同一持久化分配。Agent 在本机保存启用状态和版本,并对重复快照幂等回执。 +3. 该工具只识别明确的“在桌面创建文件夹”任务,只能创建桌面直接子文件夹;已存在目标、路径穿越或非法名称会失败,绝不覆盖。命中该工具意图但工具未部署/未应用时也会明确失败,绝不降级给 LLM。 +4. 总控审计记录部署请求、快照下发、Agent 回执和真实工具结果,且不记录 API Key、配对凭据或完整任务提示词。 +5. Agent 管理页显示每台 Agent 的 LLM、Skill 与 MCP 应用状态;任务输出和审计均会移除模型 `` 内容。 + +## 验证 + +- Go WSS 协议测试覆盖 MCP 下发、持久化版本、过期回执忽略、认证重连重发、回执恢复、任务结果回传、审计和 `` 过滤。 +- 子 Agent pytest 覆盖桌面文件夹创建、未部署拒绝而不调用 LLM、路径穿越拒绝、版本持久化与重复快照回执。 +- Windows 端到端手工验收需使用唯一目录名,并确认总控的任务详情、审计记录和 Agent 本地状态均为本次部署版本;不得以 Skill 文本或 LLM 回复替代真实目录创建证据。 + +## 下一步 + +将单一内置 MCP 升级为带版本、权限清单、哈希、签名和回滚的受控工具仓库;插件运行时扩展仍保持独立入口。 diff --git a/controller/docs/steps/15-Agent规模化管理.md b/controller/docs/steps/15-Agent规模化管理.md new file mode 100644 index 0000000..e7820c9 --- /dev/null +++ b/controller/docs/steps/15-Agent规模化管理.md @@ -0,0 +1,19 @@ +# 15:Agent 规模化管理 + +## 已实现范围 + +1. Agent 目录由总控 SQLite 服务端分页,页大小限制为 1 至 50;名称搜索使用参数化 `LIKE` 查询并转义通配符。 +2. 支持组合筛选在线/离线、MCP、LLM 和 Skill 状态。Skill 状态会汇总为未选择、等待连接、下发中、已应用或失败;任一 Skill 失败优先显示失败。 +3. Agent 详情返回受限的最近任务、最近安全审计、部署历史和当前失败原因。详情记录有数量上限,且失败文本经过显示层脱敏;不返回 API Key、配对凭据或内部推理。 +4. 配置下发的认证确认优先于异步 LLM、Skill、MCP 快照,避免重连后出现未认证配置快照。 + +## 回归范围 + +- Go 测试覆盖分页、名称和状态组合筛选、非法筛选拒绝、失败原因脱敏,以及 Agent 详情的任务、审计和部署轨迹。 +- 原有 WSS/MCP 重连补发与幂等回执测试继续通过。 + +## 验收 + +1. 在“智能体”页面用名称与任意状态组合筛选,确认分页总数和卡片状态来自总控服务端。 +2. 打开任意卡片,确认可见最近任务、审计、部署历史和失败原因;敏感字段不得显示。 +3. 离线 Agent 的下发显示等待连接,重连后先收到认证确认,再恢复持久化配置快照。 diff --git a/controller/docs/steps/16-审计日志基础设施.md b/controller/docs/steps/16-审计日志基础设施.md new file mode 100644 index 0000000..2bbbc1e --- /dev/null +++ b/controller/docs/steps/16-审计日志基础设施.md @@ -0,0 +1,14 @@ +# 16:审计日志基础设施 + +## 已实现范围 + +1. 审计事件统一包含时间、操作者、目标 Agent、资源类型、动作、结果和安全摘要;保留旧字段,现有数据库会以加列和回填方式升级,不删除历史。 +2. 审计页面由总控服务端分页和筛选,支持按目标 Agent、资源类型、结果、起止时间查询。 +3. 已覆盖的实际链路包括:配对码创建、配对完成/撤销、Agent 改名、LLM 配置保存与下发、Skill 包保存与下发、MCP 部署与工具结果、任务创建/状态变更/取消/重试/历史清除。 +4. 所有详情经过安全摘要处理,去除内部推理并脱敏 API Key、密码、令牌、凭据等字段;任务审计不记录完整任务正文。 +5. 模板、定时任务、委派、插件包和远程运维的资源类型已预留。它们尚未实现业务动作,后续对应阶段必须调用统一 `recordAudit`,不得创建伪造审计记录。 + +## 验证 + +- Go 回归覆盖统一字段、按 Agent/资源/结果筛选、分页、时间格式校验和敏感详情脱敏。 +- 清除任务历史的回归继续验证:任务和时间线可删除,审计记录必须保留。 diff --git a/controller/docs/steps/17-多Agent并发任务中心.md b/controller/docs/steps/17-多Agent并发任务中心.md new file mode 100644 index 0000000..6744082 --- /dev/null +++ b/controller/docs/steps/17-多Agent并发任务中心.md @@ -0,0 +1,25 @@ +# 17:多 Agent 并发任务中心 + +## 目标 + +让一个操作者可在总控一次填写多条互不依赖的任务,分别指定目标机器并同时下发;每台机器的执行状态与结果在同一页可见。 + +## 已实现范围 + +1. 总控增加 `CreateTasks` 受限用例。每条输入仅包含目标 Agent、文本任务和超时;一次最多 20 条。 +2. 批量下发采用逐条独立结果:一条目标离线或参数无效时,仅该行返回可读错误,其他有效行仍会创建、持久化并下发。 +3. 控制台任务区支持添加、移除和编辑多条任务草稿;每条可单独选择在线 Agent 和超时。 +4. 新增“各机器任务状态”看板:按在线 Agent 汇总近期任务、运行中的数量和最新一条状态/结果,点击可打开原有完整时间线。 +5. 既有取消、重试、超时、结果回传和任务历史能力保持不变。任务没有被改成广播;相同行中填写同一台 Agent 也仍是多个独立任务。 + +## 验证 + +- Go WSS 回归覆盖两个任务均成功下发,同时验证其中一条失败不会阻止其它任务、空批量会被拒绝。 +- 子 Agent 回归覆盖两个独立模型任务同时进入执行,确认执行器不会把任务串行化。 +- Wails 前端绑定和生产构建通过;两端 Windows EXE 已重新生成。 + +## 当前边界 + +- 看板展示总控最近 24 条任务,适合日常多机派发与结果查看;它不是工作流或定时任务系统。 +- 任务仍只会下发给当前在线 Agent。离线任务队列与进程重启后的事件外盒仍是后续可靠性工作。 +- 当前不设置人为并发上限;操作时应根据每台机器的模型、CPU 和内存能力控制同机任务数量。 diff --git a/controller/docs/steps/18-自然多Agent指派.md b/controller/docs/steps/18-自然多Agent指派.md new file mode 100644 index 0000000..ff3d090 --- /dev/null +++ b/controller/docs/steps/18-自然多Agent指派.md @@ -0,0 +1,27 @@ +# 18:自然多 Agent 指派 + +## 目标 + +在不改变既有 WSS/TLS、任务取消、超时、重试、LLM、Skill 或受控 MCP 闭环的前提下,提供两个明确分离的任务入口:单独指派,以及公共 `@Agent名称` 批量指派。 + +## 已实现范围 + +1. 主导航依次提供“控制台、智能体、批量指派、单独指派”;批量指派不再放入控制台。控制台仅保留总览、各机器运行状态与统一任务记录。 +2. “单独指派”页面必须下拉选择一台在线 Agent,随后下发一条独立文本任务;状态、结果、失败、取消和重试仍在统一任务记录中处理。 +3. “批量指派”页面只提供公共 `@` 输入区,避免与“单独指派”重复。输入 `@` 会弹出在线 Agent 候选,支持名称过滤、方向键、回车确认和 Esc 关闭;点击在线 Agent 仍可插入 `@名称`,不依赖操作者记住显示名称。 +4. 每个非空行是一组独立任务。`@设计机 在桌面新建文件夹` 与下一行 `@文案机 新建文本文档` 会分别下发不同任务;`@设计机 @文案机 做发布检查` 会向两台机器创建两条内容相同的独立任务。 +5. 解析仅接受已登记 Agent 的完整显示名称和边界;未知名称、重名、离线目标、缺少任务内容和未指定目标均会显示逐项原因,绝不根据前缀或相似名称误发到其它机器。 +6. 两种入口都复用现有 `task_dispatch`、任务持久化和执行状态机,不引入对话上下文、附件、任意 Shell、远程终端或旧 FastAPI Spawn Agent。 +7. 用户任务在持久化或下发前拒绝 API Key、配对凭据、账号密码及 `` 内部推理,避免其进入界面、任务记录、日志、结果或审计摘要。 +8. 新任务默认使用 5 分钟无进展超时;Agent 回传有效执行进展时,总控会续期。可按任务选择更长窗口。 + +## 验证 + +- Go 回归覆盖按行 `@` 拆分、同一行多 Agent 的相同任务、未知名称、重名、离线目标和无目标文本的安全拒绝。 +- 子 Agent pytest 覆盖独立文本任务;既有并发、取消、超时和 MCP 测试保留。 +- `go test -count=1 ./...`、前端 `npm run build`、子 Agent `pytest` 与 `ruff` 均通过后,再重新生成两端 Windows EXE。 + +## 当前边界 + +- `@` 仅用于一次性批量任务调度,不会自动把未标注文本广播给任何 Agent;非空行必须以一个或多个 `@Agent名称` 开始。 +- 不支持附件、任意 Shell、远程终端、旧 FastAPI Spawn Agent 或跨 Agent 共享上下文。 diff --git a/controller/docs/steps/19-图片附件任务闭环.md b/controller/docs/steps/19-图片附件任务闭环.md new file mode 100644 index 0000000..304527a --- /dev/null +++ b/controller/docs/steps/19-图片附件任务闭环.md @@ -0,0 +1,13 @@ +# 19:图片附件任务闭环 + +## 已完成范围 + +1. 批量 `@` 指派和单独指派均支持粘贴或选择 PNG、JPEG、GIF、WebP;单项任务最多 10 张、原始总大小最多 12 MiB。 +2. 多条有效批量任务带图时,每行必须明确引用“图片 1”等编号;同一行多个 Agent 共享其编号图片,未知、离线、重名和未指定目标不会收到图片。 +3. 图片只保存在总控本地 `task_images`,通过目标 Agent 的认证 WSS `task_dispatch.attachments` 下发;任务详情显示数量与缩略图,重试复用原图,清除已结束任务同时删除附件。 +4. 审计、普通日志、任务文本、结果和错误均不写入图片 Base64。 +5. 批量与单独指派页均显示本页最近任务的实时状态;任务详情在附件尚未加载时保持可用,不会造成页面空白。 + +## 验证 + +- 未配置“支持图片理解”的模型会安全失败,绝不降级为纯文本。 diff --git a/controller/docs/steps/20-默认完全访问Agent.md b/controller/docs/steps/20-默认完全访问Agent.md new file mode 100644 index 0000000..c79aa8e --- /dev/null +++ b/controller/docs/steps/20-默认完全访问Agent.md @@ -0,0 +1,15 @@ +# 20. 默认完全访问 Agent + +## 产品决定 + +用户明确要求新版 Agent 默认拥有其所在电脑的完全访问执行能力。总控的“插件与 MCP 工具”与 Agent 详情页显示此状态;旧“桌面文件工具已应用”仅作为兼容部署记录,不代表新版权限上限。 + +## 可观察性 + +- Agent 任务若实际使用本机执行工具,会回传 `full-access` 执行来源并显示为“完全访问 Agent”。 +- 任务、日志和审计仍不保存密钥、密码、凭据、附件 Base64 或模型内部推理。 +- 配对、TLS 指纹、取消、无进展超时、重试和断线语义保持不变。 + +## 非目标 + +总控不直接连接 Agent 的本机命令行或文件系统。ImageForge 等第三方桌面应用仍需由 Agent 本机的独立 MCP 客户端连接,不能复制旧 FastAPI 插件到总控。 diff --git a/controller/docs/steps/README.md b/controller/docs/steps/README.md new file mode 100644 index 0000000..41f7ff4 --- /dev/null +++ b/controller/docs/steps/README.md @@ -0,0 +1,23 @@ +# Controller 实施步骤索引 + +按编号顺序执行本目录中的文档。每一步完成后应通过该文档的验收项并提交一次可回滚的变更;不要在协议、持久化和并发模型未确定前开始堆叠界面功能。 + +1. [01-项目骨架与运行基线](01-项目骨架与运行基线.md) +2. [02-共享协议与安全边界](02-共享协议与安全边界.md) +3. [03-存储与领域模型](03-存储与领域模型.md) +4. [04-通信与Agent注册](04-通信与Agent注册.md) +5. [05-任务调度与编排](05-任务调度与编排.md) +6. [06-Wails2管理界面](06-Wails2管理界面.md) +7. [07-迁移兼容与可靠性](07-迁移兼容与可靠性.md) +8. [08-测试发布与运维](08-测试发布与运维.md) +9. [09-单Agent任务闭环](09-单Agent任务闭环.md) +10. [10-Wails2迁移与信息架构](10-Wails2迁移与信息架构.md) +11. [11-Agent管理与配对生命周期](11-Agent管理与配对生命周期.md) +12. [12-任务取消超时与重试](12-任务取消超时与重试.md) +13. [13-集中配置与远程运维](13-集中配置与远程运维.md) +14. [14-MCP受控桌面工具闭环](14-MCP受控桌面工具闭环.md) +15. [15-Agent规模化管理](15-Agent规模化管理.md) +16. [16-审计日志基础设施](16-审计日志基础设施.md) +17. [17-多Agent并发任务中心](17-多Agent并发任务中心.md) +18. [18-自然多Agent指派](18-自然多Agent指派.md) +19. [19-图片附件任务闭环](19-图片附件任务闭环.md) diff --git a/controller/frontend/index.html b/controller/frontend/index.html new file mode 100644 index 0000000..ff14aee --- /dev/null +++ b/controller/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + MultiClawController + + +
+ + + + diff --git a/controller/frontend/package-lock.json b/controller/frontend/package-lock.json new file mode 100644 index 0000000..4ec080b --- /dev/null +++ b/controller/frontend/package-lock.json @@ -0,0 +1,1883 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.6.3", + "vite": "^7.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.2.tgz", + "integrity": "sha512-Xa6RDoWa+hNiX6PgsljlH6W75RaONx3y6PVlbLhkEWW+GaPQ3dP5gwbL/erAzQHWwkvW5UxdD5l87Qx2FAQ/4A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.2.tgz", + "integrity": "sha512-vNASxsghMfQ5s+v3PrpnJd+ryL/26lxCCaGI+sDJ7VzmHiYXIrrVltsDhaawxLM1WcoMU2oYlbPHLaYQtBzhcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.2.tgz", + "integrity": "sha512-0dWDjmlrpZAgjPD/aPzUDhBW8APLRjAni5bOrM76wiiZm+E+KTMVKNhAzaTBohz8UyO2fKNAl0+fygbe2HZXOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.2.tgz", + "integrity": "sha512-N58uktcwzk3+qT4KHEuNdIxX1N01RWrkfVoml69EAbSaNDL+sbNVLx2RMl4Qd23lpA0fgPvyh5hHb4weD5WKmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.2.tgz", + "integrity": "sha512-HWF2zH8EAp2scWRpt2PGe6iUGz7zi04waXsdRr3zb4DWCk2ImIo5FZu0jjmD53nP/DGSvnW0e7/1ToCNZs2lZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.2.tgz", + "integrity": "sha512-MkvcwHMnzPSMOQEwB6wHnLzmc+hT8BGc5bW/Mhmjjgx3wbj6VBnlc47XsK74kD0K9MikFfXpQqyz4NUXaUW62A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.2.tgz", + "integrity": "sha512-xe1bCKPJaKsD0tfd7Rb6bGfUogJTpKbTEEthsfdb7hTfTRNJVQTdirabQx0o6ERVba/smkM720soMY+0QnrlSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.2.tgz", + "integrity": "sha512-yOM7LdK0p6gk6+Q773OEwtlsikT1TL3yMmYsTtRlDRPha5vV2DC5x7LqRWDr6f3cSYNMKVqxzffXv8ivxNBIFQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.2.tgz", + "integrity": "sha512-qiWuJJV3DybA2IfzvRimeKXGrGuVPv1zobSY/26KnP3HbV0VcNb3ECzgvtbvF3xjSMkcooou6HASXZuLdjnhpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.2.tgz", + "integrity": "sha512-akcZquRzCY/KpUoZAMBhGf7oi4LmXq1BzRA5CPAC3rkUf28Y/sAYV3jSL+JKd7cwEyFvR5G0XVZ0gaMedP+60A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.2.tgz", + "integrity": "sha512-fNwYHrPyYyxauPzX/cpYw8Z7LQpp+DGA0KCoswA0aVFBpmdMil9XgjB8V3Ny64Ihu797+GKcuJqnsOKEmor7fA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.2.tgz", + "integrity": "sha512-XfvsgzR7DZqREdst7K1Mj3ilSUM5xLAHJcIMDFPKdxTs9q5VHOT8aMA+a683fqBu7DQl8+Sd9HCsQYL8EMY9qA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.2.tgz", + "integrity": "sha512-Pp7gVZggEFlbcuztay+/U0gVG9S1XAh8i7I1Re/htbAzo43P5wHZHw6pTyzotISqlKohoh9RpIfnOz3RbemK1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.2.tgz", + "integrity": "sha512-zkgL2xff6i7u5hau/m6FGeS8gRkLEdgLw522WGmdWWlLd9btmNl3S80mcEjtGq+kvgUekQ3+BOYLLLcPlS2LIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.2.tgz", + "integrity": "sha512-qOheJomrkVCbbHFJ7L3J97cnhfogKqguAQphv26+3ZsAQIF1L19b+dArl//s8rjJHJLz9byykyM8NBP4nmSa1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.2.tgz", + "integrity": "sha512-XlxLD54wQhH3FciCgMofxBw27NzUe818gJH410qWvc41UT0ZFcgxVjyX5/EK8MPTupjeVWqN5oy+9pCA9mqfCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.2.tgz", + "integrity": "sha512-vdryWeRb2bLJZf0Fv/W8se6nvsHe2PkTCxV0meheK3nQE+G90VCJcke51Miy1yQRsfm2uqIyjXOu4wmUzbTtkQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.2.tgz", + "integrity": "sha512-bcq2h2pkKmH2po4cZV8VWzO4lL40STyu/nLoFpYMQp9C2tCVNTdcVv86MwSsn3D5s1FBe2Ty1atqvVAUTMimNg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.2.tgz", + "integrity": "sha512-EGoo5DMVMRkTId8fuTDaoxVlR5ZTsKULUezRjd9gCw5eeY+DjCvDpZAOlNUvKPGX+7rS1RWx6j+yOpNPx0cUgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.2.tgz", + "integrity": "sha512-MErl12k7BFHZG1TI9QF/3lSSZARzq9KgNy/FjnqFMCkv+N4RSSzoUCA5h2mqHX4Mox3WaTVKblyzhQ1zRb2ZuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.2.tgz", + "integrity": "sha512-ILs8k07Wh4p0PsNY4wYLEaXZKMOpVhrG5QDB0yHhGhuzOfDlnyHN6sflL4El/MpUP1y8uY2lUZrv4oBS6pTT3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.2.tgz", + "integrity": "sha512-hKgB3nz/TKD3Wv78XEsyXzQsNjvhOHmwKQTvXADGOyU/cIClZDO7DsoggbdmJDPGp5V80tA3Vfv61PaKTLH3LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.2.tgz", + "integrity": "sha512-T4wf1mudIDxN8Q/CWIBJC1u5gQUc+r5mPvlwoSbIvNkyVTP2TAFeobEmst5AQ4gMyAz4sSByVdoTDfvTmGK/8g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.2.tgz", + "integrity": "sha512-tC3IY7qoaD9Ll3/8WJQn49j5V2f/NuI9S41NOE2iM5MPs3sPIvOkVToLcz/7Bz4pyF7PSvrtwu8I/pUrGOSecQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.2.tgz", + "integrity": "sha512-6NHnk/K3eq2ZFYcU1X8g67s9qIJRCOTT92gwLMVBp08dB2uuuwI1/Q/empzL2Bfr2f2WRLJVwpp90RmacQyFkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz", + "integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.427", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", + "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.2.tgz", + "integrity": "sha512-l5eyksV4tPBj6lJyEa37YzIOCSOV7lkZzEHUdpjWZbtD7wTcFYmEYXSgm5bT4vV+dZLb9rBG1W9GROOG4NS4Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.2", + "@rollup/rollup-android-arm64": "4.63.2", + "@rollup/rollup-darwin-arm64": "4.63.2", + "@rollup/rollup-darwin-x64": "4.63.2", + "@rollup/rollup-freebsd-arm64": "4.63.2", + "@rollup/rollup-freebsd-x64": "4.63.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.2", + "@rollup/rollup-linux-arm-musleabihf": "4.63.2", + "@rollup/rollup-linux-arm64-gnu": "4.63.2", + "@rollup/rollup-linux-arm64-musl": "4.63.2", + "@rollup/rollup-linux-loong64-gnu": "4.63.2", + "@rollup/rollup-linux-loong64-musl": "4.63.2", + "@rollup/rollup-linux-ppc64-gnu": "4.63.2", + "@rollup/rollup-linux-ppc64-musl": "4.63.2", + "@rollup/rollup-linux-riscv64-gnu": "4.63.2", + "@rollup/rollup-linux-riscv64-musl": "4.63.2", + "@rollup/rollup-linux-s390x-gnu": "4.63.2", + "@rollup/rollup-linux-x64-gnu": "4.63.2", + "@rollup/rollup-linux-x64-musl": "4.63.2", + "@rollup/rollup-openbsd-x64": "4.63.2", + "@rollup/rollup-openharmony-arm64": "4.63.2", + "@rollup/rollup-win32-arm64-msvc": "4.63.2", + "@rollup/rollup-win32-ia32-msvc": "4.63.2", + "@rollup/rollup-win32-x64-gnu": "4.63.2", + "@rollup/rollup-win32-x64-msvc": "4.63.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/controller/frontend/package.json b/controller/frontend/package.json new file mode 100644 index 0000000..f4ae2b4 --- /dev/null +++ b/controller/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.6.3", + "vite": "^7.0.0" + } +} \ No newline at end of file diff --git a/controller/frontend/package.json.md5 b/controller/frontend/package.json.md5 new file mode 100644 index 0000000..56e9e6b --- /dev/null +++ b/controller/frontend/package.json.md5 @@ -0,0 +1 @@ +ebabc2ea396231dd6d5148053c60fb95 \ No newline at end of file diff --git a/controller/frontend/src/App.css b/controller/frontend/src/App.css new file mode 100644 index 0000000..56397a2 --- /dev/null +++ b/controller/frontend/src/App.css @@ -0,0 +1,29 @@ +:root { color: #e8edf5; background: #101827; font-family: Inter, "Segoe UI", sans-serif; } +* { box-sizing: border-box; } +.shell { min-height: 100vh; padding: 32px; background: radial-gradient(circle at 10% 0%, #20365a 0, transparent 32%), #101827; } +.topbar, .panel-heading, .task-title, .topbar-right, .overview-panel, .agent { display: flex; align-items: center; justify-content: space-between; gap: 16px; } +h1, h2, p { margin: 0; } h1 { font-size: 30px; letter-spacing: -.7px; } h2 { font-size: 18px; } .eyebrow { color: #8ba8d8; font-size: 11px; font-weight: 700; letter-spacing: 1.5px; margin-bottom: 6px; } +.listener { color: #b7c6dc; font-size: 14px; }.pulse { display: inline-block; width: 9px; height: 9px; border-radius: 100%; background: #46d99a; box-shadow: 0 0 14px #46d99a; } +.topbar-right { justify-content: flex-end; }.navigation { display: flex; flex-wrap: wrap; gap: 8px; margin: 24px 0 4px; border-bottom: 1px solid #2a3a55; }.navigation button { border-radius: 8px 8px 0 0; background: transparent; color: #9dafc8; padding: 11px 15px; }.navigation button:hover { background: #1c2c45; color: #dfeaff; }.navigation button.active { background: #273f65; color: #f5f8ff; } +.message { margin: 18px 0 0; padding: 11px 14px; border-radius: 9px; font-size: 14px; }.notice { background: #183d38; color: #b2f1cf; }.error { background: #532d39; color: #ffc5d0; } +.summary-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin: 24px 0; }.summary-grid article, .panel { border: 1px solid #2a3a55; background: rgba(23, 34, 53, .94); border-radius: 14px; box-shadow: 0 18px 45px rgba(0,0,0,.16); }.summary-grid article { padding: 18px; }.summary-grid span { display: block; color: #9dafc8; font-size: 13px; }.summary-grid strong { display: block; margin-top: 8px; font-size: 25px; }.summary-grid .address { color: #c9daff; font-size: 16px; overflow-wrap: anywhere; } +.workspace { display: grid; grid-template-columns: 1fr 1.2fr; gap: 18px; }.panel { padding: 20px; margin-top: 18px; }.panel-heading { margin-bottom: 15px; }.panel p { color: #aebbd0; line-height: 1.5; font-size: 14px; }.overview-panel { min-height: 124px; }.overview-panel p { max-width: 650px; margin-top: 8px; }.agent-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px; }.agent { justify-content: flex-start; border: 1px solid #30425e; padding: 12px; border-radius: 9px; }.agent div { flex: 1; }.agent strong, .agent code { display: block; }.agent code { color: #8195b4; font-size: 11px; margin-top: 4px; }.agent > span:last-child { color: #9ceed0; font-size: 12px; } +button { border: 0; border-radius: 8px; padding: 10px 14px; background: #6f9df5; color: #071323; font-weight: 700; cursor: pointer; } button:hover { background: #91b7ff; } button:disabled { cursor: not-allowed; opacity: .45; }.quiet { background: #263953; color: #d5e1f5; } +textarea, select, input { display: block; width: 100%; margin-top: 7px; border: 1px solid #3b4c68; border-radius: 8px; color: #e8edf5; background: #111b2a; padding: 10px; font: inherit; outline: none; } textarea:focus, select:focus, input:focus { border-color: #81aaff; box-shadow: 0 0 0 3px rgba(129,170,255,.15); } select { height: 40px; } label { display: block; color: #c8d4e9; font-weight: 600; font-size: 13px; margin-top: 13px; } .pairing-code-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; color: #c8d4e9; font-size: 13px; font-weight: 600; }.pairing-code-actions button { padding: 7px 11px; }.pairing-code { height: 125px; resize: none; margin-top: 8px; font-family: ui-monospace, Consolas, monospace; font-size: 12px; }.task-panel textarea { min-height: 108px; resize: vertical; }.hint { margin-top: 12px; color: #8292aa !important; font-size: 12px !important; } +.task-history { margin-top: 18px; }.task-history-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 10px; }.task-history-actions button { padding: 7px 11px; }.task-order { color: #8292aa; font-size: 12px; }.task-drafts { display: grid; gap: 12px; margin-top: 16px; }.task-draft { min-width: 0; margin: 0; padding: 13px; border: 1px solid #30425e; border-radius: 10px; background: #111b2a; }.task-draft legend { padding: 0 6px; color: #c9daff; font-size: 12px; font-weight: 700; }.task-draft-heading, .task-draft-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }.task-draft-heading span, .task-draft-actions span { color: #9dafc8; font-size: 12px; }.task-draft-heading button, .task-draft-actions button { padding: 7px 11px; }.task-draft-fields { display: grid; grid-template-columns: minmax(220px, 1fr) minmax(150px, .45fr); gap: 12px; }.task-draft textarea { min-height: 88px; }.task-draft-error { margin: 10px 0 0 !important; color: #ffbeca !important; }.task-draft-actions { justify-content: flex-start; margin-top: 14px; }.agent-task-board { margin-top: 18px; }.agent-task-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px; }.agent-task-status { display: grid; gap: 9px; min-width: 0; padding: 14px; border: 1px solid #30425e; border-radius: 10px; background: #142035; }.agent-task-status > div { display: flex; align-items: center; gap: 9px; }.agent-task-status .status-dot { margin: 0; }.agent-task-status strong { color: #eef3fb; }.agent-task-status p, .agent-task-status small { margin: 0; color: #9dafc8; font-size: 12px; }.agent-task-status p b { color: #f7df90; }.agent-task-status button { display: grid; grid-template-columns: auto 1fr; align-items: start; gap: 8px; width: 100%; overflow: hidden; text-align: left; font: inherit; font-weight: 400; }.agent-task-status button .badge { white-space: nowrap; }.task-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; }.task-card { display: flex; flex-direction: column; min-height: 225px; border: 1px solid #30425e; background: linear-gradient(145deg, #172640, #121e31); border-radius: 12px; overflow: hidden; transition: transform .15s ease, border-color .15s ease, box-shadow: 0 12px 30px rgba(0,0,0,.2); }.task-card:hover { border-color: #638ac9; box-shadow: 0 12px 30px rgba(0,0,0,.2); transform: translateY(-2px); }.task-card-main { display: block; flex: 1; width: 100%; padding: 15px; border: 0; border-radius: 0; color: inherit; background: transparent; text-align: left; font: inherit; font-weight: inherit; }.task-card-main:hover { background: rgba(111,157,245,.06); }.task-card-main:focus-visible { outline: 2px solid #91b7ff; outline-offset: -3px; }.badge { padding: 4px 8px; border-radius: 99px; background: #283c59; color: #b9cbe7; font-size: 12px; }.badge.completed { background: #174a3d; color: #9ceed0; }.badge.failed, .badge.cancelled, .badge.timed_out { background: #562c3d; color: #ffbeca; }.badge.running, .badge.cancelling { background: #4f421e; color: #f7df90; }.task-title time { color: #879bb9; font-size: 11px; }.instruction { color: #eef3fb !important; font-weight: 600; margin: 13px 0 7px !important; }.task-agent { color: #c9daff !important; font-size: 12px !important; margin: 5px 0 !important; }.result { color: #acb9cc !important; white-space: pre-wrap; overflow-wrap: anywhere; }.task-card .result { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }.task-card-link { display: block; margin-top: 14px; color: #91b7ff; font-size: 12px; font-weight: 700; }.task-actions { display: flex; justify-content: flex-end; gap: 8px; min-height: 48px; padding: 0 14px 12px; }.empty { padding: 18px 0; }.connection-detail { display: grid; grid-template-columns: 130px 1fr; gap: 9px 16px; margin: 18px 0; padding: 14px; background: #111b2a; border: 1px solid #30425e; border-radius: 9px; }.connection-detail span { color: #9dafc8; font-size: 13px; }.connection-detail strong { overflow-wrap: anywhere; color: #dce8ff; }.address-copy { display: flex; align-items: center; gap: 10px; min-width: 0; }.address-copy strong { min-width: 0; flex: 1; }.address-copy button { flex: 0 0 auto; padding: 7px 11px; } +.management-intro { margin-bottom: 16px; }.management-list { display: grid; gap: 12px; }.managed-agent { padding: 15px; border: 1px solid #30425e; background: #142035; border-radius: 10px; }.managed-agent-heading { display: flex; align-items: flex-start; gap: 11px; }.managed-agent-heading > div { flex: 1; }.managed-agent-heading input { width: min(360px, 100%); background: transparent; border: 0; border-bottom: 1px solid transparent; color: #eef3fb; font: 600 16px Inter, "Segoe UI", sans-serif; padding: 2px 0; }.managed-agent-heading input:focus { outline: none; border-bottom-color: #81aaff; }.managed-agent-heading code { display: block; color: #8195b4; font-size: 11px; margin-top: 5px; }.status-dot { width: 10px; height: 10px; margin-top: 7px; border-radius: 50%; background: #7c8aa0; }.status-dot.online { background: #46d99a; box-shadow: 0 0 12px #46d99a; }.status-dot.revoked { background: #ec6f84; }.status-badge { padding: 4px 8px; border-radius: 99px; background: #283c59; color: #b9cbe7; font-size: 12px; }.status-badge.online { background: #174a3d; color: #9ceed0; }.status-badge.revoked { background: #562c3d; color: #ffbeca; }.agent-meta, .agent-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 14px; }.agent-meta { color: #9dafc8; font-size: 12px; }.agent-actions { justify-content: flex-end; }.danger { background: #a84a61; color: #fff; }.danger:hover { background: #cd5c78; } +.management-intro { margin-bottom: 16px; }.management-list { display: grid; gap: 12px; }.managed-agent { padding: 15px; border: 1px solid #30425e; background: #142035; border-radius: 10px; }.managed-agent-heading { display: flex; align-items: flex-start; gap: 11px; }.managed-agent-heading > div { flex: 1; }.managed-agent-heading input { width: min(360px, 100%); background: transparent; border: 0; border-bottom: 1px solid transparent; color: #eef3fb; font: 600 16px Inter, "Segoe UI", sans-serif; padding: 2px 0; }.managed-agent-heading input:focus { outline: none; border-bottom-color: #81aaff; }.managed-agent-heading code { display: block; color: #8195b4; font-size: 11px; margin-top: 5px; }.status-dot { width: 10px; height: 10px; margin-top: 7px; border-radius: 50%; background: #7c8aa0; }.status-dot.online { background: #46d99a; box-shadow: 0 0 12px #46d99a; }.status-dot.revoked { background: #ec6f84; }.status-badge { padding: 4px 8px; border-radius: 99px; background: #283c59; color: #b9cbe7; font-size: 12px; }.status-badge.online { background: #174a3d; color: #9ceed0; }.status-badge.revoked { background: #562c3d; color: #ffbeca; }.agent-meta, .agent-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 14px; }.agent-meta { color: #9dafc8; font-size: 12px; }.agent-actions { justify-content: flex-end; }.danger { background: #a84a61; color: #fff; }.danger:hover { background: #cd5c78; }.developing-page { min-height: 230px; display: grid; align-content: center; gap: 10px; }.developing-page > p { color: #9ceed0; font-size: 24px; font-weight: 700; }.developing-page > span { color: #9dafc8; max-width: 680px; line-height: 1.6; } +.management-intro { margin-bottom: 16px; }.management-list { display: grid; gap: 12px; }.managed-agent { padding: 15px; border: 1px solid #30425e; background: #142035; border-radius: 10px; }.managed-agent-heading { display: flex; align-items: flex-start; gap: 11px; }.managed-agent-heading > div { flex: 1; }.managed-agent-heading input { width: min(360px, 100%); background: transparent; border: 0; border-bottom: 1px solid transparent; color: #eef3fb; font: 600 16px Inter, "Segoe UI", sans-serif; padding: 2px 0; }.managed-agent-heading input:focus { outline: none; border-bottom-color: #81aaff; }.managed-agent-heading code { display: block; color: #8195b4; font-size: 11px; margin-top: 5px; }.status-dot { width: 10px; height: 10px; margin-top: 7px; border-radius: 50%; background: #7c8aa0; }.status-dot.online { background: #46d99a; box-shadow: 0 0 12px #46d99a; }.status-dot.revoked { background: #ec6f84; }.status-badge { padding: 4px 8px; border-radius: 99px; background: #283c59; color: #b9cbe7; font-size: 12px; }.status-badge.online { background: #174a3d; color: #9ceed0; }.status-badge.revoked { background: #562c3d; color: #ffbeca; }.agent-meta, .agent-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 14px; }.agent-meta { color: #9dafc8; font-size: 12px; }.agent-actions { justify-content: flex-end; }.danger { background: #a84a61; color: #fff; }.danger:hover { background: #cd5c78; }.developing-page { min-height: 230px; display: grid; align-content: center; gap: 10px; }.developing-page > p { color: #9ceed0; font-size: 24px; font-weight: 700; }.developing-page > span { color: #9dafc8; max-width: 680px; line-height: 1.6; } +.llm-panel { margin-bottom: 28px; }.llm-grid { display: grid; grid-template-columns: minmax(280px, .95fr) minmax(260px, 1.05fr); gap: 18px; margin-top: 18px; }.llm-form, .llm-directory, .deployment, .deployment-status { border: 1px solid #30425e; background: #142035; border-radius: 10px; padding: 16px; }.llm-form h3, .llm-directory h3, .deployment h3, .deployment-status h3 { margin: 0; font-size: 15px; }.llm-form button { margin-top: 16px; width: 100%; }.profile-list { display: grid; gap: 9px; margin-top: 13px; max-height: 350px; overflow: auto; }.profile { display: grid; gap: 4px; padding: 11px; border: 1px solid #334967; border-radius: 8px; background: #111b2a; }.profile span, .profile small { color: #9dafc8; font-size: 12px; overflow-wrap: anywhere; }.profile code { color: #c9daff; font-size: 12px; }.profile em { color: #9ceed0; font-style: normal; font-size: 12px; }.deployment { display: grid; grid-template-columns: 1fr minmax(220px, .8fr); gap: 15px 20px; margin-top: 18px; align-items: end; }.deployment > label { margin: 0; }.deployment > button { justify-self: end; }.agent-checklist { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px; }.agent-choice { display: grid; grid-template-columns: auto 1fr; column-gap: 9px; align-items: center; margin: 0; padding: 10px; border: 1px solid #30425e; border-radius: 8px; background: #111b2a; }.agent-choice input { display: inline-block; width: 16px; height: 16px; margin: 0; padding: 0; accent-color: #6f9df5; }.agent-choice small { grid-column: 2; color: #9dafc8; font-weight: 400; margin-top: 3px; }.deployment-status { margin-top: 18px; }.deployment-status h3 { margin-bottom: 10px; }.deployment-status-row { display: grid; grid-template-columns: minmax(130px, .55fr) minmax(180px, 1fr) auto; gap: 10px; align-items: center; padding: 10px 0; border-top: 1px solid #30425e; font-size: 13px; }.deployment-status-row span, .deployment-status-row small { color: #9dafc8; }.deployment-status-row em { font-style: normal; border-radius: 99px; padding: 4px 8px; background: #283c59; color: #b9cbe7; }.deployment-status-row em.applied { color: #9ceed0; background: #174a3d; }.deployment-status-row em.failed { color: #ffbeca; background: #562c3d; } +.skill-content { min-height: 250px; resize: vertical; font-family: ui-monospace, Consolas, monospace; font-size: 12px; } +.skill-browser { display: grid; grid-template-columns: minmax(250px, .55fr) minmax(360px, 1.45fr); gap: 14px; margin-top: 18px; }.skill-list, .skill-preview { border: 1px solid #30425e; border-radius: 10px; background: #111b2a; overflow: auto; }.skill-list { max-height: 560px; }.skill-row { display: grid; width: 100%; gap: 4px; padding: 13px 14px; border: 0; border-bottom: 1px solid #30425e; border-radius: 0; background: transparent; color: #dbe7fa; text-align: left; font-weight: 400; }.skill-row:last-child { border-bottom: 0; }.skill-row:hover, .skill-row.selected { background: #1d3454; }.skill-row.selected { box-shadow: inset 3px 0 #81aaff; }.skill-row strong { color: #eef3fb; font-size: 14px; }.skill-row span, .skill-row small { color: #a7bad8; font-size: 12px; }.skill-preview { min-height: 400px; padding: 16px; }.skill-preview-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid #30425e; }.skill-preview-heading h3 { margin: 0; color: #eef3fb; font-size: 15px; }.skill-preview-heading div span, .skill-preview-heading > span { display: block; margin-top: 5px; color: #9dafc8; font-size: 12px; }.skill-preview pre { max-height: 465px; overflow: auto; margin: 14px 0 0; padding: 14px; border: 1px solid #30425e; border-radius: 8px; background: #0e1726; color: #d3deef; white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.6 ui-monospace, Consolas, monospace; } +.skill-directory-toolbar { display: flex; align-items: end; justify-content: space-between; gap: 16px; margin: 16px 0 -2px; }.skill-directory-toolbar label { width: min(400px, 100%); margin: 0; }.skill-directory-toolbar span { padding-bottom: 11px; color: #9dafc8; font-size: 12px; white-space: nowrap; }.skill-search-empty { padding: 18px 14px; color: #9dafc8 !important; } +.deployment-summary { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 14px; padding-top: 12px; border-top: 1px solid #30425e; color: #c9daff; font-size: 12px; }.deployment-summary small { width: 100%; color: #ffbeca; }.audit-list { display: grid; gap: 8px; margin-top: 16px; }.audit-row { display: grid; grid-template-columns: minmax(190px, 1fr) auto; gap: 5px 12px; padding: 12px; border: 1px solid #30425e; border-radius: 8px; background: #142035; font-size: 13px; }.audit-row span { color: #9ceed0; }.audit-row small, .audit-row time { color: #9dafc8; }.audit-row p { grid-column: 1 / -1; font-size: 12px; } +.audit-filter-toolbar { display: flex; align-items: end; flex-wrap: wrap; gap: 12px; margin: 16px 0; }.audit-filter-toolbar label { width: min(190px, 100%); margin: 0; }.audit-filter-toolbar span { padding-bottom: 11px; color: #9dafc8; font-size: 12px; white-space: nowrap; } +.agent-capability { margin-top: 14px; padding: 14px; border: 1px solid #30425e; border-radius: 9px; background: #111b2a; }.agent-capability h3 { margin: 0 0 6px; font-size: 14px; }.agent-capability p { margin: 0 0 10px; color: #9dafc8; font-size: 12px; }.agent-capability button { margin-top: 12px; }.execution-source { color: #9ceed0 !important; font-size: 12px !important; margin: 9px 0 !important; } +.agent-directory-toolbar { display: flex; align-items: end; flex-wrap: wrap; gap: 12px; margin: 16px 0 18px; }.agent-directory-toolbar label { width: min(190px, 100%); margin: 0; }.agent-directory-toolbar label:first-child { width: min(320px, 100%); }.agent-directory-toolbar span { padding-bottom: 11px; color: #9dafc8; font-size: 12px; white-space: nowrap; }.directory-error { color: #ffbeca !important; }.directory-pagination { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 18px; color: #9dafc8; font-size: 12px; }.agent-card-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 13px; }.agent-card { overflow: hidden; border: 1px solid #30425e; border-radius: 11px; background: linear-gradient(145deg, #172640, #121e31); transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease; }.agent-card:hover { border-color: #638ac9; box-shadow: 0 12px 30px rgba(0,0,0,.2); transform: translateY(-2px); }.agent-card-main { display: block; width: 100%; min-height: 172px; padding: 16px; border-radius: 0; background: transparent; color: inherit; text-align: left; font: inherit; font-weight: inherit; }.agent-card-main:hover { background: rgba(111,157,245,.06); }.agent-card-main:focus-visible { outline: 2px solid #91b7ff; outline-offset: -3px; }.agent-card-heading { display: grid; grid-template-columns: auto 1fr auto; align-items: start; gap: 11px; }.agent-card-heading .status-dot { margin-top: 5px; }.agent-card-heading strong, .agent-card-heading small { display: block; }.agent-card-heading strong { color: #eef3fb; font-size: 16px; }.agent-card-heading small { margin-top: 6px; color: #9dafc8; font-size: 11px; }.agent-card-summary { display: grid; gap: 6px; margin-top: 16px; padding-top: 12px; border-top: 1px solid #30425e; color: #c9daff; font-size: 12px; }.agent-card-link { display: block; margin-top: 14px; color: #91b7ff; font-size: 12px; font-weight: 700; }.agent-detail-modal { width: min(900px, 100%); }.agent-detail-modal .managed-agent { margin-top: 18px; }.agent-operational-detail { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 16px; }.agent-operational-detail > section { min-width: 0; padding: 14px; border: 1px solid #30425e; border-radius: 9px; background: #111b2a; }.agent-operational-detail h3 { margin: 0 0 8px; font-size: 14px; }.agent-operational-detail p { margin: 0; color: #9dafc8; font-size: 12px; }.agent-detail-list { display: grid; gap: 7px; }.agent-detail-list article { display: grid; gap: 4px; padding: 9px; border: 1px solid #30425e; border-radius: 7px; background: #0e1726; }.agent-detail-list strong { color: #dce8ff; font-size: 12px; overflow-wrap: anywhere; }.agent-detail-list span, .agent-detail-list small { color: #9dafc8; font-size: 11px; overflow-wrap: anywhere; }.failure-list small { color: #ffbeca; } +.task-modal-backdrop { position: fixed; z-index: 20; inset: 0; display: grid; place-items: center; padding: 24px; background: rgba(4, 10, 20, .74); }.task-modal { width: min(760px, 100%); max-height: min(820px, calc(100vh - 48px)); overflow: auto; padding: 22px; border: 1px solid #45618d; border-radius: 14px; background: #142035; box-shadow: 0 24px 75px rgba(0,0,0,.46); }.task-modal-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }.task-detail-meta { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0; }.task-detail-meta > span:not(.badge) { padding: 5px 8px; border-radius: 6px; background: #111b2a; color: #aebbd0; font-size: 12px; overflow-wrap: anywhere; }.task-detail-section { margin-top: 20px; }.task-detail-section h3 { margin: 0 0 8px; color: #dce8ff; font-size: 14px; }.task-detail-section pre { max-height: 260px; overflow: auto; margin: 0; padding: 12px; border: 1px solid #30425e; border-radius: 8px; background: #0e1726; color: #cbd8ec; white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.55 ui-monospace, Consolas, monospace; }.task-timeline { display: grid; gap: 9px; margin: 0; padding: 0; list-style: none; }.task-timeline li { display: grid; grid-template-columns: 150px 92px 1fr; gap: 10px; padding: 11px; border: 1px solid #30425e; border-radius: 8px; background: #111b2a; color: #b9c8de; font-size: 12px; }.task-timeline time { color: #8292aa; }.task-timeline strong { color: #9ceed0; }.task-timeline span { overflow-wrap: anywhere; } +.mention-panel { margin-bottom: 20px; }.mention-picker, .mention-selected { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 14px 0; }.mention-picker button { padding: 6px 9px; font-size: 12px; }.mention-selected { color: #9dafc8; font-size: 12px; }.mention-selected strong { padding: 4px 8px; border-radius: 99px; background: #1d3454; color: #bcd5ff; }.mention-compose, .direct-assignment-fields { display: grid; grid-template-columns: 1fr 180px; gap: 14px; align-items: end; }.mention-compose textarea { min-height: 130px; }.mention-results { display: grid; gap: 5px; margin: 13px 0 0; font-size: 12px; color: #aebbd0; }.mention-results p { margin: 0; }.direct-assignment { max-width: 820px; }.direct-assignment > label { display: block; margin-top: 15px; }.direct-assignment textarea { min-height: 160px; } +.mention-editor { position: relative; min-width: 0; }.mention-editor textarea { width: 100%; }.mention-suggestions { position: absolute; z-index: 4; left: 0; top: calc(100% + 5px); width: min(360px, 100%); overflow: hidden; border: 1px solid #45618d; border-radius: 9px; background: #162339; box-shadow: 0 12px 28px #050a14aa; }.mention-suggestions button { width: 100%; display: flex; align-items: center; gap: 7px; padding: 9px 11px; border: 0; border-radius: 0; color: #e3ecfa; background: transparent; text-align: left; }.mention-suggestions button:hover, .mention-suggestions button.active { background: #284d7d; }.mention-suggestions small { margin-left: auto; color: #9dafc8; }.image-attachments { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; margin-top: 12px; color: #9dafc8; font-size: 12px; }.image-attachments figure { position: relative; display: grid; gap: 3px; width: 76px; margin: 0; color: #cbd8ec; text-align: center; }.image-attachments img { width: 76px; height: 58px; object-fit: cover; border: 1px solid #45618d; border-radius: 6px; }.image-attachments figure button { position: absolute; top: -6px; right: -6px; width: 19px; height: 19px; padding: 0; border-radius: 50%; background: #692d3d; } + .assignment-activity { margin-top: 20px; }.activity-row { display: grid; grid-template-columns: auto 100px minmax(180px, 1fr) minmax(250px, auto); gap: 10px; align-items: center; width: 100%; text-align: left; }.activity-row strong { color: #dce8ff; }.activity-row span:last-of-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.activity-row small { color: #aebbd0; white-space: nowrap; } +@media (max-width: 780px) { .shell { padding: 18px; }.summary-grid, .workspace, .llm-grid, .deployment, .skill-browser, .agent-operational-detail, .mention-compose, .direct-assignment-fields { grid-template-columns: 1fr; }.topbar { align-items: flex-start; flex-direction: column; }.deployment > button { justify-self: stretch; }.deployment-status-row { grid-template-columns: 1fr; gap: 5px; } } diff --git a/controller/frontend/src/App.tsx b/controller/frontend/src/App.tsx new file mode 100644 index 0000000..c2f2d98 --- /dev/null +++ b/controller/frontend/src/App.tsx @@ -0,0 +1,573 @@ +import {FormEvent, useEffect, useRef, useState} from "react"; +import "./App.css"; +import {AgentDeploymentStatuses, AgentDirectory, AgentLLMStatuses, AgentOperationalDetails, AgentSkillStatuses, Agents, AuditTrail, CancelTask, ClearTaskHistory, CreatePairingCode, CreateTaskWithImages, DeployLLMProfile, DispatchMentionsWithImages, EnableDesktopFilesystem, LLMProfiles, OnlineAgents, RecentTasks, RenameAgent, RetryTask, RevokeAgent, SaveLLMProfile, SetAgentSkills, SkillDocument, Skills, Snapshot, TaskDetails} from "../wailsjs/go/main/App"; +import {controlplane} from "../wailsjs/go/models"; + +type Page = "overview" | "agents" | "batch" | "direct" | "skills" | "plugins" | "keys" | "audit" | "settings"; +type PastedImage = {id: string; name: string; dataURL: string}; + +const navigation: Array<{page: Page; label: string}> = [ + {page: "overview", label: "控制台"}, + {page: "agents", label: "智能体"}, + {page: "batch", label: "批量指派"}, + {page: "direct", label: "单独指派"}, + {page: "skills", label: "技能"}, + {page: "plugins", label: "插件"}, + {page: "keys", label: "密钥"}, + {page: "audit", label: "审计日志"}, + {page: "settings", label: "设置"}, +]; + +const statusLabel: Record = {queued: "等待下发", accepted: "已接收", running: "执行中", cancelling: "正在取消", completed: "已完成", failed: "失败", cancelled: "已取消", timed_out: "已超时"}; +const mcpStatusLabel: Record = {pending: "等待连接", sent: "下发中", applied: "已应用", failed: "应用失败", "未启用": "未启用"}; + +function App() { + const [page, setPage] = useState("overview"); + const [snapshot, setSnapshot] = useState(); + const [agents, setAgents] = useState([]); + const [managedAgents, setManagedAgents] = useState([]); + const [tasks, setTasks] = useState([]); + const [llmProfiles, setLLMProfiles] = useState([]); + const [agentLLMStatuses, setAgentLLMStatuses] = useState([]); + const [skills, setSkills] = useState([]); + const [selectedSkillDocument, setSelectedSkillDocument] = useState(); + const [skillDocumentLoading, setSkillDocumentLoading] = useState(false); + const [agentSkillStatuses, setAgentSkillStatuses] = useState([]); + const [agentDeploymentStatuses, setAgentDeploymentStatuses] = useState([]); + const [pairingCode, setPairingCode] = useState(""); + const [notice, setNotice] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const [selectedTaskDetail, setSelectedTaskDetail] = useState(); + const [taskDetailLoading, setTaskDetailLoading] = useState(false); + + const refresh = async () => { + try { + const [nextSnapshot, nextAgents, nextTasks, nextManagedAgents, nextProfiles, nextLLMStatuses, nextSkills, nextSkillStatuses, nextDeployments] = await Promise.all([Snapshot(), OnlineAgents(), RecentTasks(), Agents(), LLMProfiles(), AgentLLMStatuses(), Skills(), AgentSkillStatuses(), AgentDeploymentStatuses()]); + setSnapshot(nextSnapshot); setAgents(nextAgents); setTasks(nextTasks); setManagedAgents(nextManagedAgents); setLLMProfiles(nextProfiles); setAgentLLMStatuses(nextLLMStatuses); setSkills(nextSkills); setAgentSkillStatuses(nextSkillStatuses); setAgentDeploymentStatuses(nextDeployments); + setError(""); + } catch (reason) { setError(String(reason)); } + }; + + useEffect(() => { + void refresh(); + const timer = window.setInterval(() => void refresh(), 2000); + return () => window.clearInterval(timer); + }, []); + + const createPairingCode = async () => { + setLoading(true); + try { + setPairingCode(await CreatePairingCode()); + setNotice("已生成一次性配对码,有效期为 10 分钟。请立即复制到要加入的子 Agent。"); + setError(""); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const dispatchMentions = async (message: string, timeoutSeconds: number, images: PastedImage[]) => { + setLoading(true); + try { + const response = await DispatchMentionsWithImages(message, timeoutSeconds, images); + const succeeded = response.tasks.filter(item => item.taskID).length; + const failed = response.tasks.filter(item => item.error).length + response.issues.length; + setNotice(failed > 0 ? `${succeeded} 条 @ 指派已下发;${failed} 项未下发,请查看提示。` : `${succeeded} 条 @ 指派已独立下发,正在等待各 Agent 回传。`); + if (failed > 0) setError([...response.issues, ...response.tasks.filter(item => item.error).map(item => `@${item.mention}:${item.error}`)].join("\n")); + else setError(""); + await refresh(); + return response; + } catch (reason) { setError(String(reason)); throw reason; } + finally { setLoading(false); } + }; + + const dispatchDirectTask = async (agentID: string, instruction: string, timeoutSeconds: number, images: PastedImage[]) => { + setLoading(true); + try { + const taskID = await CreateTaskWithImages(agentID, instruction, timeoutSeconds, images); + setNotice(`已向选定 Agent 下发独立任务 ${taskID.slice(0, 8)}。`); + setError(""); + await refresh(); + } catch (reason) { setError(String(reason)); throw reason; } + finally { setLoading(false); } + }; + + const cancelTask = async (task: controlplane.Task) => { + if (!window.confirm(`取消任务 ${task.id.slice(0, 8)}?子 Agent 会停止当前执行。`)) return; + setLoading(true); + try { + await CancelTask(task.id); + setNotice(`已请求取消任务 ${task.id.slice(0, 8)}。`); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const retryTask = async (task: controlplane.Task) => { + setLoading(true); + try { + const taskID = await RetryTask(task.id); + setNotice(`已创建重试任务 ${taskID.slice(0, 8)}。`); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const clearTaskHistory = async () => { + if (!window.confirm("清除所有已结束的历史任务及其执行时间线?进行中的任务和审计日志不会被删除。")) return; + setLoading(true); + try { + const deleted = await ClearTaskHistory(); + setSelectedTaskDetail(undefined); + setNotice(`已清除 ${deleted} 条已结束任务记录;进行中的任务和审计日志已保留。`); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const showTaskDetail = async (task: controlplane.Task) => { + setSelectedTaskDetail(new controlplane.TaskDetail({task, events: []})); + setTaskDetailLoading(true); + try { + setSelectedTaskDetail(await TaskDetails(task.id)); + setError(""); + } catch (reason) { setError(String(reason)); } + finally { setTaskDetailLoading(false); } + }; + + const renameAgent = async (agentID: string, name: string) => { + setLoading(true); + try { + await RenameAgent(agentID, name); + setNotice("Agent 名称已更新。"); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const revokeAgent = async (agent: controlplane.Agent) => { + if (!window.confirm(`撤销“${agent.name}”后,该 Agent 会立即断开且必须重新配对。是否继续?`)) return; + setLoading(true); + try { + await RevokeAgent(agent.id); + setNotice(`已移除 ${agent.name},旧凭据立即失效。`); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const saveLLMProfile = async (input: controlplane.LLMProfileInput) => { + setLoading(true); + try { + const profile = await SaveLLMProfile(input); + setNotice(`已保存 LLM 配置“${profile.name}”。API Key 已受本机 Windows 凭据保护。`); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const deployLLMProfile = async (profileID: string, agentIDs: string[]) => { + setLoading(true); + try { + await DeployLLMProfile(profileID, agentIDs); + setNotice(`已将 LLM 配置下发给 ${agentIDs.length} 个 Agent,正在等待它们确认应用。`); + await refresh(); + } catch (reason) { setError(String(reason)); } + finally { setLoading(false); } + }; + + const setAgentSkills = async (targetAgentID: string, skillIDs: string[]) => { + setLoading(true); + try { await SetAgentSkills(targetAgentID, skillIDs); setNotice(`已更新该 Agent 的 ${skillIDs.length} 个 Skill,正在等待应用确认。`); await refresh(); } + catch (reason) { setError(String(reason)); } finally { setLoading(false); } + }; + const showSkillDocument = async (skill: controlplane.Skill) => { + setSelectedSkillDocument(undefined); + setSkillDocumentLoading(true); + try { + setSelectedSkillDocument(await SkillDocument(skill.id)); + setError(""); + } catch (reason) { setError(String(reason)); } + finally { setSkillDocumentLoading(false); } + }; + const enableDesktopFilesystem = async (agentIDs: string[]) => { + setLoading(true); + try { await EnableDesktopFilesystem(agentIDs); setNotice("已下发受控桌面文件工具,等待 Agent 确认。"); await refresh(); } + catch (reason) { setError(String(reason)); } finally { setLoading(false); } + }; + + return
+
+

MULTICLAW · WAILS CONTROLLER

任务控制台

+
{snapshot?.listener ?? "正在初始化"}
+
+ + {error &&
{error}
} + {notice &&
{notice}
} + {page === "overview" && <> void cancelTask(task)} onRetry={(task) => void retryTask(task)} onClearHistory={() => void clearTaskHistory()} onOpen={(task) => void showTaskDetail(task)}/>} + {page === "agents" && void renameAgent(agentID, name)} onSetSkills={(targetAgentID, skillIDs) => void setAgentSkills(targetAgentID, skillIDs)} onEnableMCP={agentID => void enableDesktopFilesystem([agentID])} onRevoke={(agent) => void revokeAgent(agent)} onPair={() => setPage("keys")}/>} + {page === "batch" && void showTaskDetail(task)}/>} + {page === "direct" && void showTaskDetail(task)}/>} + {page === "keys" && <> void createPairingCode()}/> void saveLLMProfile(input)} onDeploy={(profileID, agentIDs) => void deployLLMProfile(profileID, agentIDs)}/>} + {page === "skills" && void showSkillDocument(skill)}/>} + {page === "plugins" && } + {page === "audit" && } + {selectedTaskDetail && setSelectedTaskDetail(undefined)}/>}
; +} + +function Overview({snapshot, agents, onNavigate}: {snapshot?: controlplane.Snapshot; agents: controlplane.Agent[]; onNavigate: (page: Page) => void}) { + return <> +
+
在线 Agent{snapshot?.onlineAgents ?? "—"}
+
运行中任务{snapshot?.runningTasks ?? "—"}
+
当前电脑 IP{snapshot?.localIP ?? "—"}
+
子 Agent 连接地址{snapshot?.agentURL ?? "—"}
+
+

CONTROL PLANE

总控已就绪

此电脑 IP 已显示在上方。需要添加新的子 Agent 时,请前往“密钥”页面生成一次性配对码。

+

ONLINE AGENTS

当前在线 Agent

{agents.length === 0 ?

暂无在线 Agent。

:
{agents.map(agent =>
{agent.name}{agent.id}
在线
)}
}
+ ; +} + +function AgentManagement({skills, skillStatuses, loading, onRename, onSetSkills, onEnableMCP, onRevoke, onPair}: {skills: controlplane.Skill[]; skillStatuses: controlplane.AgentSkillStatus[]; loading: boolean; onRename: (agentID: string, name: string) => void; onSetSkills: (agentID: string, skillIDs: string[]) => void; onEnableMCP: (agentID: string) => void; onRevoke: (agent: controlplane.Agent) => void; onPair: () => void}) { + const [query, setQuery] = useState(""); + const [connectionStatus, setConnectionStatus] = useState(""); + const [mcpStatus, setMCPStatus] = useState(""); + const [llmStatus, setLLMStatus] = useState(""); + const [skillStatus, setSkillStatus] = useState(""); + const [directory, setDirectory] = useState(new controlplane.AgentDirectoryPage({items: [], page: 1, pageSize: 9, totalItems: 0, totalPages: 0})); + const [page, setPage] = useState(1); + const [directoryLoading, setDirectoryLoading] = useState(false); + const [directoryError, setDirectoryError] = useState(""); + const [selectedAgent, setSelectedAgent] = useState(); + const [selectedDetail, setSelectedDetail] = useState(); + const [detailLoading, setDetailLoading] = useState(false); + useEffect(() => { + const timer = window.setTimeout(() => { + setDirectoryLoading(true); + void AgentDirectory({page, pageSize: 9, name: query, connectionStatus, mcpStatus, llmStatus, skillStatus}).then(next => { + setDirectory(next); setDirectoryError(""); + if (next.page !== page) setPage(next.page); + }).catch(reason => setDirectoryError(String(reason))).finally(() => setDirectoryLoading(false)); + }, 180); + return () => window.clearTimeout(timer); + }, [page, query, connectionStatus, mcpStatus, llmStatus, skillStatus]); + const updateFilter = (update: () => void) => { update(); setPage(1); }; + const openAgent = async (agent: controlplane.AgentDirectoryItem) => { + setSelectedAgent(agent); setSelectedDetail(undefined); setDetailLoading(true); + try { setSelectedDetail(await AgentOperationalDetails(agent.id)); setDirectoryError(""); } + catch (reason) { setDirectoryError(String(reason)); } + finally { setDetailLoading(false); } + }; + return

AGENT DIRECTORY

Agent 管理

+

名称搜索和所有状态筛选均在总控服务端执行。点击卡片可查看最近任务、审计、部署轨迹和失败原因。

+
{directoryLoading ? "正在查询…" : `共 ${directory.totalItems} 个 Agent`}
+ {directoryError &&

{directoryError}

}{directory.totalItems === 0 ?

还没有符合筛选条件的 Agent。

: <>
{directory.items.map(agent => void openAgent(agent)}/>)}
第 {directory.page} / {directory.totalPages} 页
} + {selectedAgent && status.agentID === selectedAgent.id).map(status => status.skillID)} deployment={selectedDetail?.deployment} loading={loading || detailLoading} onClose={() => { setSelectedAgent(undefined); setSelectedDetail(undefined); }} onRename={onRename} onSetSkills={onSetSkills} onEnableMCP={onEnableMCP} onRevoke={onRevoke}/>} +
; +} + +function AgentCard({agent, onOpen}: {agent: controlplane.AgentDirectoryItem; onOpen: () => void}) { + const lastSeen = agent.lastSeenAt ? new Date(agent.lastSeenAt).toLocaleString() : "无记录"; + return
; +} + +function useCloseOnEscape(onClose: () => void) { + useEffect(() => { + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key !== "Escape") return; + event.preventDefault(); + onClose(); + }; + window.addEventListener("keydown", closeOnEscape); + return () => window.removeEventListener("keydown", closeOnEscape); + }, [onClose]); +} + +function AgentDetailModal({agent, detail, skills, selectedSkillIDs, deployment, loading, onClose, onRename, onSetSkills, onEnableMCP, onRevoke}: {agent: controlplane.Agent; detail?: controlplane.AgentOperationalDetail; skills: controlplane.Skill[]; selectedSkillIDs: string[]; deployment?: controlplane.AgentDirectoryItem; loading: boolean; onClose: () => void; onRename: (agentID: string, name: string) => void; onSetSkills: (agentID: string, skillIDs: string[]) => void; onEnableMCP: (agentID: string) => void; onRevoke: (agent: controlplane.Agent) => void}) { + useCloseOnEscape(onClose); + return
{ if (event.currentTarget === event.target) onClose(); }}>

AGENT DETAIL

{agent.name}

{loading && !detail ?

正在读取 Agent 运行详情…

: <>}
; +} + +function AgentRow({agent, skills, selectedSkillIDs, deployment, loading, onRename, onSetSkills, onEnableMCP: _onEnableMCP, onRevoke}: {agent: controlplane.Agent; skills: controlplane.Skill[]; selectedSkillIDs: string[]; deployment?: controlplane.AgentDirectoryItem; loading: boolean; onRename: (agentID: string, name: string) => void; onSetSkills: (agentID: string, skillIDs: string[]) => void; onEnableMCP: (agentID: string) => void; onRevoke: (agent: controlplane.Agent) => void}) { + const [name, setName] = useState(agent.name); + const [selected, setSelected] = useState(selectedSkillIDs); + useEffect(() => setSelected(selectedSkillIDs), [agent.id, selectedSkillIDs.join("|")]); + const changed = name.trim() !== agent.name; + const lastSeen = agent.lastSeenAt ? new Date(agent.lastSeenAt).toLocaleString() : "无记录"; + const toggleSkill = (skillID: string) => setSelected(current => current.includes(skillID) ? current.filter(value => value !== skillID) : [...current, skillID]); + const selectionChanged = selected.slice().sort().join("|") !== selectedSkillIDs.slice().sort().join("|"); + return
setName(event.target.value)}/>{agent.id}
{agent.status === "online" ? "在线" : "离线"}
最后心跳:{lastSeen}状态:{agent.status}
LLM:{deployment?.llmProfile ? `${deployment.llmProfile} · ${deployment.llmStatus}` : "未部署"}Skills:{deployment ? `${deployment.skillApplied}/${deployment.skillTotal} 已应用 · ${deployment.skillStatus}` : "未选择"}本机完全访问:默认启用{deployment?.llmFailure && LLM:{deployment.llmFailure}}{deployment?.skillFailure && Skill:{deployment.skillFailure}}

该 Agent 的 Skills

{skills.length === 0 ?

Skill 仓库为空。

:
{skills.map(skill => )}
}

本机执行权限

完全访问:默认启用。该 Agent 可实际操作所在电脑的文件、程序、网络与桌面应用;任务只能来自已认证总控。

旧桌面文件工具状态:{mcpStatusLabel[deployment?.mcpStatus ?? "未启用"] ?? deployment?.mcpStatus}
; +} + +function AgentOperationalSections({detail}: {detail?: controlplane.AgentOperationalDetail}) { + if (!detail) return null; + return

最近任务

{detail.recentTasks.length === 0 ?

暂无任务记录。

:
{detail.recentTasks.map(task =>
{statusLabel[task.status] ?? task.status}{task.id.slice(0, 8)} · {task.createdAt ? new Date(task.createdAt).toLocaleString() : "时间未知"}{preview(task.output || task.error, "等待 Agent 回报…")}
)}
}

最近审计

{detail.recentAudit.length === 0 ?

暂无审计记录。

:
{detail.recentAudit.map(event =>
{event.action} · {event.outcome}{event.createdAt ? new Date(event.createdAt).toLocaleString() : "时间未知"}{event.detail || "—"}
)}
}

部署历史

{detail.deploymentHistory.length === 0 ?

暂无部署记录。

:
{detail.deploymentHistory.map(event =>
{event.action} · {event.outcome}{event.createdAt ? new Date(event.createdAt).toLocaleString() : "时间未知"}{event.detail || "—"}
)}
}

当前失败原因

{detail.failures.length === 0 ?

没有当前部署失败。

:
{detail.failures.map((failure, index) =>
{failure.resource}{failure.reason}
)}
}
; +} + +function executionLabel(execution: string) { + if (execution === "full-access") return "完全访问 Agent"; + if (execution === "desktop-filesystem") return "旧版兼容 · 桌面文件工具"; + if (execution === "llm") return "LLM"; + return "内置演示执行器"; +} + +function preview(value: string, fallback: string) { + const compact = value.replace(/\s+/g, " ").trim(); + return compact.length > 118 ? `${compact.slice(0, 118)}…` : compact || fallback; +} + +function Tasks({agents, tasks, loading, onCancel, onRetry, onClearHistory, onOpen}: {agents: controlplane.Agent[]; tasks: controlplane.Task[]; loading: boolean; onCancel: (task: controlplane.Task) => void; onRetry: (task: controlplane.Task) => void; onClearHistory: () => void; onOpen: (task: controlplane.Task) => void}) { + return <> +

LIVE BY MACHINE

各机器任务状态

每 2 秒自动刷新
{agents.length === 0 ?

暂无在线 Agent 可接收任务。

:
{agents.map(agent => { const assigned = tasks.filter(task => task.agentID === agent.id); const active = assigned.filter(task => ["queued", "accepted", "running", "cancelling"].includes(task.status)); const latest = assigned[0]; return
{agent.name}

{active.length} 条执行中 · 最近 {assigned.length} 条

{latest ? : 尚未向该机器下发任务。}
; })}
}
+

DURABLE HISTORY

最近任务

最新在前 · 点击卡片查看详情
{tasks.length === 0 ?

还没有任务记录。

:
{tasks.map(task =>
{["queued", "accepted", "running", "cancelling"].includes(task.status) && }{["failed", "cancelled", "timed_out"].includes(task.status) && }
)}
}
+ ; +} + +function MentionDispatch({agents, tasks, loading, onSubmit, onOpen}: {agents: controlplane.Agent[]; tasks: controlplane.Task[]; loading: boolean; onSubmit: (message: string, timeoutSeconds: number, images: PastedImage[]) => Promise; onOpen: (task: controlplane.Task) => void}) { + const [message, setMessage] = useState(""); + const [timeoutSeconds, setTimeoutSeconds] = useState(300); + const [result, setResult] = useState(); + const [images, setImages] = useState([]); + const [imageNotice, setImageNotice] = useState(""); + const [mentionQuery, setMentionQuery] = useState(); + const [mentionIndex, setMentionIndex] = useState(0); + const composerRef = useRef(null); + const imageInputRef = useRef(null); + const selectedAgents = agents.filter(agent => message.includes(`@${agent.name}`)); + const candidates = mentionQuery === undefined ? [] : agents.filter(agent => agent.name.toLocaleLowerCase().includes(mentionQuery.toLocaleLowerCase())); + const updateMentionQuery = (value: string, cursor: number) => { + const before = value.slice(0, cursor); + const marker = before.lastIndexOf("@"); + const query = marker < 0 || (marker > 0 && !/\s/.test(before[marker - 1])) ? undefined : before.slice(marker + 1); + setMentionQuery(query !== undefined && !/\s/.test(query) ? query : undefined); + setMentionIndex(0); + }; + const addAgent = (agent: controlplane.Agent) => { + const field = composerRef.current; + const start = field?.selectionStart ?? message.length; + const end = field?.selectionEnd ?? message.length; + const marker = message.slice(0, start).lastIndexOf("@"); + const replaceStart = marker >= 0 && (marker === 0 || /\s/.test(message[marker - 1])) ? marker : start; + const next = `${message.slice(0, replaceStart)}@${agent.name} ${message.slice(end)}`; + setMessage(next); + setMentionQuery(undefined); + window.setTimeout(() => { if (field) { const position = replaceStart + agent.name.length + 2; field.focus(); field.selectionStart = field.selectionEnd = position; } }, 0); + }; + const addImages = (files: File[]) => { + const accepted = files.filter(file => ["image/png", "image/jpeg", "image/gif", "image/webp"].includes(file.type)).slice(0, 10 - images.length); + const currentBytes = images.reduce((total, image) => total + Math.max(0, Math.floor((image.dataURL.length - image.dataURL.indexOf(",") - 1) * 3 / 4)), 0); + let remaining = 12 * 1024 * 1024 - currentBytes; + const bounded = accepted.filter(file => { if (file.size > remaining) return false; remaining -= file.size; return true; }); + if (bounded.length !== accepted.length) setImageNotice("每项任务的图片原始总大小不能超过 12 MiB。"); + void Promise.all(bounded.map(file => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve({id: crypto.randomUUID(), name: file.name || "粘贴的图片", dataURL: String(reader.result)}); reader.onerror = () => reject(new Error("无法读取图片")); reader.readAsDataURL(file); }))).then(added => setImages(current => [...current, ...added])); + }; + const submit = async (event: FormEvent) => { + event.preventDefault(); + try { setResult(await onSubmit(message, timeoutSeconds, images)); setMessage(""); setImages([]); setImageNotice(""); } + catch { /* The application-level error banner contains the safe reason. */ } + }; + return
+

MULTI-AGENT @ ASSIGNMENT

批量指派

+

输入 @ 即会显示可选 Agent,可用方向键和回车选中;也可点击下方 Agent。每个非空行是一组独立任务:同一行多个 Agent 会收到相同任务;换行后可写不同任务。多条有效任务带图时,请在每行明确写“图片 1”等编号。

+
{agents.map(agent => )}
+ {selectedAgents.length > 0 &&
当前已识别:{selectedAgents.map(agent => {agent.name})}
} +