diff --git a/mateclaw-server/src/main/java/vip/mate/channel/web/TalkModeWebSocketHandler.java b/mateclaw-server/src/main/java/vip/mate/channel/web/TalkModeWebSocketHandler.java index 81eda93f..1d529639 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/web/TalkModeWebSocketHandler.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/web/TalkModeWebSocketHandler.java @@ -70,7 +70,9 @@ public class TalkModeWebSocketHandler extends AbstractWebSocketHandler { String type = (String) data.get("type"); if ("init".equals(type)) { - Long agentId = data.get("agentId") != null ? Long.valueOf(data.get("agentId").toString()) : null; + Object rawAgentId = data.get("agentId"); + Long agentId = (rawAgentId != null && !rawAgentId.toString().isBlank()) + ? Long.valueOf(rawAgentId.toString()) : null; String conversationId = (String) data.getOrDefault("conversationId", "talk-" + session.getId()); String username = (String) data.getOrDefault("username", "anonymous"); diff --git a/mateclaw-server/src/main/java/vip/mate/config/FlywayRepairConfig.java b/mateclaw-server/src/main/java/vip/mate/config/FlywayRepairConfig.java new file mode 100644 index 00000000..75b85d46 --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/config/FlywayRepairConfig.java @@ -0,0 +1,32 @@ +package vip.mate.config; + +import lombok.extern.slf4j.Slf4j; +import org.flywaydb.core.Flyway; +import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Flyway auto-repair configuration. + *

+ * Replaces the default {@link FlywayMigrationInitializer} with one that + * calls {@code flyway.repair()} before {@code flyway.migrate()}. + * This handles failed migrations and checksum mismatches transparently + * during version upgrades — especially important for Desktop app users + * who cannot manually run CLI commands. + * + * @author MateClaw Team + */ +@Slf4j +@Configuration +public class FlywayRepairConfig { + + @Bean + public FlywayMigrationInitializer flywayInitializer(Flyway flyway) { + return new FlywayMigrationInitializer(flyway, f -> { + log.info("[Flyway] Running repair before migrate (auto-fix failed/changed migrations)..."); + f.repair(); + f.migrate(); + }); + } +} diff --git a/mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/CwdAwareStdioClientTransport.java b/mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/CwdAwareStdioClientTransport.java index 435ffc87..36a7e86c 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/CwdAwareStdioClientTransport.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/mcp/runtime/CwdAwareStdioClientTransport.java @@ -3,17 +3,50 @@ package vip.mate.tool.mcp.runtime; import io.modelcontextprotocol.client.transport.ServerParameters; import io.modelcontextprotocol.client.transport.StdioClientTransport; import io.modelcontextprotocol.json.McpJsonMapper; +import lombok.extern.slf4j.Slf4j; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; /** - * 为 stdio MCP 子进程补充工作目录支持。 - * MCP SDK 1.0.0 的 ServerParameters 尚未暴露 cwd,这里通过覆写 ProcessBuilder 注入。 + * Enhanced stdio MCP transport with: + *

*/ +@Slf4j public class CwdAwareStdioClientTransport extends StdioClientTransport { private final String cwd; + /** Common Node.js installation paths across platforms */ + private static final String[] NODE_PATH_CANDIDATES = { + // macOS Homebrew + "/usr/local/bin", + "/opt/homebrew/bin", + // macOS nvm + System.getProperty("user.home") + "/.nvm/current/bin", + // Linux common + "/usr/bin", + "/usr/local/bin", + // Linux nvm + System.getProperty("user.home") + "/.nvm/current/bin", + // Windows common + System.getenv("APPDATA") != null ? System.getenv("APPDATA") + "\\npm" : "", + "C:\\Program Files\\nodejs", + // pnpm global + System.getProperty("user.home") + "/.local/share/pnpm", + System.getProperty("user.home") + "/Library/pnpm", + // Volta + System.getProperty("user.home") + "/.volta/bin", + // fnm + System.getProperty("user.home") + "/.fnm/current/bin", + }; + public CwdAwareStdioClientTransport(ServerParameters params, McpJsonMapper jsonMapper, String cwd) { super(params, jsonMapper); this.cwd = cwd; @@ -25,6 +58,56 @@ public class CwdAwareStdioClientTransport extends StdioClientTransport { if (cwd != null && !cwd.isBlank()) { builder.directory(new File(cwd)); } + enrichPath(builder); return builder; } + + /** + * Enrich the process PATH with common Node.js installation directories. + * Desktop apps (Electron/JRE) often don't inherit the user's shell PATH, + * causing "npx: command not found" errors. + */ + private void enrichPath(ProcessBuilder builder) { + Map env = builder.environment(); + String currentPath = env.getOrDefault("PATH", env.getOrDefault("Path", "")); + StringBuilder enriched = new StringBuilder(currentPath); + + for (String candidate : NODE_PATH_CANDIDATES) { + if (candidate == null || candidate.isEmpty()) continue; + if (currentPath.contains(candidate)) continue; + if (Files.isDirectory(Path.of(candidate))) { + enriched.append(File.pathSeparator).append(candidate); + } + } + + // Also try to resolve nvm's actual current version directory + String nvmDir = System.getenv("NVM_DIR"); + if (nvmDir == null) nvmDir = System.getProperty("user.home") + "/.nvm"; + Path nvmDefault = Path.of(nvmDir, "versions", "node"); + if (Files.isDirectory(nvmDefault)) { + try (var stream = Files.list(nvmDefault)) { + stream.filter(Files::isDirectory) + .sorted((a, b) -> b.getFileName().toString().compareTo(a.getFileName().toString())) + .findFirst() + .ifPresent(nodeDir -> { + String binPath = nodeDir.resolve("bin").toString(); + if (!currentPath.contains(binPath)) { + enriched.append(File.pathSeparator).append(binPath); + } + }); + } catch (Exception ignored) {} + } + + String finalPath = enriched.toString(); + env.put("PATH", finalPath); + // Windows uses "Path" key + if (env.containsKey("Path")) { + env.put("Path", finalPath); + } + + if (!finalPath.equals(currentPath)) { + log.debug("[MCP] Enriched PATH for subprocess: added {} entries", + finalPath.split(File.pathSeparator).length - currentPath.split(File.pathSeparator).length); + } + } } diff --git a/mateclaw-server/src/main/resources/application.yml b/mateclaw-server/src/main/resources/application.yml index 22321857..2ab8cc8a 100644 --- a/mateclaw-server/src/main/resources/application.yml +++ b/mateclaw-server/src/main/resources/application.yml @@ -37,7 +37,6 @@ spring: - classpath:db/migration/h2 validate-on-migrate: true clean-disabled: true - repair-on-migrate: true h2: console: diff --git a/mateclaw-server/src/main/resources/db/data-en.sql b/mateclaw-server/src/main/resources/db/data-en.sql index a3b975a9..e01872fe 100644 --- a/mateclaw-server/src/main/resources/db/data-en.sql +++ b/mateclaw-server/src/main/resources/db/data-en.sql @@ -405,10 +405,10 @@ VALUES ( NULL, NULL, 'npx', - '["-y","@modelcontextprotocol/server-filesystem","/Users/mate"]', + '["-y","@modelcontextprotocol/server-filesystem","${user.home}"]', '{}', - '/Users/mate', - TRUE, + NULL, + FALSE, 30, 30, 'disconnected', diff --git a/mateclaw-server/src/main/resources/db/data-mysql-en.sql b/mateclaw-server/src/main/resources/db/data-mysql-en.sql index 72690c74..97765177 100644 --- a/mateclaw-server/src/main/resources/db/data-mysql-en.sql +++ b/mateclaw-server/src/main/resources/db/data-mysql-en.sql @@ -403,10 +403,10 @@ VALUES ( NULL, NULL, 'npx', - '["-y","@modelcontextprotocol/server-filesystem","/Users/mate"]', + '["-y","@modelcontextprotocol/server-filesystem","${user.home}"]', '{}', - '/Users/mate', - TRUE, + NULL, + FALSE, 30, 30, 'disconnected', diff --git a/mateclaw-server/src/main/resources/db/data-mysql-zh.sql b/mateclaw-server/src/main/resources/db/data-mysql-zh.sql index b484b086..45ef64f3 100644 --- a/mateclaw-server/src/main/resources/db/data-mysql-zh.sql +++ b/mateclaw-server/src/main/resources/db/data-mysql-zh.sql @@ -405,10 +405,10 @@ VALUES ( NULL, NULL, 'npx', - '["-y","@modelcontextprotocol/server-filesystem","/Users/mate"]', + '["-y","@modelcontextprotocol/server-filesystem","${user.home}"]', '{}', - '/Users/mate', - TRUE, + NULL, + FALSE, 30, 30, 'disconnected', diff --git a/mateclaw-server/src/main/resources/db/data-zh.sql b/mateclaw-server/src/main/resources/db/data-zh.sql index 1085370b..3d8ee6b0 100644 --- a/mateclaw-server/src/main/resources/db/data-zh.sql +++ b/mateclaw-server/src/main/resources/db/data-zh.sql @@ -411,10 +411,10 @@ VALUES ( NULL, NULL, 'npx', - '["-y","@modelcontextprotocol/server-filesystem","/Users/mate"]', + '["-y","@modelcontextprotocol/server-filesystem","${user.home}"]', '{}', - '/Users/mate', - TRUE, + NULL, + FALSE, 30, 30, 'disconnected', diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V2__workspace_base_path.sql b/mateclaw-server/src/main/resources/db/migration/h2/V2__workspace_base_path.sql index 127d064c..f3a56a3c 100644 --- a/mateclaw-server/src/main/resources/db/migration/h2/V2__workspace_base_path.sql +++ b/mateclaw-server/src/main/resources/db/migration/h2/V2__workspace_base_path.sql @@ -1,2 +1,121 @@ --- V2: Add workspace base_path for directory restriction (RFC-002) +-- V2: Upgrade schema for databases created before Flyway was introduced. +-- All statements use IF NOT EXISTS / IF EXISTS to be idempotent. + +-- ===== Workspace tables ===== +CREATE TABLE IF NOT EXISTS mate_workspace ( + id BIGINT NOT NULL PRIMARY KEY, + name VARCHAR(128) NOT NULL, + slug VARCHAR(64) NOT NULL, + description VARCHAR(256), + owner_id BIGINT, + settings_json TEXT, + base_path VARCHAR(512), + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0, + CONSTRAINT uk_workspace_slug UNIQUE (slug) +); ALTER TABLE mate_workspace ADD COLUMN IF NOT EXISTS base_path VARCHAR(512); + +CREATE TABLE IF NOT EXISTS mate_workspace_member ( + id BIGINT NOT NULL PRIMARY KEY, + workspace_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + role VARCHAR(32) NOT NULL DEFAULT 'member', + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); +CREATE INDEX IF NOT EXISTS idx_ws_member_workspace ON mate_workspace_member(workspace_id); +CREATE INDEX IF NOT EXISTS idx_ws_member_user ON mate_workspace_member(user_id); + +-- ===== Add workspace_id column to tables that may lack it (pre-workspace era databases) ===== +ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_channel ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_conversation ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_wiki_knowledge_base ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_tool ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_skill ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; + +-- ===== Tables that may not exist in very old databases ===== +CREATE TABLE IF NOT EXISTS mate_workspace_file ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + filename VARCHAR(256) NOT NULL, + content CLOB, + file_size BIGINT NOT NULL DEFAULT 0, + enabled BOOLEAN NOT NULL DEFAULT FALSE, + sort_order INT NOT NULL DEFAULT 0, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); +CREATE INDEX IF NOT EXISTS idx_workspace_file_agent ON mate_workspace_file(agent_id); + +CREATE TABLE IF NOT EXISTS mate_usage_daily ( + id BIGINT NOT NULL PRIMARY KEY, + workspace_id BIGINT NOT NULL, + agent_id BIGINT NOT NULL, + stat_date DATE NOT NULL, + conversation_count INT NOT NULL DEFAULT 0, + message_count INT NOT NULL DEFAULT 0, + tool_call_count INT NOT NULL DEFAULT 0, + prompt_tokens BIGINT NOT NULL DEFAULT 0, + completion_tokens BIGINT NOT NULL DEFAULT 0, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS uk_usage_daily ON mate_usage_daily(workspace_id, agent_id, stat_date); + +CREATE TABLE IF NOT EXISTS mate_audit_event ( + id BIGINT NOT NULL PRIMARY KEY, + workspace_id BIGINT, + user_id BIGINT, + username VARCHAR(64), + action VARCHAR(64) NOT NULL, + resource_type VARCHAR(64), + resource_id VARCHAR(128), + detail TEXT, + ip_address VARCHAR(64), + create_time DATETIME NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_audit_ws_time ON mate_audit_event(workspace_id, create_time); + +-- ===== OAuth columns on model_provider ===== +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS auth_type VARCHAR(16) NOT NULL DEFAULT 'api_key'; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_access_token TEXT; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_refresh_token TEXT; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_expires_at BIGINT; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_account_id VARCHAR(128); + +-- ===== Agent-Skill / Agent-Tool binding tables ===== +CREATE TABLE IF NOT EXISTS mate_agent_skill ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + skill_id BIGINT NOT NULL, + create_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); +CREATE TABLE IF NOT EXISTS mate_agent_tool ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + tool_name VARCHAR(128) NOT NULL, + create_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); + +-- ===== Memory recall table ===== +CREATE TABLE IF NOT EXISTS mate_memory_recall ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + filename VARCHAR(256) NOT NULL, + content CLOB, + tags VARCHAR(512), + score DOUBLE NOT NULL DEFAULT 0.0, + last_recalled_at DATETIME, + promoted BOOLEAN NOT NULL DEFAULT FALSE, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); +CREATE INDEX IF NOT EXISTS idx_memory_recall_agent ON mate_memory_recall(agent_id); diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V2__workspace_base_path.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V2__workspace_base_path.sql index 127d064c..7e03930b 100644 --- a/mateclaw-server/src/main/resources/db/migration/mysql/V2__workspace_base_path.sql +++ b/mateclaw-server/src/main/resources/db/migration/mysql/V2__workspace_base_path.sql @@ -1,2 +1,114 @@ --- V2: Add workspace base_path for directory restriction (RFC-002) +-- V2: Upgrade schema for databases created before Flyway was introduced. + +CREATE TABLE IF NOT EXISTS mate_workspace ( + id BIGINT NOT NULL PRIMARY KEY, + name VARCHAR(128) NOT NULL, + slug VARCHAR(64) NOT NULL, + description VARCHAR(256), + owner_id BIGINT, + settings_json TEXT, + base_path VARCHAR(512), + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0, + UNIQUE KEY uk_workspace_slug (slug) +); ALTER TABLE mate_workspace ADD COLUMN IF NOT EXISTS base_path VARCHAR(512); + +CREATE TABLE IF NOT EXISTS mate_workspace_member ( + id BIGINT NOT NULL PRIMARY KEY, + workspace_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + role VARCHAR(32) NOT NULL DEFAULT 'member', + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0, + INDEX idx_ws_member_workspace (workspace_id), + INDEX idx_ws_member_user (user_id) +); + +ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_channel ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_conversation ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_wiki_knowledge_base ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_tool ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; +ALTER TABLE mate_skill ADD COLUMN IF NOT EXISTS workspace_id BIGINT NOT NULL DEFAULT 1; + +CREATE TABLE IF NOT EXISTS mate_workspace_file ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + filename VARCHAR(256) NOT NULL, + content LONGTEXT, + file_size BIGINT NOT NULL DEFAULT 0, + enabled BOOLEAN NOT NULL DEFAULT FALSE, + sort_order INT NOT NULL DEFAULT 0, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0, + INDEX idx_workspace_file_agent (agent_id) +); + +CREATE TABLE IF NOT EXISTS mate_usage_daily ( + id BIGINT NOT NULL PRIMARY KEY, + workspace_id BIGINT NOT NULL, + agent_id BIGINT NOT NULL, + stat_date DATE NOT NULL, + conversation_count INT NOT NULL DEFAULT 0, + message_count INT NOT NULL DEFAULT 0, + tool_call_count INT NOT NULL DEFAULT 0, + prompt_tokens BIGINT NOT NULL DEFAULT 0, + completion_tokens BIGINT NOT NULL DEFAULT 0, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + UNIQUE KEY uk_usage_daily (workspace_id, agent_id, stat_date) +); + +CREATE TABLE IF NOT EXISTS mate_audit_event ( + id BIGINT NOT NULL PRIMARY KEY, + workspace_id BIGINT, + user_id BIGINT, + username VARCHAR(64), + action VARCHAR(64) NOT NULL, + resource_type VARCHAR(64), + resource_id VARCHAR(128), + detail TEXT, + ip_address VARCHAR(64), + create_time DATETIME NOT NULL, + INDEX idx_audit_ws_time (workspace_id, create_time) +); + +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS auth_type VARCHAR(16) NOT NULL DEFAULT 'api_key'; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_access_token TEXT; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_refresh_token TEXT; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_expires_at BIGINT; +ALTER TABLE mate_model_provider ADD COLUMN IF NOT EXISTS oauth_account_id VARCHAR(128); + +CREATE TABLE IF NOT EXISTS mate_agent_skill ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + skill_id BIGINT NOT NULL, + create_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); +CREATE TABLE IF NOT EXISTS mate_agent_tool ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + tool_name VARCHAR(128) NOT NULL, + create_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS mate_memory_recall ( + id BIGINT NOT NULL PRIMARY KEY, + agent_id BIGINT NOT NULL, + filename VARCHAR(256) NOT NULL, + content LONGTEXT, + tags VARCHAR(512), + score DOUBLE NOT NULL DEFAULT 0.0, + last_recalled_at DATETIME, + promoted BOOLEAN NOT NULL DEFAULT FALSE, + create_time DATETIME NOT NULL, + update_time DATETIME NOT NULL, + deleted INT NOT NULL DEFAULT 0, + INDEX idx_memory_recall_agent (agent_id) +); diff --git a/mateclaw-ui/src/components/chat/TalkMode.vue b/mateclaw-ui/src/components/chat/TalkMode.vue index 43367a4e..c40fb634 100644 --- a/mateclaw-ui/src/components/chat/TalkMode.vue +++ b/mateclaw-ui/src/components/chat/TalkMode.vue @@ -90,7 +90,9 @@ const stateLabel = computed(() => { }) onMounted(() => { - connectWebSocket() + if (props.agentId) { + connectWebSocket() + } }) onBeforeUnmount(() => { diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 88110e46..3e6a29fa 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -258,41 +258,15 @@ export default { aboutTitle: 'About MateClaw', aboutDesc: 'Version and system information', about: { - heroKicker: 'Product', - heroTitle: 'MateClaw is not a chat window. It is an AI operating system for ongoing work.', - heroDesc: 'The point is not to generate a few more answers. The point is to place models inside a continuous loop of context, memory, execution, knowledge, and delivery so the product behaves like a real system.', - manifestoKicker: 'Why It Exists', - manifestoTitle: 'This page should not stop at a version number. It should explain what this product is trying to become.', - manifestoDesc: 'MateClaw is not defined by how many features it exposes. It is defined by how tightly it pulls context, tools, memory, and output into one operating surface that can keep pace with real work.', - systemKicker: 'System Shape', - systemTitle: 'A valuable product is not a pile of capabilities. It is a set of capabilities that lock together.', - systemDesc: 'Agents, Wiki, Memory, Channels, Workspace, and governance are not parallel modules. They are supposed to form one system that users can understand, trust, and extend.', - foundationKicker: 'Foundation', - foundationTitle: 'The experience needs a backbone, and the backbone has to be strong.', - foundationDesc: 'The stack matters only if it can support a stable runtime, extensible agentic patterns, and boundaries that hold up under real work.', + heroDesc: 'A personal AI operating system that places models inside a continuous loop of context, memory, execution, knowledge, and delivery.', + foundationTitle: 'Built With', pillars: { contextTitle: 'Keep context continuous', - contextDesc: 'Models, knowledge, workspace, and the active task should live inside the same frame instead of starting over every turn.', - executionTitle: 'Make execution part of the product', - executionDesc: 'Browser actions, tools, channels, and async tasks should not feel bolted on. They need to be the real execution surface.', + contextDesc: 'Models, knowledge, workspace, and the active task live inside the same frame instead of starting over every turn.', + executionTitle: 'Make execution real', + executionDesc: 'Tools, channels, and async tasks are the real execution surface, not bolted-on extras.', memoryTitle: 'Make memory compound', - memoryDesc: 'Memory should not just be stored. It should be shaped, recalled, and turned into leverage for the next task.', - }, - manifestoItems: { - runtimeTitle: 'It is a runtime before it is a feature list', - runtimeDesc: 'Strong products solve runtime coherence first: context, permissions, state, and outcomes need to speak the same language.', - knowledgeTitle: 'It must know how to organize knowledge', - knowledgeDesc: 'Wiki and memory matter because they let the system learn what is worth preserving and what should be available again later.', - multimodalTitle: 'It should handle multimodal work naturally', - multimodalDesc: 'Voice, image, video, and text should feel like one expression and execution system, not separate tricks.', - }, - systemItems: { - workspaceTitle: 'Workspace is a boundary, not a decoration', - workspaceDesc: 'Every resource, state transition, and execution path needs to know which workspace it belongs to. Platform boundaries must be real.', - governanceTitle: 'Governance belongs in the core', - governanceDesc: 'Audit, approvals, guards, activity, and observability are not back-office extras. They define whether users trust the system.', - deliveryTitle: 'Delivery matters more than answers', - deliveryDesc: 'A mature AI product should not stop at generating a response. It should keep moving the task, expose state, and leave useful results behind.', + memoryDesc: 'Memory is shaped, recalled, and turned into leverage for the next task.', }, }, model: { diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 341f7c5f..d14dcf93 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -248,41 +248,15 @@ export default { aboutTitle: '关于 MateClaw', aboutDesc: '版本与系统信息', about: { - heroKicker: 'Product', - heroTitle: 'MateClaw 不是一个聊天窗口,而是一套持续运转的 AI 工作系统。', - heroDesc: '它的目标不是帮你多生成几段文本,而是让模型真正进入任务、知识、记忆、执行和协作的连续流程里,像一个系统一样稳定工作。', - manifestoKicker: 'Why It Exists', - manifestoTitle: '这页不该只是版本号,它应该回答这个产品到底想成为什么。', - manifestoDesc: 'MateClaw 的核心不是“功能很多”,而是把上下文、工具、记忆和交付收拢成一个统一运行面,让 AI 能持续理解你正在做什么,并把结果带回来。', - systemKicker: 'System Shape', - systemTitle: '真正有价值的产品,不是把能力摆满,而是让能力彼此咬合。', - systemDesc: 'Agent、Wiki、Memory、Channels、Workspace 和治理能力不是平行堆叠的模块,它们应该共同构成一个可被理解、可被信任、可被扩展的系统。', - foundationKicker: 'Foundation', - foundationTitle: '体验必须有骨架,骨架必须足够硬。', - foundationDesc: '底层技术栈的价值不在于名字响亮,而在于它们能否支撑稳定的 runtime、可扩展的 agentic patterns,以及面向真实工作的产品边界。', + heroDesc: '一套个人 AI 操作系统,让模型真正进入上下文、记忆、执行、知识和交付的连续流程。', + foundationTitle: '技术栈', pillars: { contextTitle: '让上下文保持连续', - contextDesc: '把模型、知识、工作区和当前任务放进同一个语境里,而不是每次重新开始。', - executionTitle: '让执行成为产品的一部分', - executionDesc: 'Browser、tools、channels 与异步任务不应是外挂,它们必须成为系统的真实执行面。', - memoryTitle: '让记忆真正产生复利', - memoryDesc: '记忆不只是被保存,而是被整理、被召回,并在下一次任务里变得更有价值。', - }, - manifestoItems: { - runtimeTitle: '它首先是一套 runtime', - runtimeDesc: '任何强产品都必须先解决运行时的一致性:上下文、权限、状态和结果必须说同一种语言。', - knowledgeTitle: '它必须会组织知识', - knowledgeDesc: 'Wiki 与记忆系统的意义,不是多一个存储区,而是让系统逐渐知道什么值得保留、什么值得再次使用。', - multimodalTitle: '它应该自然处理多模态', - multimodalDesc: '语音、图片、视频与文本不能彼此割裂,用户看到的应该是一套统一的表达与执行能力。', - }, - systemItems: { - workspaceTitle: 'Workspace 是边界,不是装饰', - workspaceDesc: '所有资源、状态与执行路径都必须知道自己属于哪个工作区,平台边界要真实,而不是靠页面暗示。', - governanceTitle: '治理能力必须进入核心', - governanceDesc: 'Audit、审批、guard、activity 和 observability 不是后台功能,它们决定用户是否真正信任这个系统。', - deliveryTitle: '交付比回答更重要', - deliveryDesc: '一个成熟的 AI 产品不该停在回答层,它应该能持续推进任务、暴露状态、沉淀结果。', + contextDesc: '模型、知识、工作区和当前任务在同一个语境里运行,而不是每次重新开始。', + executionTitle: '让执行成为产品本身', + executionDesc: '工具、渠道与异步任务是系统的真实执行面,不是外挂。', + memoryTitle: '让记忆产生复利', + memoryDesc: '记忆被整理、被召回,在下一次任务里变得更有价值。', }, }, model: { diff --git a/mateclaw-ui/src/views/ChatConsole.vue b/mateclaw-ui/src/views/ChatConsole.vue index 14a8cfcf..40f36aa5 100644 --- a/mateclaw-ui/src/views/ChatConsole.vue +++ b/mateclaw-ui/src/views/ChatConsole.vue @@ -239,6 +239,7 @@ -
- -
- + +
+
@@ -26,7 +25,7 @@ - +
{{ t('datasources.columns.name') }}
@@ -89,7 +88,6 @@
-
-
-
- - - - - - - - - - - - - + + +
{{ t('mcp.columns.name') }}{{ t('mcp.columns.lastStatus') }}{{ t('mcp.columns.toolCount') }}{{ t('mcp.columns.enabled') }}{{ t('mcp.columns.actions') }}
-
-
- - - - - - -
-
-
{{ server.name }}
-
- - {{ t('mcp.transport.' + server.transport) }} - + +
+ + + + + + + + + + + + + - - - - - - - + + + + + + + - - -
{{ t('mcp.columns.name') }}{{ t('mcp.columns.lastStatus') }}{{ t('mcp.columns.toolCount') }}{{ t('mcp.columns.enabled') }}{{ t('mcp.columns.actions') }}
+
+
+ + + + + + +
+
+
{{ server.name }}
+
+ {{ server.transport }} + {{ server.description }} +
-
{{ server.description || '-' }}
- -
-
- - {{ t('mcp.status.' + (server.lastStatus || 'disconnected')) }} - - {{ server.lastConnectedTime }} -
-
- {{ truncate(server.lastError, 40) }} -
-
- {{ server.toolCount || 0 }} - - - -
- - - - -
-
-
- - +
+
+ + {{ t('mcp.status.' + (server.lastStatus || 'disconnected')) }} + {{ formatRelativeTime(server.lastConnectedTime) }} +
+
+ {{ truncate(server.lastError, 36) }} +
+
+ {{ server.toolCount || 0 }} + + + +
+ + + + +
+
+
+ - -

{{ t('mcp.messages.empty') }}

-

{{ t('mcp.messages.emptyDesc') }}

-
-
-
+

{{ t('mcp.messages.empty') }}

+

{{ t('mcp.messages.emptyDesc') }}

+
+
-