messages = new ArrayList<>();
// Layer 1: System prompt(增强指令)
@@ -305,8 +306,8 @@ public class StepExecutionNode implements NodeAction {
8. 每一步最多做一个必要的检查和一个必要的执行,不要无意义循环。
""";
messages.add(new SystemMessage(enhancedSystemPrompt));
- // 注入运行时上下文(当前时间)
- messages.add(new UserMessage(RuntimeContextInjector.buildContextMessage()));
+ // 注入运行时上下文(当前时间 + 工作目录)
+ messages.add(new UserMessage(RuntimeContextInjector.buildContextMessage(workspaceBasePath)));
// Layer 2: Working context(对话历史 + 步骤结果的受控长度摘要)
String workingContext = accessor.workingContext();
diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/state/MateClawStateKeys.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/state/MateClawStateKeys.java
index edb3267f..493f30f2 100644
--- a/mateclaw-server/src/main/java/vip/mate/agent/graph/state/MateClawStateKeys.java
+++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/state/MateClawStateKeys.java
@@ -20,6 +20,8 @@ public final class MateClawStateKeys {
public static final String CONVERSATION_ID = "conversation_id";
public static final String SYSTEM_PROMPT = "system_prompt";
public static final String AGENT_ID = "agent_id";
+ /** 工作区活动目录(为空不限制文件访问范围) */
+ public static final String WORKSPACE_BASE_PATH = "workspace_base_path";
// ===== 消息列表(APPEND 策略)=====
public static final String MESSAGES = "messages";
diff --git a/mateclaw-server/src/main/java/vip/mate/approval/ApprovalWorkflowService.java b/mateclaw-server/src/main/java/vip/mate/approval/ApprovalWorkflowService.java
index ca5f0585..3d1e6e29 100644
--- a/mateclaw-server/src/main/java/vip/mate/approval/ApprovalWorkflowService.java
+++ b/mateclaw-server/src/main/java/vip/mate/approval/ApprovalWorkflowService.java
@@ -30,7 +30,7 @@ import java.util.Map;
*/
@Slf4j
@Service
-@Order(55) // 在 ApprovalSchemaMigration(50) 之后
+@Order(55) // Schema 由 Flyway 管理,在 Flyway 迁移完成后执行
@RequiredArgsConstructor
public class ApprovalWorkflowService implements ApplicationRunner {
diff --git a/mateclaw-server/src/main/java/vip/mate/approval/config/ApprovalSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/approval/config/ApprovalSchemaMigration.java
deleted file mode 100644
index 8bf9c419..00000000
--- a/mateclaw-server/src/main/java/vip/mate/approval/config/ApprovalSchemaMigration.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package vip.mate.approval.config;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
-
-/**
- * 审批表增量 Schema 迁移
- *
- * 基础表 mate_tool_approval 已在 schema.sql / schema-mysql.sql 中定义。
- * 本类仅负责增量字段/索引迁移,确保从旧版本升级时自动补齐。
- *
- * 当前无需增量迁移,保留框架以备未来扩展。
- *
- * @author MateClaw Team
- */
-@Slf4j
-@Component
-@Order(50)
-@RequiredArgsConstructor
-public class ApprovalSchemaMigration implements ApplicationRunner {
-
- private final JdbcTemplate jdbcTemplate;
-
- @Override
- public void run(ApplicationArguments args) {
- // 当前无增量迁移。未来新增字段时在此添加 safeAddColumn 调用。
- log.debug("[ApprovalSchemaMigration] Incremental migration completed (no-op)");
- }
-}
diff --git a/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java b/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java
index aaeae7cb..91d2ec62 100644
--- a/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java
+++ b/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java
@@ -68,7 +68,7 @@ public class DatabaseBootstrapRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
- runSchemaScript();
+ // Schema creation is now handled by Flyway (db/migration/)
runToolSyncScript();
if (isDataAlreadySeeded()) {
@@ -168,10 +168,6 @@ public class DatabaseBootstrapRunner implements ApplicationRunner {
return isMySQL;
}
- private void runSchemaScript() {
- runScript(isMySQL() ? "db/schema-mysql.sql" : "db/schema.sql");
- }
-
private void runToolSyncScript() {
String script = isMySQL() ? "db/tools-sync-mysql.sql" : "db/tools-sync.sql";
runScript(script);
diff --git a/mateclaw-server/src/main/java/vip/mate/cron/config/CronSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/cron/config/CronSchemaMigration.java
deleted file mode 100644
index cc7e94c4..00000000
--- a/mateclaw-server/src/main/java/vip/mate/cron/config/CronSchemaMigration.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package vip.mate.cron.config;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
-
-/**
- * CronJob 表 Schema 迁移
- *
- * @author MateClaw Team
- */
-@Slf4j
-@Component
-@Order(200)
-@RequiredArgsConstructor
-public class CronSchemaMigration implements ApplicationRunner {
-
- private final JdbcTemplate jdbcTemplate;
-
- @Override
- public void run(ApplicationArguments args) {
- addColumnIfMissing("mate_cron_job", "timezone", "VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai'");
- addColumnIfMissing("mate_cron_job", "task_type", "VARCHAR(16) NOT NULL DEFAULT 'text'");
- addColumnIfMissing("mate_cron_job", "request_body", "TEXT");
- addColumnIfMissing("mate_cron_job", "next_run_time", "DATETIME");
- log.info("[CronSchemaMigration] mate_cron_job schema migration completed");
- }
-
- private void addColumnIfMissing(String table, String column, String definition) {
- try {
- jdbcTemplate.execute("ALTER TABLE " + table + " ADD COLUMN IF NOT EXISTS " + column + " " + definition);
- } catch (Exception e) {
- log.debug("[CronSchemaMigration] Column {} may already exist: {}", column, e.getMessage());
- }
- }
-}
diff --git a/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java b/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java
index cf19aad2..78b87029 100644
--- a/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java
+++ b/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java
@@ -60,7 +60,7 @@ public class CronJobService implements ApplicationRunner {
// ==================== 初始化与销毁 ====================
/**
- * 实现 ApplicationRunner,确保在 CronSchemaMigration(@Order=200) 执行完毕后再加载任务
+ * 实现 ApplicationRunner,确保在 Flyway 迁移和 DatabaseBootstrapRunner 完成后再加载任务
*/
@Override
public void run(ApplicationArguments args) {
diff --git a/mateclaw-server/src/main/java/vip/mate/memory/config/MemorySchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/memory/config/MemorySchemaMigration.java
deleted file mode 100644
index f95b600a..00000000
--- a/mateclaw-server/src/main/java/vip/mate/memory/config/MemorySchemaMigration.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package vip.mate.memory.config;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
-
-/**
- * Memory 模块增量 Schema 迁移
- *
- * 基础表 mate_memory_recall 已在 schema.sql / schema-mysql.sql 中定义。
- * 本类仅负责增量索引/字段迁移,确保从旧版本升级时自动补齐。
- *
- * @author MateClaw Team
- */
-@Slf4j
-@Component
-@Order(201)
-@RequiredArgsConstructor
-public class MemorySchemaMigration implements ApplicationRunner {
-
- private final JdbcTemplate jdbcTemplate;
-
- @Override
- public void run(ApplicationArguments args) {
- // 增量索引补齐(v1.1 新增的复合索引,旧版本可能没有)
- safeExecute("CREATE INDEX IF NOT EXISTS idx_memory_recall_candidates ON mate_memory_recall(agent_id, promoted, deleted)");
-
- // Session Search: MySQL FULLTEXT index on mate_message.content
- if (isMySql()) {
- safeExecute("ALTER TABLE mate_message ADD FULLTEXT INDEX ft_msg_content (content)");
- log.info("[MemorySchemaMigration] MySQL FULLTEXT index on mate_message.content created (or already exists)");
- }
-
- // max_iterations 升级:旧版默认 10,新版 25。确保已有 agent 也更新
- safeExecute("UPDATE mate_agent SET max_iterations = 25 WHERE max_iterations = 10 AND deleted = 0");
-
- log.debug("[MemorySchemaMigration] Incremental migration completed");
- }
-
- private boolean isMySql() {
- try {
- String url = jdbcTemplate.getDataSource().getConnection().getMetaData().getURL();
- return url != null && url.contains("mysql");
- } catch (Exception e) {
- return false;
- }
- }
-
- private void safeExecute(String sql) {
- try {
- jdbcTemplate.execute(sql);
- } catch (Exception e) {
- log.debug("[MemorySchemaMigration] Migration skipped (may already exist): {}", e.getMessage());
- }
- }
-}
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/EditFileTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/EditFileTool.java
index 6cb8e34a..7029d592 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/EditFileTool.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/EditFileTool.java
@@ -56,7 +56,12 @@ public class EditFileTool {
return errorResult(filePath, "oldText 和 newText 内容相同,无需替换");
}
- Path path = Paths.get(filePath).toAbsolutePath().normalize();
+ Path path;
+ try {
+ path = vip.mate.tool.guard.WorkspacePathGuard.validatePath(filePath);
+ } catch (IllegalArgumentException e) {
+ return errorResult(filePath, e.getMessage());
+ }
if (!Files.exists(path)) {
return errorResult(filePath, "文件不存在: " + path);
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/ReadFileTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/ReadFileTool.java
index 342c8d74..7af20381 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/ReadFileTool.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/ReadFileTool.java
@@ -62,7 +62,12 @@ public class ReadFileTool {
result.set("filePath", filePath);
try {
- Path path = Paths.get(filePath).toAbsolutePath().normalize();
+ Path path;
+ try {
+ path = vip.mate.tool.guard.WorkspacePathGuard.validatePath(filePath);
+ } catch (IllegalArgumentException e) {
+ return errorResult(filePath, e.getMessage());
+ }
// 文件存在性和类型校验
if (!Files.exists(path)) {
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/ShellExecuteTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/ShellExecuteTool.java
index b34466a0..0501b151 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/ShellExecuteTool.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/ShellExecuteTool.java
@@ -129,12 +129,22 @@ public class ShellExecuteTool {
* Unix: /bin/sh -c command
*/
private static ProcessBuilder buildShellProcess(String command) {
+ ProcessBuilder pb;
if (IS_WINDOWS) {
String winCommand = sanitizeWindowsCommand(command);
- return new ProcessBuilder("cmd.exe", "/D", "/S", "/C", winCommand);
+ pb = new ProcessBuilder("cmd.exe", "/D", "/S", "/C", winCommand);
} else {
- return new ProcessBuilder("/bin/sh", "-c", command);
+ pb = new ProcessBuilder("/bin/sh", "-c", command);
}
+
+ // 设置工作区活动目录
+ java.nio.file.Path workingDir = vip.mate.tool.guard.WorkspacePathGuard.getWorkingDirectory();
+ if (workingDir != null && java.nio.file.Files.isDirectory(workingDir)) {
+ pb.directory(workingDir.toFile());
+ log.info("[ShellExecute] Working directory set to: {}", workingDir);
+ }
+
+ return pb;
}
/**
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/ToolExecutionContext.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/ToolExecutionContext.java
index 7a4f5528..6e64553c 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/ToolExecutionContext.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/ToolExecutionContext.java
@@ -12,12 +12,21 @@ public final class ToolExecutionContext {
private static final ThreadLocal CONVERSATION_ID = new ThreadLocal<>();
private static final ThreadLocal USERNAME = new ThreadLocal<>();
+ /** 工作区活动目录(为空不限制) */
+ private static final ThreadLocal WORKSPACE_BASE_PATH = new ThreadLocal<>();
private ToolExecutionContext() {}
public static void set(String conversationId, String username) {
CONVERSATION_ID.set(conversationId);
USERNAME.set(username);
+ WORKSPACE_BASE_PATH.remove();
+ }
+
+ public static void set(String conversationId, String username, String workspaceBasePath) {
+ CONVERSATION_ID.set(conversationId);
+ USERNAME.set(username);
+ WORKSPACE_BASE_PATH.set(workspaceBasePath);
}
public static String conversationId() {
@@ -28,8 +37,14 @@ public final class ToolExecutionContext {
return USERNAME.get();
}
+ /** 获取当前工作区活动目录,为 null 表示不限制 */
+ public static String workspaceBasePath() {
+ return WORKSPACE_BASE_PATH.get();
+ }
+
public static void clear() {
CONVERSATION_ID.remove();
USERNAME.remove();
+ WORKSPACE_BASE_PATH.remove();
}
}
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/WriteFileTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/WriteFileTool.java
index 79cf72f5..44248eef 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/WriteFileTool.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/WriteFileTool.java
@@ -48,7 +48,12 @@ public class WriteFileTool {
content = "";
}
- Path path = Paths.get(filePath).toAbsolutePath().normalize();
+ Path path;
+ try {
+ path = vip.mate.tool.guard.WorkspacePathGuard.validatePath(filePath);
+ } catch (IllegalArgumentException e) {
+ return errorResult(filePath, e.getMessage());
+ }
// 如果路径是已有目录,拒绝
if (Files.isDirectory(path)) {
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/guard/WorkspacePathGuard.java b/mateclaw-server/src/main/java/vip/mate/tool/guard/WorkspacePathGuard.java
new file mode 100644
index 00000000..4a3917af
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/tool/guard/WorkspacePathGuard.java
@@ -0,0 +1,81 @@
+package vip.mate.tool.guard;
+
+import lombok.extern.slf4j.Slf4j;
+import vip.mate.tool.builtin.ToolExecutionContext;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * 工作区路径沙箱校验器
+ *
+ * 限制文件工具只能在工作区活动目录(basePath)及其子目录内操作。
+ * 跨平台支持:Windows / macOS / Linux 路径统一处理。
+ *
+ * 使用方式:在文件工具方法开头调用 {@link #validatePath(String)} 获取规范化路径。
+ *
+ * @author MateClaw Team
+ */
+@Slf4j
+public final class WorkspacePathGuard {
+
+ private WorkspacePathGuard() {}
+
+ /**
+ * 校验文件路径是否在当前工作区活动目录范围内。
+ *
+ * 从 {@link ToolExecutionContext#workspaceBasePath()} 读取当前活动目录。
+ * 为空时不限制(向后兼容)。
+ *
+ * @param rawPath 用户传入的原始路径
+ * @return 规范化后的绝对路径
+ * @throws IllegalArgumentException 路径不在允许范围内
+ */
+ public static Path validatePath(String rawPath) {
+ Path normalized = Paths.get(rawPath).toAbsolutePath().normalize();
+
+ String basePath = ToolExecutionContext.workspaceBasePath();
+ if (basePath == null || basePath.isBlank()) {
+ return normalized; // 未配置活动目录,不限制
+ }
+
+ Path root = Paths.get(basePath).toAbsolutePath().normalize();
+
+ // 先用 normalize 检查,再尝试 toRealPath 防符号链接逃逸
+ if (!normalized.startsWith(root)) {
+ throw new IllegalArgumentException(
+ "路径不在工作区允许范围内: " + normalized + ",允许的根目录: " + root);
+ }
+
+ // 对已存在的路径,解析符号链接后再次校验
+ try {
+ if (normalized.toFile().exists()) {
+ Path realPath = normalized.toRealPath();
+ Path realRoot = root.toFile().exists() ? root.toRealPath() : root;
+ if (!realPath.startsWith(realRoot)) {
+ throw new IllegalArgumentException(
+ "路径通过符号链接逃逸出工作区: " + realPath + ",允许的根目录: " + realRoot);
+ }
+ return realPath;
+ }
+ } catch (IOException e) {
+ log.debug("[WorkspacePathGuard] 无法解析真实路径(文件可能不存在): {}", normalized);
+ }
+
+ return normalized;
+ }
+
+ /**
+ * 获取当前工作区活动目录的 Path(用于设置 Shell 工作目录等)。
+ *
+ * @return 活动目录 Path,未配置时返回 null
+ */
+ public static Path getWorkingDirectory() {
+ String basePath = ToolExecutionContext.workspaceBasePath();
+ if (basePath == null || basePath.isBlank()) {
+ return null;
+ }
+ return Paths.get(basePath).toAbsolutePath().normalize();
+ }
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/guard/config/ToolGuardSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/tool/guard/config/ToolGuardSchemaMigration.java
deleted file mode 100644
index 37df4c3c..00000000
--- a/mateclaw-server/src/main/java/vip/mate/tool/guard/config/ToolGuardSchemaMigration.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package vip.mate.tool.guard.config;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
-
-/**
- * ToolGuard 增量 Schema 迁移
- *
- * 基础表 mate_tool_guard_rule / config / audit_log 已在 schema.sql / schema-mysql.sql 中定义。
- * 本类仅负责增量字段迁移,确保从旧版本升级时自动补齐新列。
- *
- * @author MateClaw Team
- */
-@Slf4j
-@Component
-@Order(100)
-@RequiredArgsConstructor
-public class ToolGuardSchemaMigration implements ApplicationRunner {
-
- private final JdbcTemplate jdbcTemplate;
-
- @Override
- public void run(ApplicationArguments args) {
- migrateGuardConfigAuditColumns();
- }
-
- /**
- * 为 mate_tool_guard_config 补充审计配置列(向已有表兼容迁移)
- */
- private void migrateGuardConfigAuditColumns() {
- safeAddColumn("mate_tool_guard_config", "audit_enabled", "BOOLEAN NOT NULL DEFAULT TRUE");
- safeAddColumn("mate_tool_guard_config", "audit_min_severity", "VARCHAR(16) NOT NULL DEFAULT 'INFO'");
- safeAddColumn("mate_tool_guard_config", "audit_retention_days", "INT NOT NULL DEFAULT 90");
- log.debug("[ToolGuardSchemaMigration] Incremental migration completed");
- }
-
- private void safeAddColumn(String table, String column, String definition) {
- try {
- Integer count = jdbcTemplate.queryForObject(
- "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?",
- Integer.class, table.toUpperCase(), column.toUpperCase());
- if (count != null && count > 0) {
- return;
- }
- jdbcTemplate.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
- } catch (Exception e) {
- log.debug("[ToolGuardSchemaMigration] Column migration skipped: {}", e.getMessage());
- }
- }
-}
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleSeedService.java b/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleSeedService.java
index 4f4daf5d..aa3e3a74 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleSeedService.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleSeedService.java
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
*/
@Slf4j
@Component
-@Order(110) // 在 ToolGuardSchemaMigration(100) 之后
+@Order(110) // Schema 由 Flyway 管理,在 Flyway 迁移完成后执行
@RequiredArgsConstructor
public class ToolGuardRuleSeedService implements ApplicationRunner {
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/config/WikiSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/wiki/config/WikiSchemaMigration.java
deleted file mode 100644
index f0fb9aa1..00000000
--- a/mateclaw-server/src/main/java/vip/mate/wiki/config/WikiSchemaMigration.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package vip.mate.wiki.config;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
-
-/**
- * Wiki 模块增量 Schema 迁移
- *
- * Wiki 基础表已在 schema.sql / schema-mysql.sql 中定义(随 DatabaseBootstrapRunner 初始化)。
- * 本类仅负责增量字段迁移,确保从旧版本升级时自动补齐新字段。
- *
- * 如果表已由 schema.sql 创建(全新安装),这里的迁移是无副作用的空操作。
- *
- * @author MateClaw Team
- */
-@Slf4j
-@Component
-@Order(202)
-@RequiredArgsConstructor
-public class WikiSchemaMigration implements ApplicationRunner {
-
- private final JdbcTemplate jdbcTemplate;
-
- @Override
- public void run(ApplicationArguments args) {
- migrateKnowledgeBaseColumns();
- }
-
- /**
- * 增量字段迁移(兼容从 wiki 功能上线前的旧版本升级)
- */
- private void migrateKnowledgeBaseColumns() {
- try {
- // v1.1: 添加 source_directory 列(本地目录扫描功能)
- jdbcTemplate.execute("ALTER TABLE mate_wiki_knowledge_base ADD COLUMN IF NOT EXISTS source_directory VARCHAR(512)");
- log.debug("[WikiSchemaMigration] Incremental migration completed");
- } catch (Exception e) {
- // MySQL 不支持 ADD COLUMN IF NOT EXISTS,忽略已存在的错误
- String msg = e.getMessage();
- if (msg != null && (msg.contains("Duplicate column") || msg.contains("already exists"))) {
- log.debug("[WikiSchemaMigration] Column source_directory already exists, skipping");
- } else {
- log.warn("[WikiSchemaMigration] Migration warning: {}", msg);
- }
- }
- }
-}
diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/conversation/config/ConversationSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/workspace/conversation/config/ConversationSchemaMigration.java
index 6cc601a1..58e32fc9 100644
--- a/mateclaw-server/src/main/java/vip/mate/workspace/conversation/config/ConversationSchemaMigration.java
+++ b/mateclaw-server/src/main/java/vip/mate/workspace/conversation/config/ConversationSchemaMigration.java
@@ -7,6 +7,12 @@ import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
+/**
+ * Conversation runtime data fixes (executed every startup).
+ *
+ * Schema migrations have been moved to Flyway V4__conversation_extensions.sql.
+ * This runner only handles data normalization that must run on every boot.
+ */
@Slf4j
@Component
@RequiredArgsConstructor
@@ -16,13 +22,14 @@ public class ConversationSchemaMigration implements ApplicationRunner {
@Override
public void run(org.springframework.boot.ApplicationArguments args) throws Exception {
- ensureColumn("mate_message", "content_parts", "TEXT");
- ensureColumn("mate_message", "prompt_tokens", "INT DEFAULT 0");
- ensureColumn("mate_message", "completion_tokens", "INT DEFAULT 0");
- ensureColumn("mate_message", "runtime_model", "VARCHAR(128)");
- ensureColumn("mate_message", "runtime_provider", "VARCHAR(64)");
- ensureColumn("mate_conversation", "stream_status", "VARCHAR(16) NOT NULL DEFAULT 'idle'");
- // 启动时重置孤儿状态:上次意外关机可能残留 running 状态
+ resetOrphanedStreams();
+ normalizeSharedChannelConversationOwners();
+ }
+
+ /**
+ * Reset orphaned 'running' conversations to 'idle' (crash recovery).
+ */
+ private void resetOrphanedStreams() {
try {
int updated = jdbcTemplate.update(
"UPDATE mate_conversation SET stream_status = 'idle' WHERE stream_status = 'running'");
@@ -32,8 +39,6 @@ public class ConversationSchemaMigration implements ApplicationRunner {
} catch (DataAccessException e) {
log.debug("Skipping orphan reset (table may not exist yet): {}", e.getMessage());
}
-
- normalizeSharedChannelConversationOwners();
}
private void normalizeSharedChannelConversationOwners() {
@@ -61,21 +66,4 @@ public class ConversationSchemaMigration implements ApplicationRunner {
log.debug("Skipping shared channel owner normalization: {}", e.getMessage());
}
}
-
- private void ensureColumn(String tableName, String columnName, String ddl) {
- String sql = "ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + columnName + " " + ddl;
- try {
- jdbcTemplate.execute(sql);
- log.info("Ensured schema column exists: {}.{}", tableName, columnName);
- } catch (DataAccessException e) {
- String message = e.getMessage() != null ? e.getMessage().toLowerCase() : "";
- if (message.contains("duplicate column") || message.contains("already exists")
- || message.contains("not found")) {
- log.info("Schema migration skipped for {}.{}: {}", tableName, columnName,
- message.contains("not found") ? "table not yet created" : "column already exists");
- return;
- }
- throw e;
- }
- }
}
diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/core/config/WorkspaceSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/workspace/core/config/WorkspaceSchemaMigration.java
index 04bc48b1..35547b86 100644
--- a/mateclaw-server/src/main/java/vip/mate/workspace/core/config/WorkspaceSchemaMigration.java
+++ b/mateclaw-server/src/main/java/vip/mate/workspace/core/config/WorkspaceSchemaMigration.java
@@ -67,7 +67,7 @@ public class WorkspaceSchemaMigration implements ApplicationRunner {
// 查找不在默认工作区中的用户
int inserted = jdbcTemplate.update("""
INSERT INTO mate_workspace_member (id, workspace_id, user_id, role, create_time, update_time, deleted)
- SELECT u.id, 1, u.id, u.role, NOW(), NOW(), 0
+ SELECT u.id, 1, u.id, CASE WHEN u.role = 'admin' THEN 'owner' ELSE u.role END, NOW(), NOW(), 0
FROM mate_user u
WHERE u.deleted = 0
AND NOT EXISTS (
diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/core/controller/WorkspaceController.java b/mateclaw-server/src/main/java/vip/mate/workspace/core/controller/WorkspaceController.java
index a95d1ffc..6a1c2ae8 100644
--- a/mateclaw-server/src/main/java/vip/mate/workspace/core/controller/WorkspaceController.java
+++ b/mateclaw-server/src/main/java/vip/mate/workspace/core/controller/WorkspaceController.java
@@ -56,7 +56,7 @@ public class WorkspaceController {
@PutMapping("/{id}")
public R update(@PathVariable Long id, @RequestBody WorkspaceEntity entity, Authentication auth) {
Long userId = resolveUserId(auth);
- workspaceService.requirePermission(id, userId, "owner");
+ workspaceService.requirePermission(id, userId, "admin");
entity.setId(id);
return R.ok(workspaceService.update(entity));
}
diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/core/model/WorkspaceEntity.java b/mateclaw-server/src/main/java/vip/mate/workspace/core/model/WorkspaceEntity.java
index 83cccdc1..263443e2 100644
--- a/mateclaw-server/src/main/java/vip/mate/workspace/core/model/WorkspaceEntity.java
+++ b/mateclaw-server/src/main/java/vip/mate/workspace/core/model/WorkspaceEntity.java
@@ -32,6 +32,9 @@ public class WorkspaceEntity {
/** 拥有者用户 ID */
private Long ownerId;
+ /** 工作区活动目录(限制文件工具的访问范围,为空不限制) */
+ private String basePath;
+
/** 工作区级配置(JSON) */
@TableField(value = "settings_json", updateStrategy = FieldStrategy.ALWAYS)
private String settingsJson;
diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java b/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java
index 799a55a0..9ab7f7eb 100644
--- a/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java
+++ b/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java
@@ -97,12 +97,16 @@ public class WorkspaceService {
public WorkspaceEntity update(WorkspaceEntity entity) {
WorkspaceEntity existing = getById(entity.getId());
+ // slug 为 null 时保留原值,不做修改
+ if (entity.getSlug() == null) {
+ entity.setSlug(existing.getSlug());
+ }
// 不允许修改默认工作区的 slug
if (DEFAULT_SLUG.equals(existing.getSlug()) && !DEFAULT_SLUG.equals(entity.getSlug())) {
throw new MateClawException("不能修改默认工作区的标识");
}
// 验证 slug 唯一性(如果修改了 slug)
- if (entity.getSlug() != null && !entity.getSlug().equals(existing.getSlug())) {
+ if (!entity.getSlug().equals(existing.getSlug())) {
if (getBySlug(entity.getSlug()) != null) {
throw new MateClawException("工作区标识已存在: " + entity.getSlug());
}
diff --git a/mateclaw-server/src/main/resources/application-mysql.yml b/mateclaw-server/src/main/resources/application-mysql.yml
index cccd80ff..fa5816ba 100644
--- a/mateclaw-server/src/main/resources/application-mysql.yml
+++ b/mateclaw-server/src/main/resources/application-mysql.yml
@@ -5,6 +5,10 @@ spring:
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:mateclaw123}
+ flyway:
+ locations:
+ - classpath:db/migration/mysql
+
h2:
console:
enabled: false
diff --git a/mateclaw-server/src/main/resources/application.yml b/mateclaw-server/src/main/resources/application.yml
index 38edb76e..e386ad52 100644
--- a/mateclaw-server/src/main/resources/application.yml
+++ b/mateclaw-server/src/main/resources/application.yml
@@ -25,6 +25,16 @@ spring:
init:
mode: never
+ # Flyway 数据库版本迁移
+ flyway:
+ enabled: true
+ baseline-on-migrate: true
+ baseline-version: "1"
+ locations:
+ - classpath:db/migration/h2
+ validate-on-migrate: true
+ clean-disabled: true
+
h2:
console:
enabled: true
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 0a4514d2..2f64af3b 100644
--- a/mateclaw-server/src/main/resources/db/data-mysql-en.sql
+++ b/mateclaw-server/src/main/resources/db/data-mysql-en.sql
@@ -1236,6 +1236,23 @@ VALUES (1000000008, 'WeChat', 'weixin', 1000000001, '', '{
'WeChat personal account channel (iLink Bot HTTP long polling). Get bot_token by scanning QR code to login, or enter existing token. Based on iLink Bot API, supports text, image, voice (ASR), file, and video messages', NOW(), NOW(), 0)
ON DUPLICATE KEY UPDATE name=VALUES(name), channel_type=VALUES(channel_type), agent_id=VALUES(agent_id), bot_prefix=VALUES(bot_prefix), config_json=VALUES(config_json), enabled=VALUES(enabled), description=VALUES(description), update_time=VALUES(update_time), deleted=VALUES(deleted);
+-- 9. Slack (disabled by default, requires bot_token / app_token)
+INSERT INTO mate_channel (id, name, channel_type, agent_id, bot_prefix, config_json, enabled, description, create_time, update_time, deleted)
+VALUES (1000000009, 'Slack Bot', 'slack', 1000000001, '', '{
+ "bot_token": "",
+ "app_token": "",
+ "signing_secret": "",
+ "dm_policy": "open",
+ "group_policy": "mention",
+ "allow_from": [],
+ "deny_message": "Sorry, you do not have permission",
+ "filter_thinking": true,
+ "filter_tool_messages": true,
+ "message_format": "auto"
+}', FALSE,
+ 'Slack channel (Socket Mode). Get Bot Token (xoxb-) and App-Level Token (xapp-) from Slack App settings, enable Socket Mode to start using.', NOW(), NOW(), 0)
+ON DUPLICATE KEY UPDATE name=VALUES(name), channel_type=VALUES(channel_type), agent_id=VALUES(agent_id), bot_prefix=VALUES(bot_prefix), config_json=VALUES(config_json), enabled=VALUES(enabled), description=VALUES(description), update_time=VALUES(update_time), deleted=VALUES(deleted);
+
-- ==================== Example Cron Jobs ====================
INSERT INTO mate_cron_job (id, name, cron_expression, timezone, agent_id, task_type, trigger_message, request_body, enabled, create_time, update_time, deleted)
VALUES (1000100001, 'Daily Greeting', '0 9 * * *', 'Asia/Shanghai', 1000000001, 'text', 'Good morning! Please give me today''s weather report and an inspirational quote.', NULL, TRUE, NOW(), NOW(), 0)
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 812bba94..29cfcb6c 100644
--- a/mateclaw-server/src/main/resources/db/data-mysql-zh.sql
+++ b/mateclaw-server/src/main/resources/db/data-mysql-zh.sql
@@ -1238,6 +1238,23 @@ VALUES (1000000008, '微信', 'weixin', 1000000001, '', '{
'微信个人号渠道(iLink Bot HTTP 长轮询)。通过扫描二维码登录获取 bot_token,或直接填入已有 token。基于 iLink Bot API,支持文本、图片、语音(ASR)、文件、视频消息', NOW(), NOW(), 0)
ON DUPLICATE KEY UPDATE name=VALUES(name), channel_type=VALUES(channel_type), agent_id=VALUES(agent_id), bot_prefix=VALUES(bot_prefix), config_json=VALUES(config_json), enabled=VALUES(enabled), description=VALUES(description), update_time=VALUES(update_time), deleted=VALUES(deleted);
+-- 9. Slack(默认禁用,需配置 bot_token / app_token)
+INSERT INTO mate_channel (id, name, channel_type, agent_id, bot_prefix, config_json, enabled, description, create_time, update_time, deleted)
+VALUES (1000000009, 'Slack Bot', 'slack', 1000000001, '', '{
+ "bot_token": "",
+ "app_token": "",
+ "signing_secret": "",
+ "dm_policy": "open",
+ "group_policy": "mention",
+ "allow_from": [],
+ "deny_message": "抱歉,您没有使用权限",
+ "filter_thinking": true,
+ "filter_tool_messages": true,
+ "message_format": "auto"
+}', FALSE,
+ 'Slack 渠道(Socket Mode)。在 Slack App 后台获取 Bot Token(xoxb-)和 App-Level Token(xapp-),启用 Socket Mode 后即可使用。', NOW(), NOW(), 0)
+ON DUPLICATE KEY UPDATE name=VALUES(name), channel_type=VALUES(channel_type), agent_id=VALUES(agent_id), bot_prefix=VALUES(bot_prefix), config_json=VALUES(config_json), enabled=VALUES(enabled), description=VALUES(description), update_time=VALUES(update_time), deleted=VALUES(deleted);
+
-- ==================== 示例定时任务 ====================
INSERT INTO mate_cron_job (id, name, cron_expression, timezone, agent_id, task_type, trigger_message, request_body, enabled, create_time, update_time, deleted)
VALUES (1000100001, '每日问候', '0 9 * * *', 'Asia/Shanghai', 1000000001, 'text', '早上好!请给我今天的天气播报和一句励志名言。', NULL, TRUE, NOW(), NOW(), 0)
diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V1__baseline_schema.sql b/mateclaw-server/src/main/resources/db/migration/h2/V1__baseline_schema.sql
new file mode 100644
index 00000000..6d1cb0c8
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/h2/V1__baseline_schema.sql
@@ -0,0 +1,642 @@
+-- MateClaw 数据库初始化脚本
+-- 兼容 H2(开发模式)和 MySQL 8.0+(生产模式)
+
+-- 用户表
+CREATE TABLE IF NOT EXISTS mate_user (
+ id BIGINT NOT NULL PRIMARY KEY,
+ username VARCHAR(64) NOT NULL UNIQUE,
+ password VARCHAR(200) NOT NULL,
+ nickname VARCHAR(64),
+ avatar VARCHAR(256),
+ email VARCHAR(128),
+ role VARCHAR(32) NOT NULL DEFAULT 'user',
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- Agent 配置表
+CREATE TABLE IF NOT EXISTS mate_agent (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ agent_type VARCHAR(32) NOT NULL DEFAULT 'react',
+ system_prompt TEXT,
+ model_name VARCHAR(128),
+ max_iterations INT NOT NULL DEFAULT 10,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ icon VARCHAR(256),
+ tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 模型配置表
+CREATE TABLE IF NOT EXISTS mate_model_config (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ provider VARCHAR(64) NOT NULL DEFAULT 'dashscope',
+ model_name VARCHAR(128) NOT NULL,
+ description TEXT,
+ temperature DOUBLE,
+ max_tokens INT,
+ top_p DOUBLE,
+ builtin BOOLEAN NOT NULL DEFAULT TRUE,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ is_default BOOLEAN NOT NULL DEFAULT FALSE,
+ max_input_tokens INT DEFAULT 0,
+ enable_search BOOLEAN DEFAULT FALSE,
+ search_strategy VARCHAR(32) DEFAULT NULL,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 模型 Provider 表
+CREATE TABLE IF NOT EXISTS mate_model_provider (
+ provider_id VARCHAR(64) NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ api_key_prefix VARCHAR(32),
+ chat_model VARCHAR(64),
+ api_key VARCHAR(256),
+ base_url VARCHAR(512),
+ generate_kwargs TEXT,
+ is_custom BOOLEAN NOT NULL DEFAULT FALSE,
+ is_local BOOLEAN NOT NULL DEFAULT FALSE,
+ support_model_discovery BOOLEAN NOT NULL DEFAULT FALSE,
+ support_connection_check BOOLEAN NOT NULL DEFAULT FALSE,
+ freeze_url BOOLEAN NOT NULL DEFAULT FALSE,
+ require_api_key BOOLEAN NOT NULL DEFAULT TRUE,
+ auth_type VARCHAR(16) NOT NULL DEFAULT 'api_key',
+ oauth_access_token TEXT,
+ oauth_refresh_token TEXT,
+ oauth_expires_at BIGINT,
+ oauth_account_id VARCHAR(128),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+);
+
+-- 系统设置表
+CREATE TABLE IF NOT EXISTS mate_system_setting (
+ id BIGINT NOT NULL PRIMARY KEY,
+ setting_key VARCHAR(128) NOT NULL UNIQUE,
+ setting_value TEXT,
+ description VARCHAR(256),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+);
+
+-- 技能表
+CREATE TABLE IF NOT EXISTS mate_skill (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ skill_type VARCHAR(32) NOT NULL DEFAULT 'dynamic',
+ icon VARCHAR(256),
+ version VARCHAR(32),
+ author VARCHAR(64),
+ config_json TEXT,
+ source_code TEXT,
+ skill_content TEXT,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ builtin BOOLEAN NOT NULL DEFAULT FALSE,
+ tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 工具表
+CREATE TABLE IF NOT EXISTS mate_tool (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ display_name VARCHAR(128),
+ description TEXT,
+ tool_type VARCHAR(32) NOT NULL DEFAULT 'builtin',
+ bean_name VARCHAR(128),
+ icon VARCHAR(256),
+ mcp_endpoint VARCHAR(256),
+ params_schema TEXT,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ builtin BOOLEAN NOT NULL DEFAULT FALSE,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 渠道表
+CREATE TABLE IF NOT EXISTS mate_channel (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ channel_type VARCHAR(32) NOT NULL,
+ agent_id BIGINT,
+ bot_prefix VARCHAR(64),
+ config_json TEXT,
+ enabled BOOLEAN NOT NULL DEFAULT FALSE,
+ description VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 会话表
+CREATE TABLE IF NOT EXISTS mate_conversation (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(64) NOT NULL UNIQUE,
+ title VARCHAR(256),
+ agent_id BIGINT,
+ username VARCHAR(64),
+ message_count INT NOT NULL DEFAULT 0,
+ last_message TEXT,
+ last_active_time DATETIME,
+ stream_status VARCHAR(16) NOT NULL DEFAULT 'idle',
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 消息表
+CREATE TABLE IF NOT EXISTS mate_message (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(64) NOT NULL,
+ role VARCHAR(32) NOT NULL,
+ content TEXT,
+ content_parts TEXT,
+ tool_name VARCHAR(128),
+ token_usage INT,
+ prompt_tokens INT DEFAULT 0,
+ completion_tokens INT DEFAULT 0,
+ runtime_model VARCHAR(128),
+ runtime_provider VARCHAR(64),
+ status VARCHAR(32) NOT NULL DEFAULT 'completed',
+ metadata JSON, -- 存储 toolCalls, plan, currentPhase, pendingApproval 等元数据
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 执行计划表
+CREATE TABLE IF NOT EXISTS mate_plan (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id VARCHAR(64),
+ goal TEXT,
+ status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ total_steps INT NOT NULL DEFAULT 0,
+ completed_steps INT NOT NULL DEFAULT 0,
+ summary TEXT,
+ start_time DATETIME,
+ end_time DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 子计划步骤表
+CREATE TABLE IF NOT EXISTS mate_sub_plan (
+ id BIGINT NOT NULL PRIMARY KEY,
+ plan_id BIGINT NOT NULL,
+ step_index INT NOT NULL,
+ description TEXT,
+ status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ result TEXT,
+ start_time DATETIME,
+ end_time DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 定时任务表
+CREATE TABLE IF NOT EXISTS mate_cron_job (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ cron_expression VARCHAR(128) NOT NULL,
+ timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai',
+ agent_id BIGINT NOT NULL,
+ task_type VARCHAR(16) NOT NULL DEFAULT 'text',
+ trigger_message TEXT,
+ request_body TEXT,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ next_run_time DATETIME,
+ last_run_time DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 索引
+CREATE INDEX IF NOT EXISTS idx_message_conversation ON mate_message(conversation_id);
+CREATE INDEX IF NOT EXISTS idx_conversation_username ON mate_conversation(username);
+CREATE INDEX IF NOT EXISTS idx_sub_plan_plan_id ON mate_sub_plan(plan_id);
+CREATE INDEX IF NOT EXISTS idx_model_config_model_name ON mate_model_config(model_name);
+
+-- 渠道会话存储表(主动推送标识缓存)
+CREATE TABLE IF NOT EXISTS mate_channel_session (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(128) NOT NULL UNIQUE,
+ channel_type VARCHAR(32) NOT NULL,
+ target_id VARCHAR(512) NOT NULL,
+ sender_id VARCHAR(128),
+ sender_name VARCHAR(128),
+ channel_id BIGINT,
+ last_active_time DATETIME NOT NULL,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+CREATE INDEX IF NOT EXISTS idx_channel_session_type ON mate_channel_session(channel_type);
+CREATE INDEX IF NOT EXISTS idx_channel_session_channel_id ON mate_channel_session(channel_id);
+
+-- 工作区文件表(Agent 级 Markdown 文档管理)
+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);
+
+-- ==================== MCP Server 管理 ====================
+
+-- MCP Server 配置表(独立于 mate_tool,一个 server 可暴露多个 tools)
+CREATE TABLE IF NOT EXISTS mate_mcp_server (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ transport VARCHAR(32) NOT NULL DEFAULT 'stdio',
+ url VARCHAR(512),
+ headers_json TEXT,
+ command VARCHAR(512),
+ args_json TEXT,
+ env_json TEXT,
+ cwd VARCHAR(512),
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ connect_timeout_seconds INT NOT NULL DEFAULT 30,
+ read_timeout_seconds INT NOT NULL DEFAULT 30,
+ last_status VARCHAR(32) NOT NULL DEFAULT 'disconnected',
+ last_error TEXT,
+ last_connected_time DATETIME,
+ tool_count INT NOT NULL DEFAULT 0,
+ builtin 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_mcp_server_enabled ON mate_mcp_server(enabled);
+
+-- ==================== 工具安全治理(ToolGuard) ====================
+
+-- 工具审批表
+CREATE TABLE IF NOT EXISTS mate_tool_approval (
+ id BIGINT NOT NULL PRIMARY KEY,
+ pending_id VARCHAR(32) NOT NULL UNIQUE,
+ conversation_id VARCHAR(128) NOT NULL,
+ user_id VARCHAR(64),
+ agent_id VARCHAR(64),
+ channel_type VARCHAR(32),
+ requester_name VARCHAR(128),
+ reply_target VARCHAR(512),
+ tool_name VARCHAR(128) NOT NULL,
+ tool_arguments TEXT,
+ tool_call_payload TEXT,
+ tool_call_hash VARCHAR(64),
+ sibling_tool_calls TEXT,
+ summary TEXT,
+ findings_json TEXT,
+ max_severity VARCHAR(16),
+ status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
+ resolved_by VARCHAR(64),
+ created_at DATETIME NOT NULL,
+ resolved_at DATETIME,
+ expire_at DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+CREATE INDEX IF NOT EXISTS idx_tool_approval_conv ON mate_tool_approval(conversation_id);
+CREATE INDEX IF NOT EXISTS idx_tool_approval_status ON mate_tool_approval(status);
+CREATE INDEX IF NOT EXISTS idx_tool_approval_pending_id ON mate_tool_approval(pending_id);
+
+-- 安全规则表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_rule (
+ id BIGINT NOT NULL PRIMARY KEY,
+ rule_id VARCHAR(64) NOT NULL UNIQUE,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ tool_name VARCHAR(128),
+ param_name VARCHAR(128),
+ category VARCHAR(64) NOT NULL,
+ severity VARCHAR(16) NOT NULL,
+ decision VARCHAR(16) NOT NULL DEFAULT 'NEEDS_APPROVAL',
+ pattern VARCHAR(512) NOT NULL,
+ exclude_pattern VARCHAR(512),
+ remediation TEXT,
+ builtin BOOLEAN NOT NULL DEFAULT FALSE,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ priority INT NOT NULL DEFAULT 100,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 安全全局配置表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_config (
+ id BIGINT NOT NULL PRIMARY KEY,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ guard_scope VARCHAR(32) NOT NULL DEFAULT 'all',
+ guarded_tools_json TEXT,
+ denied_tools_json TEXT,
+ file_guard_enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ sensitive_paths_json TEXT,
+ audit_enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ audit_min_severity VARCHAR(16) NOT NULL DEFAULT 'INFO',
+ audit_retention_days INT NOT NULL DEFAULT 90,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+);
+
+-- 安全审计日志表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_audit_log (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(128),
+ agent_id VARCHAR(64),
+ user_id VARCHAR(64),
+ channel_type VARCHAR(32),
+ tool_name VARCHAR(128) NOT NULL,
+ tool_params_json TEXT,
+ decision VARCHAR(16) NOT NULL,
+ max_severity VARCHAR(16),
+ findings_json TEXT,
+ pending_id VARCHAR(32),
+ replay_payload_hash VARCHAR(64),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 外部数据源表(查数功能)
+CREATE TABLE IF NOT EXISTS mate_datasource (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description VARCHAR(512),
+ db_type VARCHAR(32) NOT NULL,
+ host VARCHAR(256) NOT NULL,
+ port INT NOT NULL,
+ database_name VARCHAR(128) NOT NULL,
+ username VARCHAR(128),
+ password VARCHAR(512),
+ extra_params VARCHAR(512),
+ schema_name VARCHAR(128),
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ last_test_time DATETIME,
+ last_test_ok BOOLEAN,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+CREATE INDEX IF NOT EXISTS idx_guard_audit_conv ON mate_tool_guard_audit_log(conversation_id);
+CREATE INDEX IF NOT EXISTS idx_guard_audit_time ON mate_tool_guard_audit_log(create_time);
+
+-- 记忆召回追踪表(Dreaming 评分驱动记忆整合)
+CREATE TABLE IF NOT EXISTS mate_memory_recall (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ filename VARCHAR(256) NOT NULL,
+ snippet_hash VARCHAR(64),
+ snippet_preview VARCHAR(512),
+ recall_count INT NOT NULL DEFAULT 0,
+ daily_count INT NOT NULL DEFAULT 0,
+ query_hashes TEXT,
+ 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);
+CREATE INDEX IF NOT EXISTS idx_memory_recall_agent_file ON mate_memory_recall(agent_id, filename);
+CREATE INDEX IF NOT EXISTS idx_memory_recall_score ON mate_memory_recall(agent_id, score);
+CREATE INDEX IF NOT EXISTS idx_memory_recall_candidates ON mate_memory_recall(agent_id, promoted, deleted);
+
+-- 补充复合索引(高频查询优化)
+CREATE INDEX IF NOT EXISTS idx_message_conv_time ON mate_message(conversation_id, create_time);
+CREATE INDEX IF NOT EXISTS idx_workspace_file_agent_enabled ON mate_workspace_file(agent_id, enabled);
+
+-- 清理 Codex 不支持的 ChatGPT OAuth 模型(gpt-4o, o3, o4-mini 在 Codex 模式下不可用)
+DELETE FROM mate_model_config WHERE provider = 'openai-chatgpt' AND model_name IN ('gpt-4o', 'o3', 'o4-mini');
+
+-- ==================== 异步任务(视频/图片生成等长耗时操作) ====================
+
+CREATE TABLE IF NOT EXISTS mate_async_task (
+ id BIGINT NOT NULL PRIMARY KEY,
+ task_id VARCHAR(64) NOT NULL UNIQUE,
+ task_type VARCHAR(32) NOT NULL,
+ status VARCHAR(16) NOT NULL DEFAULT 'pending',
+ conversation_id VARCHAR(128),
+ message_id BIGINT,
+ provider_name VARCHAR(64),
+ provider_task_id VARCHAR(128),
+ request_json TEXT,
+ result_json TEXT,
+ error_message VARCHAR(512),
+ progress INT DEFAULT 0,
+ created_by VARCHAR(64),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS idx_async_task_conv ON mate_async_task(conversation_id);
+CREATE INDEX IF NOT EXISTS idx_async_task_status ON mate_async_task(status);
+
+-- ==================== Wiki 知识库 ====================
+
+CREATE TABLE IF NOT EXISTS mate_wiki_knowledge_base (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ agent_id BIGINT,
+ config_content CLOB,
+ source_directory VARCHAR(512),
+ status VARCHAR(32) NOT NULL DEFAULT 'active',
+ page_count INT NOT NULL DEFAULT 0,
+ raw_count INT NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wiki_kb_agent ON mate_wiki_knowledge_base(agent_id);
+
+CREATE TABLE IF NOT EXISTS mate_wiki_raw_material (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ title VARCHAR(256) NOT NULL,
+ source_type VARCHAR(32) NOT NULL DEFAULT 'text',
+ source_path VARCHAR(512),
+ original_content CLOB,
+ extracted_text CLOB,
+ content_hash VARCHAR(64),
+ file_size BIGINT NOT NULL DEFAULT 0,
+ processing_status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ last_processed_at DATETIME,
+ error_message VARCHAR(512),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wiki_raw_kb ON mate_wiki_raw_material(kb_id);
+CREATE INDEX IF NOT EXISTS idx_wiki_raw_status ON mate_wiki_raw_material(kb_id, processing_status);
+
+CREATE TABLE IF NOT EXISTS mate_wiki_page (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ slug VARCHAR(256) NOT NULL,
+ title VARCHAR(256) NOT NULL,
+ content CLOB,
+ summary VARCHAR(1024),
+ outgoing_links CLOB,
+ source_raw_ids CLOB,
+ version INT NOT NULL DEFAULT 1,
+ last_updated_by VARCHAR(32) NOT NULL DEFAULT 'ai',
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ CONSTRAINT uk_wiki_page_kb_slug UNIQUE (kb_id, slug)
+);
+CREATE INDEX IF NOT EXISTS idx_wiki_page_kb ON mate_wiki_page(kb_id);
+
+-- =============================================
+-- 工作区表(Phase 2)
+-- =============================================
+
+-- 工作区
+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)
+);
+
+-- 工作区成员
+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);
+
+-- =============================================
+-- Agent-Skill / Agent-Tool 绑定表(Phase 3 Sprint 2)
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS mate_agent_skill (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ skill_id BIGINT NOT NULL,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ config_json TEXT,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_agent_skill ON mate_agent_skill(agent_id, skill_id);
+
+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,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_agent_tool ON mate_agent_tool(agent_id, tool_name);
+
+-- =============================================
+-- CronJob 执行历史(Phase 3 Sprint 3)
+-- =============================================
+CREATE TABLE IF NOT EXISTS mate_cron_job_run (
+ id BIGINT NOT NULL PRIMARY KEY,
+ cron_job_id BIGINT NOT NULL,
+ conversation_id VARCHAR(64),
+ status VARCHAR(32) NOT NULL,
+ trigger_type VARCHAR(32) NOT NULL DEFAULT 'scheduled',
+ started_at DATETIME NOT NULL,
+ finished_at DATETIME,
+ error_message TEXT,
+ token_usage INT DEFAULT 0,
+ create_time DATETIME NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_cron_run_job ON mate_cron_job_run(cron_job_id, started_at);
+
+-- =============================================
+-- 用量日统计(Phase 3 Sprint 3)
+-- =============================================
+CREATE TABLE IF NOT EXISTS mate_usage_daily (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT NOT NULL,
+ agent_id BIGINT,
+ stat_date DATE NOT NULL,
+ conversation_count INT DEFAULT 0,
+ message_count INT DEFAULT 0,
+ total_tokens BIGINT DEFAULT 0,
+ prompt_tokens BIGINT DEFAULT 0,
+ completion_tokens BIGINT DEFAULT 0,
+ tool_call_count INT DEFAULT 0,
+ error_count INT DEFAULT 0,
+ create_time DATETIME NOT NULL
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_usage_daily ON mate_usage_daily(workspace_id, agent_id, stat_date);
+
+-- =============================================
+-- 操作审计事件表(Phase 3 Sprint 1)
+-- =============================================
+CREATE TABLE IF NOT EXISTS mate_audit_event (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT,
+ user_id BIGINT NOT NULL,
+ username VARCHAR(64) NOT NULL,
+ action VARCHAR(64) NOT NULL,
+ resource_type VARCHAR(64) NOT NULL,
+ resource_id VARCHAR(128),
+ resource_name VARCHAR(256),
+ detail_json TEXT,
+ ip_address VARCHAR(64),
+ user_agent VARCHAR(256),
+ create_time DATETIME NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_audit_ws_time ON mate_audit_event(workspace_id, create_time);
+CREATE INDEX IF NOT EXISTS idx_audit_user ON mate_audit_event(user_id);
+CREATE INDEX IF NOT EXISTS idx_audit_resource ON mate_audit_event(resource_type, resource_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V1__baseline_schema.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V1__baseline_schema.sql
new file mode 100644
index 00000000..c00f11d0
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/mysql/V1__baseline_schema.sql
@@ -0,0 +1,629 @@
+-- MateClaw 数据库初始化脚本(MySQL / MariaDB 专用)
+
+-- 用户表
+CREATE TABLE IF NOT EXISTS mate_user (
+ id BIGINT NOT NULL PRIMARY KEY,
+ username VARCHAR(64) NOT NULL UNIQUE,
+ password VARCHAR(200) NOT NULL,
+ nickname VARCHAR(64),
+ avatar VARCHAR(256),
+ email VARCHAR(128),
+ role VARCHAR(32) NOT NULL DEFAULT 'user',
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- Agent 配置表
+CREATE TABLE IF NOT EXISTS mate_agent (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ agent_type VARCHAR(32) NOT NULL DEFAULT 'react',
+ system_prompt TEXT,
+ model_name VARCHAR(128),
+ max_iterations INT NOT NULL DEFAULT 10,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ icon VARCHAR(256),
+ tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 模型配置表
+CREATE TABLE IF NOT EXISTS mate_model_config (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ provider VARCHAR(64) NOT NULL DEFAULT 'dashscope',
+ model_name VARCHAR(128) NOT NULL,
+ description TEXT,
+ temperature DOUBLE,
+ max_tokens INT,
+ top_p DOUBLE,
+ builtin TINYINT(1) NOT NULL DEFAULT 1,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ is_default TINYINT(1) NOT NULL DEFAULT 0,
+ max_input_tokens INT DEFAULT 0,
+ enable_search TINYINT(1) DEFAULT 0,
+ search_strategy VARCHAR(32) DEFAULT NULL,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_model_config_model_name (model_name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 模型 Provider 表
+CREATE TABLE IF NOT EXISTS mate_model_provider (
+ provider_id VARCHAR(64) NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ api_key_prefix VARCHAR(32),
+ chat_model VARCHAR(64),
+ api_key VARCHAR(256),
+ base_url VARCHAR(512),
+ generate_kwargs TEXT,
+ is_custom TINYINT(1) NOT NULL DEFAULT 0,
+ is_local TINYINT(1) NOT NULL DEFAULT 0,
+ support_model_discovery TINYINT(1) NOT NULL DEFAULT 0,
+ support_connection_check TINYINT(1) NOT NULL DEFAULT 0,
+ freeze_url TINYINT(1) NOT NULL DEFAULT 0,
+ require_api_key TINYINT(1) NOT NULL DEFAULT 1,
+ auth_type VARCHAR(16) NOT NULL DEFAULT 'api_key',
+ oauth_access_token TEXT,
+ oauth_refresh_token TEXT,
+ oauth_expires_at BIGINT,
+ oauth_account_id VARCHAR(128),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 系统设置表
+CREATE TABLE IF NOT EXISTS mate_system_setting (
+ id BIGINT NOT NULL PRIMARY KEY,
+ setting_key VARCHAR(128) NOT NULL UNIQUE,
+ setting_value TEXT,
+ description VARCHAR(256),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 技能表
+CREATE TABLE IF NOT EXISTS mate_skill (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ skill_type VARCHAR(32) NOT NULL DEFAULT 'dynamic',
+ icon VARCHAR(256),
+ version VARCHAR(32),
+ author VARCHAR(64),
+ config_json TEXT,
+ source_code TEXT,
+ skill_content TEXT,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ builtin TINYINT(1) NOT NULL DEFAULT 0,
+ tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 工具表
+CREATE TABLE IF NOT EXISTS mate_tool (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ display_name VARCHAR(128),
+ description TEXT,
+ tool_type VARCHAR(32) NOT NULL DEFAULT 'builtin',
+ bean_name VARCHAR(128),
+ icon VARCHAR(256),
+ mcp_endpoint VARCHAR(256),
+ params_schema TEXT,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ builtin TINYINT(1) NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 渠道表
+CREATE TABLE IF NOT EXISTS mate_channel (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ channel_type VARCHAR(32) NOT NULL,
+ agent_id BIGINT,
+ bot_prefix VARCHAR(64),
+ config_json TEXT,
+ enabled TINYINT(1) NOT NULL DEFAULT 0,
+ description VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 会话表
+CREATE TABLE IF NOT EXISTS mate_conversation (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(64) NOT NULL UNIQUE,
+ title VARCHAR(256),
+ agent_id BIGINT,
+ username VARCHAR(64),
+ message_count INT NOT NULL DEFAULT 0,
+ last_message TEXT,
+ last_active_time DATETIME,
+ stream_status VARCHAR(16) NOT NULL DEFAULT 'idle',
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_conversation_username (username)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 消息表
+CREATE TABLE IF NOT EXISTS mate_message (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(64) NOT NULL,
+ role VARCHAR(32) NOT NULL,
+ content TEXT,
+ content_parts TEXT,
+ tool_name VARCHAR(128),
+ token_usage INT,
+ prompt_tokens INT DEFAULT 0,
+ completion_tokens INT DEFAULT 0,
+ runtime_model VARCHAR(128),
+ runtime_provider VARCHAR(64),
+ status VARCHAR(32) NOT NULL DEFAULT 'completed',
+ metadata JSON COMMENT '存储 toolCalls, plan, currentPhase, pendingApproval 等元数据',
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_message_conversation (conversation_id),
+ INDEX idx_message_conv_time (conversation_id, create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 执行计划表
+CREATE TABLE IF NOT EXISTS mate_plan (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id VARCHAR(64),
+ goal TEXT,
+ status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ total_steps INT NOT NULL DEFAULT 0,
+ completed_steps INT NOT NULL DEFAULT 0,
+ summary TEXT,
+ start_time DATETIME,
+ end_time DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 子计划步骤表
+CREATE TABLE IF NOT EXISTS mate_sub_plan (
+ id BIGINT NOT NULL PRIMARY KEY,
+ plan_id BIGINT NOT NULL,
+ step_index INT NOT NULL,
+ description TEXT,
+ status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ result TEXT,
+ start_time DATETIME,
+ end_time DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_sub_plan_plan_id (plan_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 定时任务表
+CREATE TABLE IF NOT EXISTS mate_cron_job (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ cron_expression VARCHAR(128) NOT NULL,
+ timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai',
+ agent_id BIGINT NOT NULL,
+ task_type VARCHAR(16) NOT NULL DEFAULT 'text',
+ trigger_message TEXT,
+ request_body TEXT,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ next_run_time DATETIME,
+ last_run_time DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 渠道会话存储表
+CREATE TABLE IF NOT EXISTS mate_channel_session (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(128) NOT NULL UNIQUE,
+ channel_type VARCHAR(32) NOT NULL,
+ target_id VARCHAR(512) NOT NULL,
+ sender_id VARCHAR(128),
+ sender_name VARCHAR(128),
+ channel_id BIGINT,
+ last_active_time DATETIME NOT NULL,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_channel_session_type (channel_type),
+ INDEX idx_channel_session_channel_id (channel_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 工作区文件表(Agent 级 Markdown 文档管理)
+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 TINYINT(1) NOT NULL DEFAULT 0,
+ 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),
+ INDEX idx_workspace_file_agent_enabled (agent_id, enabled)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ==================== MCP Server 管理 ====================
+
+CREATE TABLE IF NOT EXISTS mate_mcp_server (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ transport VARCHAR(32) NOT NULL DEFAULT 'stdio',
+ url VARCHAR(512),
+ headers_json TEXT,
+ command VARCHAR(512),
+ args_json TEXT,
+ env_json TEXT,
+ cwd VARCHAR(512),
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ connect_timeout_seconds INT NOT NULL DEFAULT 30,
+ read_timeout_seconds INT NOT NULL DEFAULT 30,
+ last_status VARCHAR(32) NOT NULL DEFAULT 'disconnected',
+ last_error TEXT,
+ last_connected_time DATETIME,
+ tool_count INT NOT NULL DEFAULT 0,
+ builtin TINYINT(1) NOT NULL DEFAULT 0,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_mcp_server_enabled (enabled)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ==================== 工具安全治理(ToolGuard) ====================
+
+-- 工具审批表
+CREATE TABLE IF NOT EXISTS mate_tool_approval (
+ id BIGINT NOT NULL PRIMARY KEY,
+ pending_id VARCHAR(32) NOT NULL UNIQUE,
+ conversation_id VARCHAR(128) NOT NULL,
+ user_id VARCHAR(64),
+ agent_id VARCHAR(64),
+ channel_type VARCHAR(32),
+ requester_name VARCHAR(128),
+ reply_target VARCHAR(512),
+ tool_name VARCHAR(128) NOT NULL,
+ tool_arguments TEXT,
+ tool_call_payload TEXT,
+ tool_call_hash VARCHAR(64),
+ sibling_tool_calls TEXT,
+ summary TEXT,
+ findings_json TEXT,
+ max_severity VARCHAR(16),
+ status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
+ resolved_by VARCHAR(64),
+ created_at DATETIME NOT NULL,
+ resolved_at DATETIME,
+ expire_at DATETIME,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_tool_approval_conv (conversation_id),
+ INDEX idx_tool_approval_status (status),
+ INDEX idx_tool_approval_pending_id (pending_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 安全规则表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_rule (
+ id BIGINT NOT NULL PRIMARY KEY,
+ rule_id VARCHAR(64) NOT NULL UNIQUE,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ tool_name VARCHAR(128),
+ param_name VARCHAR(128),
+ category VARCHAR(64) NOT NULL,
+ severity VARCHAR(16) NOT NULL,
+ decision VARCHAR(16) NOT NULL DEFAULT 'NEEDS_APPROVAL',
+ pattern VARCHAR(512) NOT NULL,
+ exclude_pattern VARCHAR(512),
+ remediation TEXT,
+ builtin TINYINT(1) NOT NULL DEFAULT 0,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ priority INT NOT NULL DEFAULT 100,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 安全全局配置表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_config (
+ id BIGINT NOT NULL PRIMARY KEY,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ guard_scope VARCHAR(32) NOT NULL DEFAULT 'all',
+ guarded_tools_json TEXT,
+ denied_tools_json TEXT,
+ file_guard_enabled TINYINT(1) NOT NULL DEFAULT 1,
+ sensitive_paths_json TEXT,
+ audit_enabled TINYINT(1) NOT NULL DEFAULT 1,
+ audit_min_severity VARCHAR(16) NOT NULL DEFAULT 'INFO',
+ audit_retention_days INT NOT NULL DEFAULT 90,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 安全审计日志表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_audit_log (
+ id BIGINT NOT NULL PRIMARY KEY,
+ conversation_id VARCHAR(128),
+ agent_id VARCHAR(64),
+ user_id VARCHAR(64),
+ channel_type VARCHAR(32),
+ tool_name VARCHAR(128) NOT NULL,
+ tool_params_json TEXT,
+ decision VARCHAR(16) NOT NULL,
+ max_severity VARCHAR(16),
+ findings_json TEXT,
+ pending_id VARCHAR(32),
+ replay_payload_hash VARCHAR(64),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_guard_audit_conv (conversation_id),
+ INDEX idx_guard_audit_time (create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ==================== 外部数据源 ====================
+
+CREATE TABLE IF NOT EXISTS mate_datasource (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description VARCHAR(512),
+ db_type VARCHAR(32) NOT NULL,
+ host VARCHAR(256) NOT NULL,
+ port INT NOT NULL,
+ database_name VARCHAR(128) NOT NULL,
+ username VARCHAR(128),
+ password VARCHAR(512),
+ extra_params VARCHAR(512),
+ schema_name VARCHAR(128),
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ last_test_time DATETIME,
+ last_test_ok TINYINT(1),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ==================== 异步任务(视频/图片生成等长耗时操作) ====================
+
+CREATE TABLE IF NOT EXISTS mate_async_task (
+ id BIGINT NOT NULL PRIMARY KEY,
+ task_id VARCHAR(64) NOT NULL,
+ task_type VARCHAR(32) NOT NULL,
+ status VARCHAR(16) NOT NULL DEFAULT 'pending',
+ conversation_id VARCHAR(128),
+ message_id BIGINT,
+ provider_name VARCHAR(64),
+ provider_task_id VARCHAR(128),
+ request_json TEXT,
+ result_json TEXT,
+ error_message VARCHAR(512),
+ progress INT DEFAULT 0,
+ created_by VARCHAR(64),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ UNIQUE KEY uk_async_task_taskid (task_id),
+ INDEX idx_async_task_conv (conversation_id),
+ INDEX idx_async_task_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ==================== 记忆召回追踪 ====================
+
+CREATE TABLE IF NOT EXISTS mate_memory_recall (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ filename VARCHAR(256) NOT NULL,
+ snippet_hash VARCHAR(64),
+ snippet_preview VARCHAR(512),
+ recall_count INT NOT NULL DEFAULT 0,
+ daily_count INT NOT NULL DEFAULT 0,
+ query_hashes TEXT,
+ score DOUBLE NOT NULL DEFAULT 0.0,
+ last_recalled_at DATETIME,
+ promoted TINYINT(1) NOT NULL DEFAULT 0,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_memory_recall_agent (agent_id),
+ INDEX idx_memory_recall_agent_file (agent_id, filename),
+ INDEX idx_memory_recall_score (agent_id, score),
+ INDEX idx_memory_recall_candidates (agent_id, promoted, deleted)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- ==================== Wiki 知识库 ====================
+
+CREATE TABLE IF NOT EXISTS mate_wiki_knowledge_base (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ agent_id BIGINT,
+ config_content LONGTEXT,
+ source_directory VARCHAR(512),
+ status VARCHAR(32) NOT NULL DEFAULT 'active',
+ page_count INT NOT NULL DEFAULT 0,
+ raw_count INT NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_wiki_kb_agent (agent_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS mate_wiki_raw_material (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ title VARCHAR(256) NOT NULL,
+ source_type VARCHAR(32) NOT NULL DEFAULT 'text',
+ source_path VARCHAR(512),
+ original_content LONGTEXT,
+ extracted_text LONGTEXT,
+ content_hash VARCHAR(64),
+ file_size BIGINT NOT NULL DEFAULT 0,
+ processing_status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ last_processed_at DATETIME,
+ error_message VARCHAR(512),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ INDEX idx_wiki_raw_kb (kb_id),
+ INDEX idx_wiki_raw_status (kb_id, processing_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS mate_wiki_page (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ slug VARCHAR(256) NOT NULL,
+ title VARCHAR(256) NOT NULL,
+ content LONGTEXT,
+ summary VARCHAR(1024),
+ outgoing_links LONGTEXT,
+ source_raw_ids LONGTEXT,
+ version INT NOT NULL DEFAULT 1,
+ last_updated_by VARCHAR(32) NOT NULL DEFAULT 'ai',
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ UNIQUE KEY uk_wiki_page_kb_slug (kb_id, slug),
+ INDEX idx_wiki_page_kb (kb_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- =============================================
+-- 工作区表(Phase 2)
+-- =============================================
+
+-- 工作区
+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)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 工作区成员
+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)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- =============================================
+-- Agent-Skill / Agent-Tool 绑定表(Phase 3 Sprint 2)
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS mate_agent_skill (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ skill_id BIGINT NOT NULL,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ config_json TEXT,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ UNIQUE KEY uk_agent_skill (agent_id, skill_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+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,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ UNIQUE KEY uk_agent_tool (agent_id, tool_name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- =============================================
+-- CronJob 执行历史(Phase 3 Sprint 3)
+-- =============================================
+CREATE TABLE IF NOT EXISTS mate_cron_job_run (
+ id BIGINT NOT NULL PRIMARY KEY,
+ cron_job_id BIGINT NOT NULL,
+ conversation_id VARCHAR(64),
+ status VARCHAR(32) NOT NULL,
+ trigger_type VARCHAR(32) NOT NULL DEFAULT 'scheduled',
+ started_at DATETIME NOT NULL,
+ finished_at DATETIME,
+ error_message TEXT,
+ token_usage INT DEFAULT 0,
+ create_time DATETIME NOT NULL,
+ INDEX idx_cron_run_job (cron_job_id, started_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS mate_usage_daily (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT NOT NULL,
+ agent_id BIGINT,
+ stat_date DATE NOT NULL,
+ conversation_count INT DEFAULT 0,
+ message_count INT DEFAULT 0,
+ total_tokens BIGINT DEFAULT 0,
+ prompt_tokens BIGINT DEFAULT 0,
+ completion_tokens BIGINT DEFAULT 0,
+ tool_call_count INT DEFAULT 0,
+ error_count INT DEFAULT 0,
+ create_time DATETIME NOT NULL,
+ UNIQUE KEY uk_usage_daily (workspace_id, agent_id, stat_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- =============================================
+-- 操作审计事件表(Phase 3 Sprint 1)
+-- =============================================
+CREATE TABLE IF NOT EXISTS mate_audit_event (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT,
+ user_id BIGINT NOT NULL,
+ username VARCHAR(64) NOT NULL,
+ action VARCHAR(64) NOT NULL,
+ resource_type VARCHAR(64) NOT NULL,
+ resource_id VARCHAR(128),
+ resource_name VARCHAR(256),
+ detail_json TEXT,
+ ip_address VARCHAR(64),
+ user_agent VARCHAR(256),
+ create_time DATETIME NOT NULL,
+ INDEX idx_audit_ws_time (workspace_id, create_time),
+ INDEX idx_audit_user (user_id),
+ INDEX idx_audit_resource (resource_type, resource_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 清理 Codex 不支持的 ChatGPT OAuth 模型(gpt-4o, o3, o4-mini 在 Codex 模式下不可用)
+DELETE FROM mate_model_config WHERE provider = 'openai-chatgpt' AND model_name IN ('gpt-4o', 'o3', 'o4-mini');
diff --git a/mateclaw-server/src/main/resources/db/schema-mysql.sql b/mateclaw-server/src/main/resources/db/schema-mysql.sql
index ebf11388..c00f11d0 100644
--- a/mateclaw-server/src/main/resources/db/schema-mysql.sql
+++ b/mateclaw-server/src/main/resources/db/schema-mysql.sql
@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS mate_agent (
enabled TINYINT(1) NOT NULL DEFAULT 1,
icon VARCHAR(256),
tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -103,6 +104,7 @@ CREATE TABLE IF NOT EXISTS mate_skill (
enabled TINYINT(1) NOT NULL DEFAULT 1,
builtin TINYINT(1) NOT NULL DEFAULT 0,
tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -121,6 +123,7 @@ CREATE TABLE IF NOT EXISTS mate_tool (
params_schema TEXT,
enabled TINYINT(1) NOT NULL DEFAULT 1,
builtin TINYINT(1) NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -136,6 +139,7 @@ CREATE TABLE IF NOT EXISTS mate_channel (
config_json TEXT,
enabled TINYINT(1) NOT NULL DEFAULT 0,
description VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -152,6 +156,7 @@ CREATE TABLE IF NOT EXISTS mate_conversation (
last_message TEXT,
last_active_time DATETIME,
stream_status VARCHAR(16) NOT NULL DEFAULT 'idle',
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0,
@@ -176,7 +181,8 @@ CREATE TABLE IF NOT EXISTS mate_message (
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0,
- INDEX idx_message_conversation (conversation_id)
+ INDEX idx_message_conversation (conversation_id),
+ INDEX idx_message_conv_time (conversation_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 执行计划表
@@ -258,7 +264,8 @@ CREATE TABLE IF NOT EXISTS mate_workspace_file (
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0,
- INDEX idx_workspace_file_agent (agent_id)
+ INDEX idx_workspace_file_agent (agent_id),
+ INDEX idx_workspace_file_agent_enabled (agent_id, enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ==================== MCP Server 管理 ====================
@@ -460,6 +467,7 @@ CREATE TABLE IF NOT EXISTS mate_wiki_knowledge_base (
status VARCHAR(32) NOT NULL DEFAULT 'active',
page_count INT NOT NULL DEFAULT 0,
raw_count INT NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0,
@@ -516,6 +524,7 @@ CREATE TABLE IF NOT EXISTS mate_workspace (
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,
@@ -535,62 +544,32 @@ CREATE TABLE IF NOT EXISTS mate_workspace_member (
INDEX idx_ws_member_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--- 现有表增加 workspace_id 列(幂等)
--- MySQL 不支持 ADD COLUMN IF NOT EXISTS,使用存储过程处理
-DROP PROCEDURE IF EXISTS mate_add_workspace_id;
-DELIMITER $$
-CREATE PROCEDURE mate_add_workspace_id()
-BEGIN
- IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='mate_agent' AND COLUMN_NAME='workspace_id') THEN
- ALTER TABLE mate_agent ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='mate_channel' AND COLUMN_NAME='workspace_id') THEN
- ALTER TABLE mate_channel ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='mate_conversation' AND COLUMN_NAME='workspace_id') THEN
- ALTER TABLE mate_conversation ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='mate_wiki_knowledge_base' AND COLUMN_NAME='workspace_id') THEN
- ALTER TABLE mate_wiki_knowledge_base ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='mate_tool' AND COLUMN_NAME='workspace_id') THEN
- ALTER TABLE mate_tool ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='mate_skill' AND COLUMN_NAME='workspace_id') THEN
- ALTER TABLE mate_skill ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
- END IF;
-END$$
-DELIMITER ;
-
-CALL mate_add_workspace_id();
-DROP PROCEDURE IF EXISTS mate_add_workspace_id;
-
-- =============================================
-- Agent-Skill / Agent-Tool 绑定表(Phase 3 Sprint 2)
-- =============================================
CREATE TABLE IF NOT EXISTS mate_agent_skill (
- id BIGINT NOT NULL PRIMARY KEY,
- agent_id BIGINT NOT NULL,
- skill_id BIGINT NOT NULL,
- enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ skill_id BIGINT NOT NULL,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
config_json TEXT,
- create_time DATETIME NOT NULL,
- update_time DATETIME NOT NULL,
- deleted INT NOT NULL DEFAULT 0,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
UNIQUE KEY uk_agent_skill (agent_id, skill_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS mate_agent_tool (
- id BIGINT NOT NULL PRIMARY KEY,
- agent_id BIGINT NOT NULL,
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
tool_name VARCHAR(128) NOT NULL,
- enabled BOOLEAN NOT NULL DEFAULT TRUE,
- create_time DATETIME NOT NULL,
- update_time DATETIME NOT NULL,
- deleted INT NOT NULL DEFAULT 0,
+ enabled TINYINT(1) NOT NULL DEFAULT 1,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
UNIQUE KEY uk_agent_tool (agent_id, tool_name)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- =============================================
-- CronJob 执行历史(Phase 3 Sprint 3)
@@ -607,7 +586,7 @@ CREATE TABLE IF NOT EXISTS mate_cron_job_run (
token_usage INT DEFAULT 0,
create_time DATETIME NOT NULL,
INDEX idx_cron_run_job (cron_job_id, started_at)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS mate_usage_daily (
id BIGINT NOT NULL PRIMARY KEY,
@@ -623,7 +602,7 @@ CREATE TABLE IF NOT EXISTS mate_usage_daily (
error_count INT DEFAULT 0,
create_time DATETIME NOT NULL,
UNIQUE KEY uk_usage_daily (workspace_id, agent_id, stat_date)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- =============================================
-- 操作审计事件表(Phase 3 Sprint 1)
@@ -644,4 +623,7 @@ CREATE TABLE IF NOT EXISTS mate_audit_event (
INDEX idx_audit_ws_time (workspace_id, create_time),
INDEX idx_audit_user (user_id),
INDEX idx_audit_resource (resource_type, resource_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 清理 Codex 不支持的 ChatGPT OAuth 模型(gpt-4o, o3, o4-mini 在 Codex 模式下不可用)
+DELETE FROM mate_model_config WHERE provider = 'openai-chatgpt' AND model_name IN ('gpt-4o', 'o3', 'o4-mini');
diff --git a/mateclaw-server/src/main/resources/db/schema.sql b/mateclaw-server/src/main/resources/db/schema.sql
index e1821777..6d1cb0c8 100644
--- a/mateclaw-server/src/main/resources/db/schema.sql
+++ b/mateclaw-server/src/main/resources/db/schema.sql
@@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS mate_agent (
enabled BOOLEAN NOT NULL DEFAULT TRUE,
icon VARCHAR(256),
tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -46,6 +47,9 @@ CREATE TABLE IF NOT EXISTS mate_model_config (
builtin BOOLEAN NOT NULL DEFAULT TRUE,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
+ max_input_tokens INT DEFAULT 0,
+ enable_search BOOLEAN DEFAULT FALSE,
+ search_strategy VARCHAR(32) DEFAULT NULL,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -66,6 +70,11 @@ CREATE TABLE IF NOT EXISTS mate_model_provider (
support_connection_check BOOLEAN NOT NULL DEFAULT FALSE,
freeze_url BOOLEAN NOT NULL DEFAULT FALSE,
require_api_key BOOLEAN NOT NULL DEFAULT TRUE,
+ auth_type VARCHAR(16) NOT NULL DEFAULT 'api_key',
+ oauth_access_token TEXT,
+ oauth_refresh_token TEXT,
+ oauth_expires_at BIGINT,
+ oauth_account_id VARCHAR(128),
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
);
@@ -80,11 +89,6 @@ CREATE TABLE IF NOT EXISTS mate_system_setting (
update_time DATETIME NOT NULL
);
-ALTER TABLE mate_model_config ADD COLUMN IF NOT EXISTS builtin BOOLEAN NOT NULL DEFAULT TRUE;
-ALTER TABLE mate_model_config ADD COLUMN IF NOT EXISTS max_input_tokens INT DEFAULT 0;
-ALTER TABLE mate_model_config ADD COLUMN IF NOT EXISTS enable_search BOOLEAN DEFAULT FALSE;
-ALTER TABLE mate_model_config ADD COLUMN IF NOT EXISTS search_strategy VARCHAR(32) DEFAULT NULL;
-
-- 技能表
CREATE TABLE IF NOT EXISTS mate_skill (
id BIGINT NOT NULL PRIMARY KEY,
@@ -100,6 +104,7 @@ CREATE TABLE IF NOT EXISTS mate_skill (
enabled BOOLEAN NOT NULL DEFAULT TRUE,
builtin BOOLEAN NOT NULL DEFAULT FALSE,
tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -118,6 +123,7 @@ CREATE TABLE IF NOT EXISTS mate_tool (
params_schema TEXT,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
builtin BOOLEAN NOT NULL DEFAULT FALSE,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -133,6 +139,7 @@ CREATE TABLE IF NOT EXISTS mate_channel (
config_json TEXT,
enabled BOOLEAN NOT NULL DEFAULT FALSE,
description VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -149,6 +156,7 @@ CREATE TABLE IF NOT EXISTS mate_conversation (
last_message TEXT,
last_active_time DATETIME,
stream_status VARCHAR(16) NOT NULL DEFAULT 'idle',
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -404,9 +412,6 @@ CREATE TABLE IF NOT EXISTS mate_datasource (
deleted INT NOT NULL DEFAULT 0
);
--- 为现有表添加 metadata 列(向后兼容,防止迁移时数据丢失)
-ALTER TABLE mate_message ADD COLUMN IF NOT EXISTS metadata JSON;
-
CREATE INDEX IF NOT EXISTS idx_guard_audit_conv ON mate_tool_guard_audit_log(conversation_id);
CREATE INDEX IF NOT EXISTS idx_guard_audit_time ON mate_tool_guard_audit_log(create_time);
@@ -437,13 +442,6 @@ CREATE INDEX IF NOT EXISTS idx_memory_recall_candidates ON mate_memory_recall(ag
CREATE INDEX IF NOT EXISTS idx_message_conv_time ON mate_message(conversation_id, create_time);
CREATE INDEX IF NOT EXISTS idx_workspace_file_agent_enabled ON mate_workspace_file(agent_id, enabled);
--- ==================== OAuth 支持 ====================
-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);
-
-- 清理 Codex 不支持的 ChatGPT OAuth 模型(gpt-4o, o3, o4-mini 在 Codex 模式下不可用)
DELETE FROM mate_model_config WHERE provider = 'openai-chatgpt' AND model_name IN ('gpt-4o', 'o3', 'o4-mini');
@@ -467,7 +465,6 @@ CREATE TABLE IF NOT EXISTS mate_async_task (
update_time DATETIME NOT NULL
);
-CREATE INDEX IF NOT EXISTS idx_async_task_taskid ON mate_async_task(task_id);
CREATE INDEX IF NOT EXISTS idx_async_task_conv ON mate_async_task(conversation_id);
CREATE INDEX IF NOT EXISTS idx_async_task_status ON mate_async_task(status);
@@ -483,6 +480,7 @@ CREATE TABLE IF NOT EXISTS mate_wiki_knowledge_base (
status VARCHAR(32) NOT NULL DEFAULT 'active',
page_count INT NOT NULL DEFAULT 0,
raw_count INT NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
deleted INT NOT NULL DEFAULT 0
@@ -539,6 +537,7 @@ CREATE TABLE IF NOT EXISTS mate_workspace (
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,
@@ -558,14 +557,6 @@ CREATE TABLE IF NOT EXISTS mate_workspace_member (
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);
--- 现有表增加 workspace_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;
-
-- =============================================
-- Agent-Skill / Agent-Tool 绑定表(Phase 3 Sprint 2)
-- =============================================
diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts
index 298123d6..877779c6 100644
--- a/mateclaw-ui/src/i18n/locales/en-US.ts
+++ b/mateclaw-ui/src/i18n/locales/en-US.ts
@@ -752,6 +752,7 @@ export default {
name: 'Name',
slug: 'Slug',
description: 'Description',
+ basePath: 'Working Directory',
created: 'Created',
actions: 'Actions',
},
@@ -764,6 +765,9 @@ export default {
slugHint: 'Slug cannot be changed after creation.',
description: 'Description',
descriptionPlaceholder: 'Optional description',
+ basePath: 'Working Directory',
+ basePathPlaceholder: 'e.g. /home/user/project or D:\\codes\\MyProject',
+ basePathHint: 'Restrict agent file access to this directory. Leave empty for no restriction.',
},
editDialog: {
title: 'Edit Workspace',
diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts
index 351bb46e..d49af249 100644
--- a/mateclaw-ui/src/i18n/locales/zh-CN.ts
+++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts
@@ -752,6 +752,7 @@ export default {
name: '名称',
slug: '标识',
description: '描述',
+ basePath: '活动目录',
created: '创建时间',
actions: '操作',
},
@@ -764,6 +765,9 @@ export default {
slugHint: '标识创建后不可修改。',
description: '描述',
descriptionPlaceholder: '可选描述',
+ basePath: '活动目录',
+ basePathPlaceholder: '例如:/home/user/project 或 D:\\codes\\MyProject',
+ basePathHint: '限制 Agent 文件操作的根目录,留空不限制。',
},
editDialog: {
title: '编辑工作区',
diff --git a/mateclaw-ui/src/stores/useWorkspaceStore.ts b/mateclaw-ui/src/stores/useWorkspaceStore.ts
index 33413f81..7f7f53ed 100644
--- a/mateclaw-ui/src/stores/useWorkspaceStore.ts
+++ b/mateclaw-ui/src/stores/useWorkspaceStore.ts
@@ -7,6 +7,7 @@ export interface Workspace {
name: string
slug: string
description?: string
+ basePath?: string
ownerId?: number
settingsJson?: string
createTime?: string
diff --git a/mateclaw-ui/src/views/Security/Workspaces/index.vue b/mateclaw-ui/src/views/Security/Workspaces/index.vue
index f1c2f2b5..268ee50d 100644
--- a/mateclaw-ui/src/views/Security/Workspaces/index.vue
+++ b/mateclaw-ui/src/views/Security/Workspaces/index.vue
@@ -23,6 +23,7 @@
| {{ t('security.workspaces.columns.name') }} |
{{ t('security.workspaces.columns.slug') }} |
{{ t('security.workspaces.columns.description') }} |
+ {{ t('security.workspaces.columns.basePath') }} |
{{ t('security.workspaces.columns.created') }} |
{{ t('security.workspaces.columns.actions') }} |
@@ -37,6 +38,7 @@
{{ ws.slug }} |
{{ ws.description || '-' }} |
+ {{ ws.basePath || '-' }} |
{{ formatDate(ws.createTime) }} |
@@ -93,6 +95,11 @@
+
+
+
+ {{ t('security.workspaces.createDialog.basePathHint') }}
+
|