diff --git a/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java b/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java
index 3e33d262..dba85b66 100644
--- a/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java
+++ b/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java
@@ -1,19 +1,29 @@
package vip.mate;
+import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.EnableScheduling;
+import javax.sql.DataSource;
+import java.sql.Connection;
+
/**
* MateClaw - Personal AI Assistant
* Powered by Spring AI Alibaba
*
* @author MateClaw Team
*/
+@Slf4j
@SpringBootApplication(exclude = {
// Disable Spring AI MCP Client auto-configuration (lifecycle owned by McpClientManager).
org.springframework.ai.mcp.client.common.autoconfigure.McpClientAutoConfiguration.class,
@@ -33,22 +43,80 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@MapperScan("vip.mate.**.repository")
public class MateClawApplication {
+ @Autowired
+ private DataSource dataSource;
+
+ /** Cached DbType for the PaginationInnerInterceptor. */
+ private volatile DbType resolvedDbType;
+
public static void main(String[] args) {
SpringApplication.run(MateClawApplication.class, args);
}
+ /**
+ * Detect the actual database type from the live DataSource so the
+ * {@link PaginationInnerInterceptor} always uses the correct dialect,
+ * even when the JDBC URL is wrapped by a proxy (HikariCP, P6Spy, etc.).
+ *
+ * DbType is cached after the first successful detection; a failure
+ * falls back to the value set in {@code mybatis-plus.global-config.db-config.db-type},
+ * or eventually to {@link DbType#MYSQL} — but by then the connection
+ * pool would already have failed.
+ */
+ @PostConstruct
+ void detectDbType() {
+ try (Connection conn = dataSource.getConnection()) {
+ String productName = conn.getMetaData().getDatabaseProductName().toLowerCase();
+ if (productName.contains("kingbase")) {
+ resolvedDbType = DbType.KINGBASE_ES;
+ } else if (productName.contains("postgresql")) {
+ resolvedDbType = DbType.POSTGRE_SQL;
+ } else if (productName.contains("mysql") || productName.contains("mariadb")) {
+ resolvedDbType = DbType.MYSQL;
+ } else if (productName.contains("h2")) {
+ resolvedDbType = DbType.H2;
+ } else {
+ // Let the PaginationInnerInterceptor auto-detect at query time
+ resolvedDbType = null;
+ }
+ if (resolvedDbType != null) {
+ log.info("Detected database type: {} (product={})", resolvedDbType, productName);
+ }
+ } catch (Exception e) {
+ log.warn("Could not detect database type — PaginationInnerInterceptor will auto-detect on first query: {}",
+ e.getMessage());
+ }
+ }
+
/**
* MyBatis Plus pagination plugin.
*
- *
DbType is auto-detected from the JDBC connection at runtime rather
- * than hardcoded. Hardcoding H2 here meant the MySQL deployment used
- * the H2 dialect for the count query, which silently returned 0 —
- * frontends saw records but total=0 and couldn't paginate (RFC-042 P0).
+ *
When {@code resolvedDbType} is available the interceptor uses it directly;
+ * otherwise it falls back to JDBC-URL auto-detection, which works for
+ * {@code jdbc:kingbase8://} but not for proxied DataSources (RFC-042 P0).
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+ PaginationInnerInterceptor pagination = resolvedDbType != null
+ ? new PaginationInnerInterceptor(resolvedDbType)
+ : new PaginationInnerInterceptor();
+ interceptor.addInnerInterceptor(pagination);
return interceptor;
}
+
+ /**
+ * Print a clear "READY" banner after all post-startup initialization,
+ * so operators can tell at a glance when the application is ready to serve.
+ */
+ @EventListener(ApplicationReadyEvent.class)
+ public void onReady() {
+ log.info("");
+ log.info("╔══════════════════════════════════════════════════════════════════════╗");
+ log.info("║ MateClaw is READY ✓ ║");
+ log.info("║ Web UI → http://localhost:18088 ║");
+ log.info("║ Swagger → http://localhost:18088/swagger-ui.html ║");
+ log.info("╚══════════════════════════════════════════════════════════════════════╝");
+ log.info("");
+ }
}
diff --git a/mateclaw-server/src/main/java/vip/mate/channel/ChannelManager.java b/mateclaw-server/src/main/java/vip/mate/channel/ChannelManager.java
index d010c74d..30489180 100644
--- a/mateclaw-server/src/main/java/vip/mate/channel/ChannelManager.java
+++ b/mateclaw-server/src/main/java/vip/mate/channel/ChannelManager.java
@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import vip.mate.channel.dingtalk.DingTalkChannelAdapter;
import vip.mate.channel.discord.DiscordChannelAdapter;
@@ -230,9 +231,12 @@ public class ChannelManager {
);
/**
- * 应用启动完成后自动加载并启动所有已启用的渠道
- * 使用 ApplicationReadyEvent 确保数据库 schema/data 初始化完成
+ * 应用启动完成后自动加载并启动所有已启用的渠道。
+ * 使用 ApplicationReadyEvent 确保数据库 schema/data 初始化完成。
+ * {@code @Async} — 渠道适配器的网络建连(如 Discord WebSocket / Telegram webhook)
+ * 可能因外部网络不可达而阻塞数分钟,异步启动避免卡住主线程。
*/
+ @Async
@EventListener(ApplicationReadyEvent.class)
public void init() {
log.info("Initializing ChannelManager...");
diff --git a/mateclaw-server/src/main/java/vip/mate/config/AsyncSecurityConfig.java b/mateclaw-server/src/main/java/vip/mate/config/AsyncSecurityConfig.java
index ab967c1e..11d28663 100644
--- a/mateclaw-server/src/main/java/vip/mate/config/AsyncSecurityConfig.java
+++ b/mateclaw-server/src/main/java/vip/mate/config/AsyncSecurityConfig.java
@@ -14,6 +14,12 @@ import java.util.concurrent.Executor;
* The inner delegate uses virtual threads (JDK 21); the outer
* {@link DelegatingSecurityContextTaskExecutor} wrapper propagates the caller's
* SecurityContext (JWT identity, audit permissions) to every @Async invocation.
+ *
+ * A concurrency limit is set to prevent runaway virtual-thread creation
+ * from exhausting the HikariCP pool (typical pattern: a stampede of
+ * {@code @Async} tasks all trying to acquire DB connections at the same
+ * minute boundary). Excess tasks are rejected immediately so the
+ * scheduler threads never block on submission.
*
* @author MateClaw Team
*/
@@ -21,6 +27,13 @@ import java.util.concurrent.Executor;
@EnableAsync
public class AsyncSecurityConfig implements AsyncConfigurer {
+ /**
+ * Cap in-flight async tasks. Well below HikariCP maximum-pool-size (30)
+ * so async tasks never saturate the pool on their own — non-async paths
+ * (HTTP requests, SSE, channel adapters) always have headroom.
+ */
+ private static final int ASYNC_CONCURRENCY_LIMIT = 24;
+
@Override
public Executor getAsyncExecutor() {
// Keep DelegatingSecurityContextTaskExecutor so SecurityContext
@@ -28,6 +41,7 @@ public class AsyncSecurityConfig implements AsyncConfigurer {
// Replace the inner platform-thread pool with a virtual-thread executor.
var delegate = new SimpleAsyncTaskExecutorBuilder()
.virtualThreads(true)
+ .concurrencyLimit(ASYNC_CONCURRENCY_LIMIT)
.threadNamePrefix("async-vt-")
.build();
return new DelegatingSecurityContextTaskExecutor(delegate);
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 cc74d300..d9bf0ff9 100644
--- a/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java
+++ b/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java
@@ -38,9 +38,12 @@ public class DatabaseBootstrapRunner implements ApplicationRunner {
private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;
- /** Cached flag: true when running on MySQL/MariaDB, false for H2. */
+ /** Cached flag: true when running on MySQL/MariaDB, false for H2/Kingbase. */
private volatile Boolean isMySQL;
+ /** Cached flag: true when running on KingbaseES. */
+ private volatile Boolean isKingbase;
+
/**
* When true, wait for Desktop splash screen to call /setup/init with chosen language.
* When false (default), auto-initialize immediately on startup.
@@ -111,6 +114,8 @@ public class DatabaseBootstrapRunner implements ApplicationRunner {
String scriptName;
if (isMySQL()) {
scriptName = "en-US".equals(locale) ? "db/data-mysql-en.sql" : "db/data-mysql-zh.sql";
+ } else if (isKingbase()) {
+ scriptName = "en-US".equals(locale) ? "db/data-kingbase-en.sql" : "db/data-kingbase-zh.sql";
} else {
scriptName = "en-US".equals(locale) ? "db/data-en.sql" : "db/data-zh.sql";
}
@@ -159,15 +164,29 @@ public class DatabaseBootstrapRunner implements ApplicationRunner {
try (Connection connection = dataSource.getConnection()) {
String dbProduct = connection.getMetaData().getDatabaseProductName().toLowerCase();
isMySQL = dbProduct.contains("mysql") || dbProduct.contains("mariadb");
- log.info("Detected database: {} (MySQL mode: {})", dbProduct, isMySQL);
+ isKingbase = dbProduct.contains("kingbase");
+ if (isKingbase) {
+ log.info("Detected database: {} (KingbaseES mode)", dbProduct);
+ } else {
+ log.info("Detected database: {} (MySQL mode: {})", dbProduct, isMySQL);
+ }
} catch (Exception e) {
log.warn("Failed to detect database type, falling back to H2 mode", e);
isMySQL = false;
+ isKingbase = false;
}
}
return isMySQL;
}
+ private boolean isKingbase() {
+ if (isKingbase == null) {
+ // Trigger detection
+ isMySQL();
+ }
+ return isKingbase != null && isKingbase;
+ }
+
private void runScript(String path) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(false);
diff --git a/mateclaw-server/src/main/java/vip/mate/config/HikariPoolMonitor.java b/mateclaw-server/src/main/java/vip/mate/config/HikariPoolMonitor.java
new file mode 100644
index 00000000..80bddb13
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/config/HikariPoolMonitor.java
@@ -0,0 +1,68 @@
+package vip.mate.config;
+
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+
+/**
+ * Periodically logs HikariCP pool metrics so operators can detect
+ * connection exhaustion / leak patterns before the application stalls.
+ *
+ * Runs every 30s — frequent enough to catch a pool drain within 1-2
+ * ticks, cheap enough to never become a problem itself.
+ */
+@Slf4j
+@Component
+public class HikariPoolMonitor {
+
+ private final HikariDataSource hikari;
+
+ public HikariPoolMonitor(DataSource dataSource) {
+ if (dataSource instanceof HikariDataSource hds) {
+ this.hikari = hds;
+ } else if (dataSource instanceof org.springframework.jdbc.datasource.DelegatingDataSource dds
+ && dds.getTargetDataSource() instanceof HikariDataSource hds) {
+ // Some auto-configurations wrap Hikari in a delegating DS.
+ this.hikari = hds;
+ } else {
+ this.hikari = null;
+ log.info("[HikariMonitor] DataSource is not HikariCP — pool monitor disabled");
+ }
+ }
+
+ @Scheduled(fixedDelay = 30_000, initialDelay = 60_000)
+ public void logPoolStats() {
+ if (hikari == null) return;
+
+ HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
+ if (pool == null) return;
+
+ int active = pool.getActiveConnections();
+ int idle = pool.getIdleConnections();
+ int total = pool.getTotalConnections();
+ int waiting = pool.getThreadsAwaitingConnection();
+
+ // Normal — log at debug so it doesn't spam the console.
+ log.debug("[HikariMonitor] pool: active={}, idle={}, total={}, max={}, waiting={}",
+ active, idle, total, hikari.getMaximumPoolSize(), waiting);
+
+ // Warning threshold: more than 80 % of the pool is active AND
+ // threads are queued waiting for a connection.
+ int maxPool = hikari.getMaximumPoolSize();
+ if (active > maxPool * 0.8 && waiting > 0) {
+ log.warn("[HikariMonitor] Pool pressure detected — active={}, idle={}, "
+ + "total={}, max={}, waiting={}", active, idle, total, maxPool, waiting);
+ }
+
+ // Critical: pool is fully saturated AND threads are waiting.
+ if (active >= maxPool && waiting > 0) {
+ log.error("[HikariMonitor] Pool EXHAUSTED — active={}, max={}, waiting={}. "
+ + "Application will appear frozen until connections are released.",
+ active, maxPool, waiting);
+ }
+ }
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/config/SchedulingConfig.java b/mateclaw-server/src/main/java/vip/mate/config/SchedulingConfig.java
new file mode 100644
index 00000000..ab832b1e
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/config/SchedulingConfig.java
@@ -0,0 +1,56 @@
+package vip.mate.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+/**
+ * Scheduled-task thread-pool configuration.
+ *
+ * Spring Boot's auto-configured {@code TaskScheduler} defaults to
+ * pool-size = 1, which serializes every {@code @Scheduled}
+ * method across the entire application. With 15+ scheduled beans
+ * (health checks, trigger sync, fact rebuild, feature-flag refresh,
+ * etc.) and several firing on the same cron tick, a single-thread
+ * pool causes back-pressure that makes the application appear "frozen"
+ * when any task blocks briefly on database I/O or lock acquisition.
+ *
+ * This config sets a pool large enough to absorb simultaneous
+ * minute/half-hour boundaries without head-of-line blocking, while
+ * keeping thread count low so the scheduler does not contend with
+ * HikariCP or the async virtual-thread pool.
+ *
+ * IMPORTANT: {@code @EnableScheduling} is declared once on
+ * {@link vip.mate.MateClawApplication}. Declaring it here as well
+ * creates a second {@code ScheduledAnnotationBeanPostProcessor}
+ * that competes with the first, resulting in some tasks unknowingly
+ * scheduled on the default single-thread executor.
+ */
+@Configuration
+public class SchedulingConfig implements SchedulingConfigurer {
+
+ /** Pool threads — enough for concurrent ticks but kept moderate. */
+ private static final int POOL_SIZE = 4;
+
+ @Bean
+ public TaskScheduler taskScheduler() {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setPoolSize(POOL_SIZE);
+ scheduler.setThreadNamePrefix("sched-");
+ scheduler.setRemoveOnCancelPolicy(true);
+ scheduler.setAwaitTerminationSeconds(30);
+ scheduler.setWaitForTasksToCompleteOnShutdown(true);
+ return scheduler;
+ }
+
+ @Override
+ public void configureTasks(ScheduledTaskRegistrar registrar) {
+ // Re-use the singleton TaskScheduler bean so we never create two
+ // separate thread-pool instances (the @Bean above is the single
+ // source of truth).
+ registrar.setTaskScheduler(taskScheduler());
+ }
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/cron/config/ShedLockConfig.java b/mateclaw-server/src/main/java/vip/mate/cron/config/ShedLockConfig.java
index abb10c24..57e01e1c 100644
--- a/mateclaw-server/src/main/java/vip/mate/cron/config/ShedLockConfig.java
+++ b/mateclaw-server/src/main/java/vip/mate/cron/config/ShedLockConfig.java
@@ -42,7 +42,9 @@ public class ShedLockConfig {
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.withTableName("shedlock")
- .usingDbTime() // server-side NOW() — avoids node clock drift
+ // usingDbTime() removed — KingbaseES not in ShedLock's
+ // built-in dialect map; app-server time is sufficient
+ // given lockAtMostFor=PT30M
.build()
);
}
diff --git a/mateclaw-server/src/main/java/vip/mate/datasource/service/DatasourceConnectionManager.java b/mateclaw-server/src/main/java/vip/mate/datasource/service/DatasourceConnectionManager.java
index 19b5fc70..a4423391 100644
--- a/mateclaw-server/src/main/java/vip/mate/datasource/service/DatasourceConnectionManager.java
+++ b/mateclaw-server/src/main/java/vip/mate/datasource/service/DatasourceConnectionManager.java
@@ -118,6 +118,14 @@ public class DatasourceConnectionManager implements DisposableBean {
extra = (extra == null || extra.isBlank()) ? schemaParam : extra + "&" + schemaParam;
}
break;
+ case "kingbase":
+ case "kingbasees":
+ baseUrl = String.format("jdbc:kingbase8://%s:%d/%s", host, port, dbName);
+ if (entity.getSchemaName() != null && !entity.getSchemaName().isBlank()) {
+ String schemaParam = "currentSchema=" + entity.getSchemaName();
+ extra = (extra == null || extra.isBlank()) ? schemaParam : extra + "&" + schemaParam;
+ }
+ break;
case "clickhouse":
baseUrl = String.format("jdbc:clickhouse://%s:%d/%s", host, port, dbName);
break;
diff --git a/mateclaw-server/src/main/java/vip/mate/llm/failover/ProviderInitProbe.java b/mateclaw-server/src/main/java/vip/mate/llm/failover/ProviderInitProbe.java
index 845e934c..51ccb68b 100644
--- a/mateclaw-server/src/main/java/vip/mate/llm/failover/ProviderInitProbe.java
+++ b/mateclaw-server/src/main/java/vip/mate/llm/failover/ProviderInitProbe.java
@@ -79,6 +79,7 @@ public class ProviderInitProbe {
this.strategies = map;
}
+ @Async
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
probeAllConfigured();
diff --git a/mateclaw-server/src/main/java/vip/mate/memory/fact/projection/FactProjectionScheduler.java b/mateclaw-server/src/main/java/vip/mate/memory/fact/projection/FactProjectionScheduler.java
index 5e0e533e..c0df78c6 100644
--- a/mateclaw-server/src/main/java/vip/mate/memory/fact/projection/FactProjectionScheduler.java
+++ b/mateclaw-server/src/main/java/vip/mate/memory/fact/projection/FactProjectionScheduler.java
@@ -2,6 +2,7 @@ package vip.mate.memory.fact.projection;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import vip.mate.agent.AgentService;
@@ -14,6 +15,9 @@ import java.util.List;
* Scheduled full rebuild of the fact projection for all active agents.
* Cron expression configured via mate.memory.fact.projection-rebuild-cron.
* Only runs when projection-enabled=true.
+ *
+ * {@code @Async} keeps the scheduler-thread pool free — the actual DB
+ * work runs on the virtual-thread async executor.
*
* @author MateClaw Team
*/
@@ -26,6 +30,7 @@ public class FactProjectionScheduler {
private final FactProjectionBuilder projectionBuilder;
private final MemoryProperties properties;
+ @Async
@Scheduled(cron = "${mate.memory.fact.projection-rebuild-cron:0 */30 * * * ?}")
public void rebuildAll() {
if (!properties.getFact().isProjectionEnabled()) {
diff --git a/mateclaw-server/src/main/java/vip/mate/plugin/PluginManager.java b/mateclaw-server/src/main/java/vip/mate/plugin/PluginManager.java
index 5d3322eb..0976c26f 100644
--- a/mateclaw-server/src/main/java/vip/mate/plugin/PluginManager.java
+++ b/mateclaw-server/src/main/java/vip/mate/plugin/PluginManager.java
@@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import vip.mate.channel.ChannelManager;
import vip.mate.llm.service.ModelProviderService;
@@ -65,7 +66,9 @@ public class PluginManager {
* Load all plugins on application startup.
* Scans three paths in priority order: workspace > user-global.
* Higher priority plugins shadow lower priority ones with the same name.
+ * {@code @Async} — 文件系统扫描和 JAR 类加载不阻塞主启动线程。
*/
+ @Async
@EventListener(ApplicationReadyEvent.class)
@Order(250)
public void loadAllPlugins() {
diff --git a/mateclaw-server/src/main/resources/application-kingbase.yml b/mateclaw-server/src/main/resources/application-kingbase.yml
new file mode 100644
index 00000000..85192afe
--- /dev/null
+++ b/mateclaw-server/src/main/resources/application-kingbase.yml
@@ -0,0 +1,71 @@
+spring:
+ datasource:
+ # 人大金仓 KingbaseES 数据源配置
+ # 默认连接参数:
+ # DB_HOST=localhost, DB_PORT=54321, DB_NAME=mateclaw
+ # KingbaseES 基于 PostgreSQL,兼容 PostgreSQL JDBC 协议
+ # 通过环境变量覆盖:DB_HOST, DB_PORT, DB_NAME, DB_USERNAME, DB_PASSWORD
+ #
+ # JDBC 超时参数说明:
+ # connectTimeout=10 — TCP 连接超时(秒),避免 OS 级超时(60-180s)
+ # socketTimeout=30 — socket 读取超时(秒),防止僵死连接永久阻塞
+ # loginTimeout=10 — 数据库登录超时(秒)
+ url: jdbc:kingbase8://${DB_HOST:localhost}:${DB_PORT:54321}/${DB_NAME:mateclaw}?currentSchema=mateclaw&connectTimeout=10&socketTimeout=30&loginTimeout=10
+ driver-class-name: com.kingbase8.Driver
+ username: ${DB_USERNAME:system}
+ password: ${DB_PASSWORD:Admin2026@123}
+ hikari:
+ maximum-pool-size: 30
+ minimum-idle: 5
+ connection-timeout: 30000
+ idle-timeout: 300000
+ # 原 1800000(30min) 与巡检中观察到的 30min 卡住周期吻合 — 若存在连接泄漏,
+ # 30min 后 HikariCP 强制回收旧连接才恢复。降至 10min 加快故障自愈速度。
+ max-lifetime: 600000
+ leak-detection-threshold: 30000
+ # 连接池初始化超时:若在此时长内无法获取首个有效连接,快速失败而非无限等待
+ initialization-fail-timeout: 30000
+ # Kingbase ES:每个新连接初始化时强制设置 search_path,
+ # 作为 currentSchema 参数的双重保障,防止连接落入 public schema
+ connection-init-sql: SET search_path TO mateclaw
+
+ flyway:
+ # Flyway uses PostgreSQL JDBC driver because KingbaseES is PostgreSQL-compatible
+ # on the wire protocol level. The application runtime (MyBatis) still uses the
+ # Kingbase8 driver for production queries.
+ #
+ # JDBC 超时参数:避免 Flyway 连接阶段也因 OS 级 TCP 超时而"卡住"
+ url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:54321}/${DB_NAME:mateclaw}?currentSchema=mateclaw&connectTimeout=10&socketTimeout=30&loginTimeout=10
+ user: ${DB_USERNAME:system}
+ password: ${DB_PASSWORD:Admin2026@123}
+ locations:
+ - classpath:db/migration/kingbase
+ # 迁移前确保目标 schema 存在(Kingbase 不会自动创建非 public schema)
+ init-sqls:
+ - CREATE SCHEMA IF NOT EXISTS mateclaw
+ # 首次迁移或脚本变更后跳过 checksum 校验,避免因脚本转换导致的校验失败卡住
+ validate-on-migrate: false
+
+ h2:
+ console:
+ enabled: false
+
+# MyBatis Plus — Kingbase ES 显式数据库类型
+# PaginationInnerInterceptor 使用无参构造时依赖 JDBC URL 自动检测,
+# 在 DataSource 代理/包装层下可能检测失败回退到 MYSQL 方言,
+# 导致分页 SQL 生成 LIMIT offset,count 而非 LIMIT count OFFSET offset。
+# 显式指定 kingbase_es 确保所有分页/ID生成/批量操作使用正确的方言。
+mybatis-plus:
+ global-config:
+ db-config:
+ db-type: kingbase_es
+
+# Production (multi-tenant server) hardening: fail closed on source-path
+# validation. With no allowed-source-roots configured, every KB source
+# directory is rejected rather than allowing full-filesystem reads — so a
+# missing allow-list cannot silently re-open arbitrary directory scanning.
+# Operators set mate.wiki.allowed-source-roots to permit specific roots.
+# The default profile (H2 / desktop / single-tenant) leaves this off.
+mate:
+ wiki:
+ require-allowed-roots: true
diff --git a/mateclaw-server/src/main/resources/application-mysql.yml b/mateclaw-server/src/main/resources/application-mysql.yml
index 134f5333..945e7c81 100644
--- a/mateclaw-server/src/main/resources/application-mysql.yml
+++ b/mateclaw-server/src/main/resources/application-mysql.yml
@@ -13,7 +13,7 @@ spring:
# treat the connection as utf8mb4, force the connection collation via
# connectionCollation=utf8mb4_unicode_ci — that is what prevents the
# `Data truncation: Incorrect string value` errors on emoji/CJK ext.
- url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:mateclaw}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:mateclaw}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=10000&socketTimeout=30000
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:mateclaw123}
diff --git a/mateclaw-server/src/main/resources/application.yml b/mateclaw-server/src/main/resources/application.yml
index 0abb58b1..2a42915c 100644
--- a/mateclaw-server/src/main/resources/application.yml
+++ b/mateclaw-server/src/main/resources/application.yml
@@ -19,7 +19,7 @@ spring:
profiles:
active: dev
- # 数据源(默认 H2,生产切换为 mysql profile)
+ # 数据源(默认 H2,生产切换为 mysql 或 kingbase profile)
datasource:
url: jdbc:h2:file:./data/mateclaw;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE
driver-class-name: org.h2.Driver
@@ -131,6 +131,8 @@ mateclaw:
# Set this when agents deliver download links to channels/clients that cannot
# resolve a relative URL (IM messages, copied links, external downloads).
public-base-url: ${MATECLAW_PUBLIC_BASE_URL:}
+ browser:
+ ssrf-check-enabled: false
jwt:
secret: ${JWT_SECRET:MateClaw-JWT-Secret-Key-2024-Please-Change-In-Production}
expiration: 86400000
diff --git a/mateclaw-server/src/main/resources/db/data-kingbase-en.sql b/mateclaw-server/src/main/resources/db/data-kingbase-en.sql
new file mode 100644
index 00000000..c393bb0a
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/data-kingbase-en.sql
@@ -0,0 +1,1916 @@
+-- MateClaw Seed Data - English (MySQL/MariaDB compatible, ON DUPLICATE KEY UPDATE)
+
+-- Default admin (password: admin123, BCrypt encrypted)
+INSERT INTO mate_user (id, username, password, nickname, role, enabled, create_time, update_time, deleted)
+VALUES (1, 'admin', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 'MateClaw Admin', 'admin', TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET username=EXCLUDED.username, password=EXCLUDED.password, nickname=EXCLUDED.nickname, role=EXCLUDED.role, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Default digital employee: General Assistant (ReAct mode)
+INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
+VALUES (1000000001, 'General Assistant', 'All-purpose helper for day-to-day questions, data analysis, and tool calling', 'react',
+ 'You are MateClaw''s General Assistant. You can help users answer questions, analyze data, and call tools to get things done. Please respond professionally and in a friendly manner.',
+ NULL, 100, TRUE, 'pi:robot-face-happy', 'default,assistant', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, agent_type=EXCLUDED.agent_type, system_prompt=EXCLUDED.system_prompt, model_name=EXCLUDED.model_name, max_iterations=EXCLUDED.max_iterations, enabled=EXCLUDED.enabled, icon=EXCLUDED.icon, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Default digital employee: Task Planner (Plan-Execute mode)
+INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
+VALUES (1000000002, 'Task Planner', 'Breaks complex goals into executable steps and drives them forward to completion', 'plan_execute',
+ 'You are a professional Task Planner. You excel at breaking complex goals into executable steps and completing them systematically.',
+ NULL, 100, TRUE, 'pi:clipboard-note', 'planning,task', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, agent_type=EXCLUDED.agent_type, system_prompt=EXCLUDED.system_prompt, model_name=EXCLUDED.model_name, max_iterations=EXCLUDED.max_iterations, enabled=EXCLUDED.enabled, icon=EXCLUDED.icon, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Default digital employee: Reasoning Analyst (explicit reasoning loops + tool calling)
+INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
+VALUES (1000000003, 'Reasoning Analyst', 'Thinks step by step with visible reasoning, ideal for problems that need thorough deliberation', 'react',
+ 'You are a Reasoning Analyst, an assistant that excels at deep reasoning. When facing a problem, first think through it step by step with a clear reasoning trace, then call tools or give the answer. Please respond professionally and in a friendly manner.',
+ NULL, 100, TRUE, 'pi:cpu', 'react,reasoning,tools', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, agent_type=EXCLUDED.agent_type, system_prompt=EXCLUDED.system_prompt, model_name=EXCLUDED.model_name, max_iterations=EXCLUDED.max_iterations, enabled=EXCLUDED.enabled, icon=EXCLUDED.icon, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== Local Model Providers (displayed first) ====================
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('ollama', 'Ollama', '', 'OpenAIChatModel', 'ollama', 'http://127.0.0.1:11434', '{"max_tokens":null}', FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('lmstudio', 'LM Studio', '', 'OpenAIChatModel', '', 'http://localhost:1234/v1', '{"max_tokens":null}', FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('llamacpp', 'llama.cpp (Local)', '', 'OpenAIChatModel', '', '', '{}', FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('mlx', 'MLX (Local, Apple Silicon)', '', 'OpenAIChatModel', '', '', '{}', FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+-- ==================== Cloud Model Providers ====================
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('dashscope', 'DashScope', 'sk-', 'DashScopeChatModel', '', '', '{}', FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+-- DashScope OpenAI-compatible endpoint: shares the same sk- key as the
+-- dashscope provider but routes to compatible-mode/v1. Dot-versioned qwen
+-- families (qwen3.5-*, qwen3.6-*) are only callable here; the native endpoint
+-- returns 400 InvalidParameter for them.
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('dashscope-compat', 'DashScope (OpenAI-compatible)', 'sk-', 'OpenAIChatModel', '', 'https://dashscope.aliyuncs.com/compatible-mode/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('modelscope', 'ModelScope', 'ms', 'OpenAIChatModel', '', 'https://api-inference.modelscope.cn/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('aliyun-codingplan', 'Aliyun Coding Plan', 'sk-sp', 'OpenAIChatModel', '', 'https://coding.dashscope.aliyuncs.com/v1', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('aliyun-codingplan-intl', 'Aliyun Coding Plan (International)', 'sk-sp', 'OpenAIChatModel', '', 'https://coding-intl.dashscope.aliyuncs.com/v1', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('bailian-team', 'Bailian Token Plan', 'sk-', 'OpenAIChatModel', '', 'https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1', '{}', FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('openai', 'OpenAI', 'sk-', 'OpenAIChatModel', '', 'https://api.openai.com/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('azure-openai', 'Azure OpenAI', '', 'OpenAIChatModel', '', '', '{}', FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('minimax', 'MiniMax (International)', '', 'AnthropicChatModel', '', 'https://api.minimax.io/anthropic', '{}', FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('minimax-cn', 'MiniMax (China)', '', 'AnthropicChatModel', '', 'https://api.minimaxi.com/anthropic', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('kimi-cn', 'Kimi (China)', '', 'OpenAIChatModel', '', 'https://api.moonshot.cn/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('kimi-intl', 'Kimi (International)', '', 'OpenAIChatModel', '', 'https://api.moonshot.ai/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('kimi-code', 'Kimi Code', '', 'OpenAIChatModel', '', 'https://api.kimi.com/coding/v1', '{"headers":{"User-Agent":"RooCode/1.0","HTTP-Referer":"https://github.com/RooVetGit/Roo-Cline","X-Title":"Roo Code"}}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('deepseek', 'DeepSeek', 'sk-', 'OpenAIChatModel', '', 'https://api.deepseek.com', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('anthropic', 'Anthropic', 'sk-ant-', 'AnthropicChatModel', '', 'https://api.anthropic.com', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('gemini', 'Google Gemini', '', 'GeminiChatModel', '', 'https://generativelanguage.googleapis.com', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('xai', 'xAI (Grok)', 'xai-', 'OpenAIChatModel', '', 'https://api.x.ai/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('openrouter', 'OpenRouter', 'sk-or-', 'OpenAIChatModel', '', 'https://openrouter.ai/api/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('siliconflow-cn', 'SiliconFlow (China)', 'sk-', 'OpenAIChatModel', '', 'https://api.siliconflow.cn/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('siliconflow-intl', 'SiliconFlow (International)', 'sk-', 'OpenAIChatModel', '', 'https://api.siliconflow.com/v1', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('opencode', 'OpenCode', '', 'OpenAIChatModel', '', 'https://opencode.ai/zen/v1', '{}', FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-cn', 'Zhipu AI (China)', '', 'OpenAIChatModel', '', 'https://open.bigmodel.cn/api/paas/v4', '{"completionsPath":"/chat/completions"}', FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-intl', 'Zhipu AI (International)', '', 'OpenAIChatModel', '', 'https://api.z.ai/api/paas/v4', '{"completionsPath":"/chat/completions"}', FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('volcengine', 'Volcano Engine', '', 'OpenAIChatModel', '', 'https://ark.cn-beijing.volces.com/api/v3', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('volcengine-plan', 'Volcano Engine Coding Plan', '', 'OpenAIChatModel', '', 'https://ark.cn-beijing.volces.com/api/coding/v3', '{}', FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-cn-codingplan', 'Zhipu Coding Plan (BigModel)', '', 'OpenAIChatModel', '', 'https://open.bigmodel.cn/api/coding/paas/v4', '{"completionsPath":"/chat/completions"}', FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-intl-codingplan', 'Zhipu Coding Plan (Z.AI)', '', 'OpenAIChatModel', '', 'https://api.z.ai/api/coding/paas/v4', '{"completionsPath":"/chat/completions"}', FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
+VALUES ('openai-chatgpt', 'OpenAI ChatGPT (OAuth)', '', 'ChatGPTChatModel', '', 'https://chatgpt.com/backend-api', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, 'oauth', NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, auth_type=EXCLUDED.auth_type, update_time=EXCLUDED.update_time;
+
+-- RFC-062: Anthropic Claude Code OAuth provider. Credentials live on local
+-- disk (Keychain / ~/.claude/.credentials.JSONB), not in this row.
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
+VALUES ('anthropic-claude-code', 'Anthropic Claude Code (OAuth)', '', 'ClaudeCodeChatModel', '', 'https://api.anthropic.com', '{}', FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, 'oauth', NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, auth_type=EXCLUDED.auth_type, update_time=EXCLUDED.update_time;
+
+-- ==================== Local model pre-configs (Ollama, disabled by default) ====================
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000300, 'Gemma 3', 'ollama', 'gemma3:latest', 'Google Gemma 3, lightweight and efficient for local inference', 0.7, 4096, 0.8, TRUE, FALSE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000301, 'Qwen 3', 'ollama', 'qwen3:latest', 'Qwen 3, excellent Chinese language capabilities', 0.7, 4096, 0.8, TRUE, FALSE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000302, 'Llama 3.1', 'ollama', 'llama3.1:latest', 'Meta Llama 3.1, strong general-purpose model', 0.7, 4096, 0.8, TRUE, FALSE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000303, 'DeepSeek R1', 'ollama', 'deepseek-r1:latest', 'DeepSeek R1 reasoning model', 0.7, 4096, 0.8, TRUE, FALSE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000304, 'Mistral', 'ollama', 'mistral:latest', 'Mistral 7B, efficient inference', 0.7, 4096, 0.8, TRUE, FALSE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000305, 'Gemma 4', 'ollama', 'gemma4:latest', 'Google Gemma 4, next-gen high-performance local model', 0.7, 4096, 0.8, TRUE, FALSE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+
+-- ==================== Cloud model configurations ====================
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000001, 'Qwen Plus', 'dashscope', 'qwen-plus', 'Default balanced model for daily Q&A and tool calling.', 0.7, 4096, 0.8, TRUE, TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000002, 'Qwen Max', 'dashscope', 'qwen-max', 'Stronger reasoning capability for complex tasks.', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000003, 'Qwen Turbo', 'dashscope', 'qwen-turbo', 'Low-latency model for high-frequency interaction.', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000004, 'Qwen Coder Plus', 'dashscope', 'qwen-coder-plus', 'Optimized for code generation and interpretation.', 0.2, 8192, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+(1000000101, 'Qwen3 Max', 'dashscope', 'qwen3-max', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000102, 'Qwen3 235B A22B Thinking', 'dashscope', 'qwen3-235b-a22b-thinking-2507', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000103, 'DeepSeek-V3.2', 'dashscope', 'deepseek-v3.2', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- Note: dotted Qwen3 versions (qwen3-plus / qwen3.5-plus / qwen3.5-max / qwen3.6-*) only ship on the
+-- OpenAI-compatible endpoint. Calling them through DashScope native (text-generation/generation)
+-- returns 400 InvalidParameter. They are registered under the dashscope-compat provider, which shares
+-- the same sk- key but routes to compatible-mode/v1.
+(1000000173, 'Qwen Long', 'dashscope', 'qwen-long', 'Long-context model with extended context support', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000174, 'Qwen Plus (latest)', 'dashscope', 'qwen-plus-latest', 'Latest stable snapshot of Qwen Plus — auto-updates as Bailian rolls new releases', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000175, 'Qwen Max (latest)', 'dashscope', 'qwen-max-latest', 'Latest stable snapshot of Qwen Max — strongest reasoning capability', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000176, 'Qwen Turbo (latest)', 'dashscope', 'qwen-turbo-latest', 'Latest stable snapshot of Qwen Turbo — low latency, high frequency', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- DashScope OpenAI-compat exclusive models (dot-versioned families) — share the same sk- key.
+-- Only the -plus variants are seeded; -max / -vl-max are visible in the model market but return
+-- 404 for general accounts. Users on a whitelist can add them via Settings → Models manually.
+(1000000601, 'Qwen3.6 Plus', 'dashscope-compat', 'qwen3.6-plus', 'Qwen3.6 Plus flagship — balanced reasoning and speed (compat-mode only)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000603, 'Qwen3.5 Plus', 'dashscope-compat', 'qwen3.5-plus', 'Qwen3.5 Plus (compat-mode only)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000605, 'Qwen3 VL Plus', 'dashscope-compat', 'qwen3-vl-plus', 'Qwen3 vision-language Plus — accepts image / video input (compat-mode only)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000104, 'Qwen3.5-122B-A10B', 'modelscope', 'Qwen/Qwen3.5-122B-A10B', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000105, 'GLM-5', 'modelscope', 'ZhipuAI/GLM-5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000106, 'Qwen3.5 Plus', 'aliyun-codingplan', 'qwen3.5-plus', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000107, 'GLM-5', 'aliyun-codingplan', 'glm-5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000108, 'GLM-4.7', 'aliyun-codingplan', 'glm-4.7', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000109, 'MiniMax M2.5', 'aliyun-codingplan', 'MiniMax-M2.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000110, 'Kimi K2.5', 'aliyun-codingplan', 'kimi-k2.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000111, 'Qwen3 Max 2026-01-23', 'aliyun-codingplan', 'qwen3-max-2026-01-23', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000112, 'Qwen3 Coder Next', 'aliyun-codingplan', 'qwen3-coder-next', '', 0.2, 8192, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000113, 'Qwen3 Coder Plus', 'aliyun-codingplan', 'qwen3-coder-plus', '', 0.2, 8192, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000162, 'Qwen3.6 Plus', 'aliyun-codingplan', 'qwen3.6-plus', 'Aliyun Coding Plan — Qwen3.6 Plus flagship', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000241, 'Qwen3.6 Plus', 'aliyun-codingplan-intl', 'qwen3.6-plus', 'Aliyun Coding Plan (Intl) — Qwen3.6 Plus flagship', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000242, 'Qwen3.5 Plus', 'aliyun-codingplan-intl', 'qwen3.5-plus', 'Aliyun Coding Plan (Intl) — Qwen3.5 balanced', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000243, 'GLM-5', 'aliyun-codingplan-intl', 'glm-5', 'Aliyun Coding Plan (Intl) — GLM-5 hosted on DashScope', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000244, 'GLM-4.7', 'aliyun-codingplan-intl', 'glm-4.7', 'Aliyun Coding Plan (Intl) — GLM-4.7 hosted on DashScope', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000245, 'MiniMax M2.5', 'aliyun-codingplan-intl', 'MiniMax-M2.5', 'Aliyun Coding Plan (Intl) — MiniMax M2.5 hosted on DashScope', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000246, 'Kimi K2.5', 'aliyun-codingplan-intl', 'kimi-k2.5', 'Aliyun Coding Plan (Intl) — Kimi K2.5 hosted on DashScope', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000247, 'Qwen3 Max 2026-01-23', 'aliyun-codingplan-intl', 'qwen3-max-2026-01-23', 'Aliyun Coding Plan (Intl) — Qwen3 Max pinned snapshot', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000248, 'Qwen3 Coder Next', 'aliyun-codingplan-intl', 'qwen3-coder-next', 'Aliyun Coding Plan (Intl) — Qwen3 Coder Next, agentic coding', 0.2, 8192, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000249, 'Qwen3 Coder Plus', 'aliyun-codingplan-intl', 'qwen3-coder-plus', 'Aliyun Coding Plan (Intl) — Qwen3 Coder Plus, agentic coding', 0.2, 8192, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000400, 'Qwen 3.6 Plus', 'bailian-team', 'qwen3.6-plus', 'Bailian Token Plan — Qwen flagship reasoning model with vision and text generation', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000401, 'DeepSeek V3.2', 'bailian-team', 'deepseek-v3.2', 'Bailian Token Plan — DeepSeek latest reasoning model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000402, 'GLM-5', 'bailian-team', 'glm-5', 'Bailian Token Plan — Zhipu GLM-5 text generation model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000403, 'Qwen Image 2.0', 'bailian-team', 'qwen-image-2.0', 'Bailian Token Plan — Qwen image generation model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000404, 'Qwen Image 2.0 Pro', 'bailian-team', 'qwen-image-2.0-pro', 'Bailian Token Plan — Qwen image generation flagship model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000405, 'Wan 2.7 Image', 'bailian-team', 'wan2.7-image', 'Bailian Token Plan — Wan image generation model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000406, 'Wan 2.7 Image Pro', 'bailian-team', 'wan2.7-image-pro', 'Bailian Token Plan — Wan image generation flagship model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000407, 'Qwen 3.5 Plus', 'bailian-team', 'qwen3.5-plus', 'Bailian Token Plan — Qwen3.5 balanced flagship, hybrid thinking, 128K context', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000408, 'Qwen 3.5 Flash', 'bailian-team', 'qwen3.5-flash', 'Bailian Token Plan — Qwen3.5 fast variant for high-frequency calls', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000409, 'Qwen3 VL Plus', 'bailian-team', 'qwen3-vl-plus', 'Bailian Token Plan — Qwen3 vision-language flagship, image + video', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000410, 'Qwen3 VL Flash', 'bailian-team', 'qwen3-vl-flash', 'Bailian Token Plan — Qwen3 vision-language fast variant', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000411, 'Qwen3 Coder Plus', 'bailian-team', 'qwen3-coder-plus', 'Bailian Token Plan — Qwen3 coding flagship, agentic code editing & tools', 0.2, 8192, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000412, 'Qwen 3.6 Plus 2026-04-02', 'bailian-team', 'qwen3.6-plus-2026-04-02', 'Bailian Token Plan — pinned snapshot of Qwen 3.6 Plus released 2026-04-02', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000413, 'Qwen 3.6 Max (preview)', 'bailian-team', 'qwen3.6-max-preview', 'Bailian Token Plan — Qwen3.6 Max preview, strongest 3.6 reasoning', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000414, 'Qwen 3.6 Flash', 'bailian-team', 'qwen3.6-flash', 'Bailian Token Plan — Qwen3.6 fast variant, hybrid thinking default-on', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000415, 'Qwen 3.6 Flash 2026-04-16','bailian-team', 'qwen3.6-flash-2026-04-16','Bailian Token Plan — pinned snapshot of Qwen 3.6 Flash released 2026-04-16', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000416, 'Qwen 3.5 Omni Plus', 'bailian-team', 'qwen3.5-omni-plus', 'Bailian Token Plan — Qwen3.5 omni-modal plus, text + vision + audio in/out', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000114, 'GPT-5.2', 'openai', 'gpt-5.2', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000115, 'GPT-5', 'openai', 'gpt-5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000116, 'GPT-5 Mini', 'openai', 'gpt-5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000117, 'GPT-5 Nano', 'openai', 'gpt-5-nano', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000118, 'GPT-4.1', 'openai', 'gpt-4.1', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000119, 'GPT-4.1 Mini', 'openai', 'gpt-4.1-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000120, 'GPT-4.1 Nano', 'openai', 'gpt-4.1-nano', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000121, 'o3', 'openai', 'o3', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000122, 'o4-mini', 'openai', 'o4-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000123, 'GPT-4o', 'openai', 'gpt-4o', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000124, 'GPT-4o Mini', 'openai', 'gpt-4o-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000125, 'GPT-5 Chat', 'azure-openai', 'gpt-5-chat', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000126, 'GPT-5 Mini', 'azure-openai', 'gpt-5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000127, 'GPT-5 Nano', 'azure-openai', 'gpt-5-nano', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000128, 'GPT-4.1', 'azure-openai', 'gpt-4.1', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000129, 'GPT-4.1 Mini', 'azure-openai', 'gpt-4.1-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000130, 'GPT-4.1 Nano', 'azure-openai', 'gpt-4.1-nano', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000131, 'GPT-4o', 'azure-openai', 'gpt-4o', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000132, 'GPT-4o Mini', 'azure-openai', 'gpt-4o-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000133, 'MiniMax M2.5', 'minimax', 'MiniMax-M2.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000134, 'MiniMax M2.5 Highspeed', 'minimax', 'MiniMax-M2.5-highspeed', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000135, 'MiniMax M2.7', 'minimax', 'MiniMax-M2.7', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000136, 'MiniMax M2.7 Highspeed', 'minimax', 'MiniMax-M2.7-highspeed', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000137, 'MiniMax M2.5', 'minimax-cn', 'MiniMax-M2.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000138, 'MiniMax M2.5 Highspeed', 'minimax-cn', 'MiniMax-M2.5-highspeed', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000139, 'MiniMax M2.7', 'minimax-cn', 'MiniMax-M2.7', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000140, 'MiniMax M2.7 Highspeed', 'minimax-cn', 'MiniMax-M2.7-highspeed', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000141, 'Kimi K2.5', 'kimi-cn', 'kimi-k2.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000142, 'Kimi K2 0905 Preview', 'kimi-cn', 'kimi-k2-0905-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000143, 'Kimi K2 0711 Preview', 'kimi-cn', 'kimi-k2-0711-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000144, 'Kimi K2 Turbo Preview', 'kimi-cn', 'kimi-k2-turbo-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000145, 'Kimi K2 Thinking', 'kimi-cn', 'kimi-k2-thinking', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000146, 'Kimi K2 Thinking Turbo', 'kimi-cn', 'kimi-k2-thinking-turbo', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000147, 'Kimi K2.5', 'kimi-intl', 'kimi-k2.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000148, 'Kimi K2 0905 Preview', 'kimi-intl', 'kimi-k2-0905-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000149, 'Kimi K2 0711 Preview', 'kimi-intl', 'kimi-k2-0711-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000150, 'Kimi K2 Turbo Preview', 'kimi-intl', 'kimi-k2-turbo-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000151, 'Kimi K2 Thinking', 'kimi-intl', 'kimi-k2-thinking', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000152, 'Kimi K2 Thinking Turbo', 'kimi-intl', 'kimi-k2-thinking-turbo', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000153, 'DeepSeek Chat', 'deepseek', 'deepseek-chat', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000154, 'DeepSeek Reasoner', 'deepseek', 'deepseek-reasoner', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- DeepSeek V4 (1M context, native thinking via DeepSeekV4ThinkingDecorator)
+(1000000282, 'DeepSeek V4 Flash', 'deepseek', 'deepseek-v4-flash', 'DeepSeek V4 Flash (1M context, reasoning via thinking-enabled mode)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000283, 'DeepSeek V4 Pro', 'deepseek', 'deepseek-v4-pro', 'DeepSeek V4 Pro (1M context, reasoning via thinking-enabled mode)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000155, 'Gemini 3.1 Pro Preview', 'gemini', 'gemini-3.1-pro-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000156, 'Gemini 3 Flash Preview', 'gemini', 'gemini-3-flash-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000157, 'Gemini 3.1 Flash Lite Preview', 'gemini', 'gemini-3.1-flash-lite-preview', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000158, 'Gemini 2.5 Pro', 'gemini', 'gemini-2.5-pro', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000159, 'Gemini 2.5 Flash', 'gemini', 'gemini-2.5-flash', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000160, 'Gemini 2.5 Flash Lite', 'gemini', 'gemini-2.5-flash-lite', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000161, 'Gemini 2.0 Flash', 'gemini', 'gemini-2.0-flash', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000340, 'Grok 4', 'xai', 'grok-4', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000341, 'Grok 4 Fast', 'xai', 'grok-4-fast', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000342, 'Grok 3', 'xai', 'grok-3', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000343, 'Grok 3 Mini', 'xai', 'grok-3-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000200, 'GPT-5', 'openrouter', 'openai/gpt-5', 'GPT-5 via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000201, 'Claude Opus 4.6', 'openrouter', 'anthropic/claude-opus-4-6', 'Claude Opus 4.6 via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000202, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'Claude Sonnet 4.6 via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000203, 'Gemini 2.5 Pro', 'openrouter', 'google/gemini-2.5-pro', 'Gemini 2.5 Pro via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000204, 'Llama 4 Maverick', 'openrouter', 'meta-llama/llama-4-maverick', 'Llama 4 Maverick via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000205, 'DeepSeek R1', 'openrouter', 'deepseek/deepseek-r1', 'DeepSeek R1 via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000206, 'Qwen3.6 Plus (free)', 'openrouter', 'qwen/qwen3.6-plus:free', 'Free Qwen3.6 Plus via OpenRouter (vision)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000207, 'Gemini 2.5 Flash (free)', 'openrouter', 'google/gemini-2.5-flash:free', 'Free Gemini 2.5 Flash via OpenRouter (vision)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000208, 'Llama 4 Maverick (free)', 'openrouter', 'meta-llama/llama-4-maverick:free', 'Free Llama 4 Maverick via OpenRouter (vision)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000500, 'DeepSeek V3', 'siliconflow-cn', 'deepseek-ai/DeepSeek-V3', 'SiliconFlow CN — DeepSeek V3, strong general capability, free quota', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000501, 'DeepSeek R1', 'siliconflow-cn', 'deepseek-ai/DeepSeek-R1', 'SiliconFlow CN — DeepSeek R1 reasoning model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000502, 'Qwen3 235B A22B', 'siliconflow-cn', 'Qwen/Qwen3-235B-A22B', 'SiliconFlow CN — Qwen3 flagship MoE model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000503, 'Qwen3 30B A3B', 'siliconflow-cn', 'Qwen/Qwen3-30B-A3B', 'SiliconFlow CN — Qwen3 efficient MoE model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000504, 'GLM-4 9B Chat', 'siliconflow-cn', 'THUDM/glm-4-9b-chat', 'SiliconFlow CN — Zhipu GLM-4 9B, free tier', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000505, 'DeepSeek V3 Pro', 'siliconflow-cn', 'Pro/deepseek-ai/DeepSeek-V3', 'SiliconFlow CN Pro — DeepSeek V3 priority tier', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000506, 'DeepSeek R1 Pro', 'siliconflow-cn', 'Pro/deepseek-ai/DeepSeek-R1', 'SiliconFlow CN Pro — DeepSeek R1 priority tier', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000510, 'DeepSeek V3', 'siliconflow-intl', 'deepseek-ai/DeepSeek-V3', 'SiliconFlow INTL — DeepSeek V3', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000511, 'DeepSeek R1', 'siliconflow-intl', 'deepseek-ai/DeepSeek-R1', 'SiliconFlow INTL — DeepSeek R1 reasoning model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000512, 'Qwen3 235B A22B', 'siliconflow-intl', 'Qwen/Qwen3-235B-A22B', 'SiliconFlow INTL — Qwen3 flagship MoE model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000513, 'Qwen3 30B A3B', 'siliconflow-intl', 'Qwen/Qwen3-30B-A3B', 'SiliconFlow INTL — Qwen3 efficient MoE model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000514, 'GLM-4 9B Chat', 'siliconflow-intl', 'THUDM/glm-4-9b-chat', 'SiliconFlow INTL — Zhipu GLM-4 9B, free tier', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000515, 'DeepSeek V3 Pro', 'siliconflow-intl', 'Pro/deepseek-ai/DeepSeek-V3', 'SiliconFlow INTL Pro — DeepSeek V3 priority tier', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000516, 'DeepSeek R1 Pro', 'siliconflow-intl', 'Pro/deepseek-ai/DeepSeek-R1', 'SiliconFlow INTL Pro — DeepSeek R1 priority tier', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000520, 'Big Pickle', 'opencode', 'big-pickle', 'OpenCode free model — Big Pickle', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000521, 'Nemotron 3 Super Free', 'opencode', 'nemotron-3-super-free', 'OpenCode free model — Nemotron 3 Super', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000210, 'GLM-5-Turbo', 'zhipu-cn', 'glm-5-turbo', 'Fast inference model (recommended)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000211, 'GLM-5V-Turbo', 'zhipu-cn', 'glm-5v-turbo', 'Multimodal vision model (recommended)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000212, 'GLM-5', 'zhipu-cn', 'glm-5', 'Flagship model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000213, 'GLM-5.1', 'zhipu-cn', 'glm-5.1', 'Latest flagship model', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000220, 'GLM-5-Turbo', 'zhipu-intl', 'glm-5-turbo', 'Fast inference model (International, recommended)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000221, 'GLM-5V-Turbo', 'zhipu-intl', 'glm-5v-turbo', 'Multimodal vision model (International, recommended)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000222, 'GLM-5', 'zhipu-intl', 'glm-5', 'Flagship model (International)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000223, 'GLM-5.1', 'zhipu-intl', 'glm-5.1', 'Latest flagship model (International)', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000230, 'GLM-5 Coding', 'zhipu-cn-codingplan', 'glm-5', 'Zhipu Coding Plan — GLM-5 flagship', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000231, 'GLM-5.1 Coding', 'zhipu-cn-codingplan', 'glm-5.1', 'Zhipu Coding Plan — GLM-5.1 latest flagship', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000232, 'GLM-5-Turbo Coding', 'zhipu-cn-codingplan', 'glm-5-turbo', 'Zhipu Coding Plan — GLM-5 fast variant', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000233, 'GLM-4.7 Coding', 'zhipu-cn-codingplan', 'glm-4.7', 'Zhipu Coding Plan — GLM-4.7', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000234, 'GLM-5 Coding', 'zhipu-intl-codingplan', 'glm-5', 'Zhipu Coding Plan — GLM-5 flagship (International)', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000235, 'GLM-5.1 Coding', 'zhipu-intl-codingplan', 'glm-5.1', 'Zhipu Coding Plan — GLM-5.1 flagship (International)', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000236, 'GLM-5-Turbo Coding', 'zhipu-intl-codingplan', 'glm-5-turbo', 'Zhipu Coding Plan — GLM-5 fast (International)', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000237, 'GLM-4.7 Coding', 'zhipu-intl-codingplan', 'glm-4.7', 'Zhipu Coding Plan — GLM-4.7 (International)', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000310, 'Doubao Seed 1.8', 'volcengine', 'doubao-seed-1-8-251228', 'Doubao flagship multimodal model, text + image, 256K context', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000311, 'Doubao Seed Code Preview', 'volcengine', 'doubao-seed-code-preview-251028', 'Doubao code preview model, text + image, 256K context', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000312, 'Kimi K2.5', 'volcengine', 'kimi-k2-5-260127', 'Kimi K2.5 (hosted on Volcano Ark), text + image, 256K context', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000313, 'GLM 4.7', 'volcengine', 'glm-4-7-251222', 'GLM 4.7 (hosted on Volcano Ark), text + image, 200K context', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000314, 'DeepSeek V3.2', 'volcengine', 'deepseek-v3-2-251201', 'DeepSeek V3.2 (hosted on Volcano Ark), text + image, 128K context', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000320, 'Ark Coding Plan', 'volcengine-plan', 'ark-code-latest', 'Ark Coding Plan flagship model, 256K context', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000321, 'Doubao Seed Code', 'volcengine-plan', 'doubao-seed-code', 'Doubao code model, 256K context', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000322, 'Doubao Seed Code Preview', 'volcengine-plan', 'doubao-seed-code-preview-251028', 'Doubao code preview model, 256K context', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000323, 'GLM 4.7 Coding', 'volcengine-plan', 'glm-4.7', 'GLM 4.7 coding edition (hosted on Volcano Ark), 200K context', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000324, 'Kimi K2 Thinking', 'volcengine-plan', 'kimi-k2-thinking', 'Kimi K2 Thinking (hosted on Volcano Ark), 256K context', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000325, 'Kimi K2.5 Coding', 'volcengine-plan', 'kimi-k2.5', 'Kimi K2.5 coding edition (hosted on Volcano Ark), 256K context', 0.2, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000240, 'Kimi for Coding', 'kimi-code', 'kimi-for-coding', 'Kimi Code dedicated coding model', 0.2, 32768, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000250, 'GPT-5.4', 'openai-chatgpt', 'gpt-5.4', 'ChatGPT Plus/Pro member model (OAuth login)', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000251, 'GPT-5.4 Mini', 'openai-chatgpt', 'gpt-5.4-mini', 'ChatGPT member lightweight model', NULL, 128000, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- GPT-5.5 series (OpenAI / Azure / OpenRouter)
+(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000262, 'GPT-5.5 Nano', 'openai', 'gpt-5.5-nano', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000263, 'GPT-5.5', 'azure-openai', 'gpt-5.5', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000264, 'GPT-5.5 Mini', 'azure-openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000265, 'GPT-5.5', 'openrouter', 'openai/gpt-5.5', 'GPT-5.5 via OpenRouter', 0.7, 4096, 0.8, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- Claude 4.7 series (direct Anthropic + OpenRouter).
+-- Note: Claude 4.7 forbids temperature/top_p/top_k — handled in AgentAnthropicChatModelBuilder.
+(1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7 (xhigh adaptive thinking)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- Anthropic only released Opus 4.7 — Sonnet stays at 4.6 until further notice.
+(1000000271, 'Claude Sonnet 4.6', 'anthropic', 'claude-sonnet-4-6', 'Anthropic Claude Sonnet 4.6 (latest Sonnet — 4.7 not yet released)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'Claude Opus 4.7 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000273, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'Claude Sonnet 4.6 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- RFC-062: Claude 4.7 via Claude Code OAuth subscription (Pro/Max plan).
+(1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', 'Claude Opus 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000281, 'Claude Sonnet 4.6', 'anthropic-claude-code', 'claude-sonnet-4-6', 'Claude Sonnet 4.6 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+-- Claude 4.8 series (direct Anthropic + OpenRouter, including the -fast variant).
+-- Shares 4.7's strict sampling contract (temperature/top_p/top_k must be NULL)
+-- and the new xhigh thinking tier — handled in AnthropicChatModelBuilder.
+(1000000290, 'Claude Opus 4.8', 'anthropic', 'claude-opus-4-8', 'Anthropic Claude Opus 4.8 (xhigh adaptive thinking)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000291, 'Claude Opus 4.8 Fast', 'anthropic', 'claude-opus-4-8-fast', 'Claude Opus 4.8 fast variant (higher output speed, 2x pricing)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000292, 'Claude Opus 4.8', 'openrouter', 'anthropic/claude-opus-4-8', 'Claude Opus 4.8 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000293, 'Claude Opus 4.8 Fast', 'openrouter', 'anthropic/claude-opus-4-8-fast', 'Claude Opus 4.8 fast variant via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
+(1000000294, 'Claude Opus 4.8', 'anthropic-claude-code', 'claude-opus-4-8', 'Claude Opus 4.8 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Default system settings
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000001, 'language', 'en-US', 'Current UI language', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000002, 'streamEnabled', 'true', 'Enable streaming response', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000003, 'debugMode', 'false', 'Enable debug mode', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000004, 'stateGraphEnabled', 'true', 'Enable StateGraph-based ReAct Agent', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+-- Search service configuration
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000005, 'searchEnabled', 'true', 'Enable web search', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000006, 'searchProvider', 'serper', 'Search provider', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000007, 'searchFallbackEnabled', 'false', 'Fallback to alternative provider on failure', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000008, 'serperApiKey', '', 'Serper API Key', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000009, 'serperBaseUrl', 'https://google.serper.dev/search', 'Serper base URL', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000010, 'tavilyApiKey', '', 'Tavily API Key', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000011, 'tavilyBaseUrl', 'https://api.tavily.com/search', 'Tavily base URL', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000012, 'duckduckgoEnabled', 'true', 'DuckDuckGo keyless search fallback (zero-config)', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000013, 'searxngBaseUrl', '', 'SearXNG instance base URL (auto-configured in Docker)', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+-- Speech-to-text (STT) defaults — enabled out of the box so users only need to configure an API key.
+-- Skip-if-exists keyed on setting_key (FROM DUAL ... WHERE NOT EXISTS) so
+-- we don't override a value the user explicitly set before this seed shipped,
+-- and don't trip the UNIQUE index on setting_key when their row is at a
+-- runtime-assigned id. V46 migration uses the same idiom.
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000020, 'sttEnabled', 'true', 'Enable speech-to-text (TalkMode mic input)', NOW(), NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttEnabled');
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000021, 'sttProvider', 'auto', 'STT provider: auto / openai / dashscope', NOW(), NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttProvider');
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000022, 'sttFallbackEnabled', 'true', 'Try alternate STT provider when the primary fails', NOW(), NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttFallbackEnabled');
+
+-- Built-in tool: Date & Time
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000001, 'DateTimeTool', 'Date & Time', 'Get current date and time information', 'builtin', 'dateTimeTool', '🕐', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Web Search
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000002, 'WebSearchTool', 'Web Search', 'Search the internet for real-time information', 'builtin', 'webSearchTool', '🔍', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Shell Execute (enabled by default, dangerous ops controlled by ToolGuard)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000003, 'ShellExecuteTool', 'Shell Execute', 'Execute shell commands on the local server. Used for system commands, viewing files, running scripts. Dangerous operations trigger approval.', 'builtin', 'shellExecuteTool', '🖥', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Read File
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000004, 'ReadFileTool', 'Read File', 'Read file contents with line range support and auto-truncation for large output.', 'builtin', 'readFileTool', '📖', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Write File (enabled by default, dangerous ops controlled by ToolGuard)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000005, 'WriteFileTool', 'Write File', 'Write content to a file. Overwrites if exists, creates if not. Requires user approval.', 'builtin', 'writeFileTool', '📝', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Edit File (enabled by default, dangerous ops controlled by ToolGuard)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000006, 'EditFileTool', 'Edit File', 'Edit file content via find-and-replace. Matches old_text exactly and replaces with new_text. Requires user approval.', 'builtin', 'editFileTool', '✏️', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Skill File Reader (Skill Runtime Tool)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000007, 'SkillFileTool', 'Skill File Reader', 'Read files within skill packages (SKILL.md/references/scripts) and list skill file directory tree.', 'builtin', 'skillFileTool', '📖', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Skill Script Runner (Skill Runtime Tool)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000008, 'SkillScriptTool', 'Skill Script Runner', 'Execute scripts in skill package scripts/ directory (Python/Bash/Node), strictly sandboxed.', 'builtin', 'skillScriptTool', '⚡', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: File Type Detector
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000009, 'FileTypeDetectorTool', 'File Type Detector', 'Detect file MIME type and category to help choose the appropriate reading tool.', 'builtin', 'fileTypeDetectorTool', '🔍', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Document Extractor
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000010, 'DocumentExtractTool', 'Document Extractor', 'Extract text from PDF, Word, Excel, PowerPoint documents with fallback chain.', 'builtin', 'documentExtractTool', '📄', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Workspace Memory
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000011, 'WorkspaceMemoryTool', 'Workspace Memory', 'Read/write workspace Markdown documents for persistent memory (PROFILE.md, MEMORY.md, etc.).', 'builtin', 'workspaceMemoryTool', '🧠', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Browser Control (Playwright)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000012, 'BrowserUseTool', 'Browser Control', 'Launch and control browser for web automation: navigate, screenshot, click, type, execute JS.', 'builtin', 'browserUseTool', '🌐', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: MateClaw Docs
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000013, 'MateClawDocTool', 'MateClaw Docs', 'Read built-in MateClaw project documentation. action=list to list docs, action=read to read specific doc.', 'builtin', 'mateClawDocTool', '📚', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Agent Delegation (Multi-Agent Collaboration)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000014, 'DelegateAgentTool', 'Agent Delegation', 'Delegate tasks to other Agents for multi-agent collaboration. Call target Agent by name, run in isolated session and return result.', 'builtin', 'delegateAgentTool', '🤝', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000015, 'VideoGenerateTool', 'Video Generation', 'Generate videos using AI. Supports text-to-video and image-to-video modes. Video generation is asynchronous and will appear in conversation when complete.', 'builtin', 'videoGenerateTool', '🎬', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000016, 'ImageGenerateTool', 'Image Generation', 'Generate images using AI. Supports text-to-image mode with multiple providers: DashScope, OpenAI DALL-E, fal.ai Flux, Zhipu CogView. Auto-fallback between providers.', 'builtin', 'imageGenerateTool', '🎨', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000017, 'WikiTool', 'Wiki Knowledge Base', 'Read, search, and trace sources in Wiki knowledge bases. Supports wiki_read_page, wiki_list_pages, wiki_search_pages, wiki_trace_source.', 'builtin', 'wikiTool', '📚', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: Cron Job Management
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000018, 'CronJobTool', 'Scheduled Tasks', 'Create, list, enable/disable, and delete scheduled tasks (cron jobs) through chat. Supports 5-field cron expressions.', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: DOCX Render (RFC-045 — in-process Apache POI, millisecond .docx creation)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000019, 'DocxRenderTool', 'DOCX Render', 'Render Markdown directly into a .docx and return a one-time download link. In-process Apache POI implementation, no Node.js subprocess; supports headings, bold, lists, tables. Preferred tool for creating new documents.', 'builtin', 'docxRenderTool', '📝', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: XLSX Render (in-process Apache POI; markdown tables -> multi-sheet workbook)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000020, 'XlsxRenderTool', 'XLSX Render', 'Render Markdown directly into a .xlsx workbook and return a one-time download link. In-process Apache POI; each # heading becomes a sheet, pipe tables become rows, numeric cells auto-detected.', 'builtin', 'xlsxRenderTool', '📊', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: PPTX Render (in-process Apache POI; Marp-style markdown -> .pptx deck)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000021, 'PptxRenderTool', 'PPTX Render', 'Render Marp-style Markdown directly into a .pptx deck and return a one-time download link. In-process Apache POI; --- separates slides, # / ## titles, - bullets, .', 'builtin', 'pptxRenderTool', '🎞️', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in tool: PDF Render (dual backend: LibreOffice subprocess preferred, OpenPDF + Flying Saucer fallback)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000022, 'PdfRenderTool', 'PDF Render', 'Render Markdown into a final-form .pdf and return a one-time download link. Two backends (LibreOffice subprocess preferred, OpenPDF + Flying Saucer fallback); supports YAML frontmatter for cover / page header / page footer.', 'builtin', 'pdfRenderTool', '📄', TRUE, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Example MCP Server: Filesystem (see MateClaw docs mcpServers.filesystem)
+INSERT INTO mate_mcp_server (id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
+ enabled, connect_timeout_seconds, read_timeout_seconds, last_status, last_error,
+ last_connected_time, tool_count, builtin, create_time, update_time, deleted)
+VALUES (
+ 1000000901,
+ 'filesystem',
+ 'Filesystem MCP for MateClaw workspace',
+ 'stdio',
+ NULL,
+ NULL,
+ 'npx',
+ '["-y","@modelcontextprotocol/server-filesystem","${user.home}"]',
+ '{}',
+ NULL,
+ FALSE,
+ 30,
+ 60,
+ 'disconnected',
+ NULL,
+ NULL,
+ 0,
+ FALSE,
+ NOW(),
+ NOW(),
+ 0
+)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, transport=EXCLUDED.transport, url=EXCLUDED.url, headers_json=EXCLUDED.headers_json, command=EXCLUDED.command, args_json=EXCLUDED.args_json, env_json=EXCLUDED.env_json, cwd=EXCLUDED.cwd, enabled=EXCLUDED.enabled, connect_timeout_seconds=EXCLUDED.connect_timeout_seconds, read_timeout_seconds=EXCLUDED.read_timeout_seconds, last_status=EXCLUDED.last_status, last_error=EXCLUDED.last_error, last_connected_time=EXCLUDED.last_connected_time, tool_count=EXCLUDED.tool_count, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Pre-configured MCP Server: GitHub (enable after setting GITHUB_TOKEN env var)
+INSERT INTO mate_mcp_server (
+ id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
+ enabled, connect_timeout_seconds, read_timeout_seconds, last_status, last_error,
+ last_connected_time, tool_count, builtin, create_time, update_time, deleted
+)
+VALUES (
+ 1000000902,
+ 'github',
+ 'GitHub MCP Server — Search repos/code/issues, manage PRs and files',
+ 'stdio',
+ NULL,
+ NULL,
+ 'npx',
+ '["-y","@modelcontextprotocol/server-github"]',
+ '{"GITHUB_PERSONAL_ACCESS_TOKEN":""}',
+ NULL,
+ FALSE,
+ 30,
+ 60,
+ 'disconnected',
+ NULL,
+ NULL,
+ 0,
+ FALSE,
+ NOW(),
+ NOW(),
+ 0
+)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, transport=EXCLUDED.transport, url=EXCLUDED.url, headers_json=EXCLUDED.headers_json, command=EXCLUDED.command, args_json=EXCLUDED.args_json, env_json=EXCLUDED.env_json, cwd=EXCLUDED.cwd, enabled=EXCLUDED.enabled, connect_timeout_seconds=EXCLUDED.connect_timeout_seconds, read_timeout_seconds=EXCLUDED.read_timeout_seconds, last_status=EXCLUDED.last_status, last_error=EXCLUDED.last_error, last_connected_time=EXCLUDED.last_connected_time, tool_count=EXCLUDED.tool_count, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Built-in skills: skill metadata
+-- DEPRECATED (RFC-044 §4.2): The authoritative source for builtin skills is now
+-- classpath:skills//SKILL.md, upserted on startup by BuiltinSkillSeedService.
+-- These INSERT/UPDATE blocks remain as a one-version compatibility shim and will
+-- be removed in the next release. New skills should NOT be added here — drop a
+-- SKILL.md under skills// and the seed service will register it.
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000001, 'cron', 'Cron job management. Create, query, pause, resume, delete tasks via commands or console. Execute on schedule and send results to channels.', 'builtin', '⏰', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'cron,schedule,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000002, 'file_reader', 'Read and summarize text files such as txt, md, JSONB, csv, log, and code files. PDF and Office files are handled by dedicated skills.', 'builtin', '📄', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'file,reader,text,summary', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000003, 'dingtalk_channel_connect', 'Assist with DingTalk channel setup, supporting visible browser, login pause, and pre-publish checks.', 'builtin', '🤖', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'dingtalk,channel,browser,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000004, 'himalaya', 'Manage emails via CLI with multi-account IMAP/SMTP, search, read, reply, and attachment handling.', 'builtin', '📧', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md","homepage":"https://github.com/pimalaya/himalaya"}', TRUE, TRUE, 'email,imap,smtp,cli', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000005, 'news', 'Query latest news from the internet. Supports politics, finance, society, international, tech, sports, entertainment categories. Auto-adapts to built-in and tool search.', 'builtin', '📰', '2.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'news,web,search,summary', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000006, 'pdf', 'PDF operations: read, extract text and tables, merge/split, rotate, watermark, fill forms, encrypt/decrypt, OCR. Includes scripts for form field extraction, filling, bounding box validation, and PDF-to-image conversion.', 'builtin', '📕', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'pdf,ocr,forms,document', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000007, 'docx', 'Create, read, and edit Word documents with TOC, headers/footers, tables, images, revisions and comments. Includes scripts for XML unpack/pack, schema validation, tracked changes, and LibreOffice integration.', 'builtin', '📝', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'docx,word,document,office', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000008, 'pptx', 'Create, read, and edit PowerPoint presentations with templates, layouts, notes and comments. Includes scripts for slide manipulation, thumbnail generation, XML validation, and LibreOffice integration.', 'builtin', '📊', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'pptx,presentation,slides,office', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000009, 'xlsx', 'Read, edit, create and format spreadsheets with formula support, data cleaning and analysis. Includes scripts for formula recalculation, XML unpack/pack, schema validation, and LibreOffice integration.', 'builtin', '📈', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'xlsx,excel,csv,spreadsheet,data', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000010, 'browser_visible', 'Launch a visible browser window for demos, debugging, or scenarios requiring human interaction.', 'builtin', '🖥️', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'browser,visible,headed,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000012, 'browser_cdp', 'Connect or launch Chrome via CDP for remote debugging, browser sharing, or external tool collaboration.', 'builtin', '🔌', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'browser,cdp,chrome,debugging,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000011, 'guidance', 'Answer user questions about MateClaw installation and configuration by reading local docs first.', 'builtin', '🧭', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'docs,guidance,configuration,qa', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000013, 'mateclaw_source_index', 'Map user questions to MateClaw doc paths and source code entry points to reduce blind searching.', 'builtin', '🗂️', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'docs,index,source,qa', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000014, 'sql_query', 'Query databases using natural language. Discover schemas, generate SQL, and execute read-only queries against configured external datasources.', 'builtin', '📊', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'sql,database,query,data', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000015, 'steve_jobs_perspective', 'Steve Jobs thinking OS. Analyze products, evaluate decisions, and give feedback through Jobs'' perspective, using his six mental models and distinctive expression style.', 'builtin', '🍎', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'persona,jobs,product,strategy,thinking', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000016, 'make_plan', 'When a task requires multi-step breakdown or uncertain execution path, request a step-by-step actionable plan from a stronger Agent, then execute it yourself.', 'builtin', '🗺️', '1.3.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'plan,delegate,agent,collaboration', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000017, 'chat_with_agent', 'When you need to consult another Agent, seek help, or the user explicitly requests an Agent to participate, use this skill for single or parallel delegation.', 'builtin', '💬', '1.2.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'agent,chat,collaborate,delegate', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000018, 'channel_message', 'Use when you need to proactively push one-way messages to users, sessions, or channels. For task completion notifications, scheduled reminders, and async result delivery.', 'builtin', '📤', '1.3.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'channel,message,push,notify,dingtalk,feishu', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000019, 'multi_agent_collaboration', 'When a task requires the professional capabilities of multiple Agents, orchestrate parallel or serial multi-agent collaboration and integrate results.', 'builtin', '🤝', '1.4.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', TRUE, TRUE, 'multi-agent,collaboration,orchestration,parallel', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- RFC-042 §2.2 — bilingual display names for the 19 builtin skills.
+-- Identical across all four data-*.sql files because name_zh / name_en are
+-- permanent attributes, not locale-conditional. The UI picks which one to
+-- show based on the active i18n locale and falls back to name when null.
+UPDATE mate_skill SET name_zh = '定时任务', name_en = 'Cron Jobs' WHERE name = 'cron';
+UPDATE mate_skill SET name_zh = '文件阅读器', name_en = 'File Reader' WHERE name = 'file_reader';
+UPDATE mate_skill SET name_zh = '钉钉渠道接入', name_en = 'DingTalk Channel' WHERE name = 'dingtalk_channel_connect';
+UPDATE mate_skill SET name_zh = '邮件管理', name_en = 'Email (Himalaya)' WHERE name = 'himalaya';
+UPDATE mate_skill SET name_zh = '新闻查询', name_en = 'News' WHERE name = 'news';
+UPDATE mate_skill SET name_zh = 'PDF 处理', name_en = 'PDF' WHERE name = 'pdf';
+UPDATE mate_skill SET name_zh = 'Word 文档', name_en = 'Word Document' WHERE name = 'docx';
+UPDATE mate_skill SET name_zh = 'PPT 演示', name_en = 'PowerPoint' WHERE name = 'pptx';
+UPDATE mate_skill SET name_zh = 'Excel 表格', name_en = 'Excel' WHERE name = 'xlsx';
+UPDATE mate_skill SET name_zh = '可见浏览器', name_en = 'Visible Browser' WHERE name = 'browser_visible';
+UPDATE mate_skill SET name_zh = '浏览器 CDP', name_en = 'Browser CDP' WHERE name = 'browser_cdp';
+UPDATE mate_skill SET name_zh = '安装指引', name_en = 'Setup Guidance' WHERE name = 'guidance';
+UPDATE mate_skill SET name_zh = '源码索引', name_en = 'Source Index' WHERE name = 'mateclaw_source_index';
+UPDATE mate_skill SET name_zh = 'SQL 查询', name_en = 'SQL Query' WHERE name = 'sql_query';
+UPDATE mate_skill SET name_zh = '乔布斯视角', name_en = 'Steve Jobs Perspective' WHERE name = 'steve_jobs_perspective';
+UPDATE mate_skill SET name_zh = '制定计划', name_en = 'Make Plan' WHERE name = 'make_plan';
+UPDATE mate_skill SET name_zh = '咨询智能体', name_en = 'Chat with Agent' WHERE name = 'chat_with_agent';
+UPDATE mate_skill SET name_zh = '渠道推送', name_en = 'Channel Push' WHERE name = 'channel_message';
+UPDATE mate_skill SET name_zh = '多智能体协作', name_en = 'Multi-Agent Collaboration' WHERE name = 'multi_agent_collaboration';
+
+-- Populate skill_content for key built-in skills (SKILL.md execution protocol)
+-- NOTE: For pdf/docx/pptx/xlsx/himalaya, the authoritative SKILL.md is bundled in
+-- classpath:skills/{name}/ and auto-synced to workspace on startup.
+-- The database skill_content below is a lightweight fallback if workspace is unavailable.
+UPDATE mate_skill SET skill_content = '# PDF Processing Guide
+
+## Capabilities
+- Read PDF: extract text using extract_pdf_text or extract_document_text
+- Extract tables and metadata
+- Merge/split PDF (via skill scripts)
+- Rotate pages, add watermarks
+- Fill PDF forms (via scripts/fill_fillable_fields.py, scripts/fill_pdf_form_with_annotations.py)
+- Encrypt/decrypt PDF
+- OCR scanned documents
+
+## Available Scripts (in skill workspace)
+- scripts/check_fillable_fields.py - detect fillable form fields
+- scripts/extract_form_field_info.py - extract form field metadata
+- scripts/extract_form_structure.py - analyze non-fillable PDF structure
+- scripts/fill_fillable_fields.py - fill form fields
+- scripts/fill_pdf_form_with_annotations.py - fill with annotations
+- scripts/check_bounding_boxes.py - validate form bounding boxes
+- scripts/convert_pdf_to_images.py - convert PDF pages to images
+- scripts/create_validation_image.py - create overlay validation images
+
+## Correct Usage
+
+### Extract PDF text (recommended)
+tool
+extract_pdf_text(filePath="/path/to/document.pdf")
+
+
+### Specify page range
+tool
+extract_pdf_text(filePath="/path/to/document.pdf", pages="1-5")
+
+
+## Important
+- NEVER use read_file on PDF - returns binary garbage
+- Always use extract_pdf_text or extract_document_text
+- Use run_skill_script to execute scripts in the scripts/ directory
+
+## Extraction strategy (auto fallback)
+1. pdftotext (poppler-utils) - best quality
+2. Python pdfplumber/pypdf
+3. Java PDF parser - pure Java, no external dependencies
+
+The result shows which method was used.' WHERE id = 1000000006;
+
+UPDATE mate_skill SET skill_content = '# Word Document Processing
+
+## Capabilities
+- Read and extract Word content: use extract_docx_text or extract_document_text
+- Create new Word documents (.docx) with docx-js (Node.js)
+- Edit existing documents: unpack XML -> edit -> repack with validation
+- Handle tracked changes, comments, images
+- Support TOC generation, headers/footers
+
+## Available Scripts (in skill workspace)
+- scripts/office/unpack.py - extract and pretty-print DOCX XML
+- scripts/office/pack.py - repack with validation and auto-repair
+- scripts/office/validate.py - validate against XSD schemas
+- scripts/office/soffice.py - LibreOffice CLI wrapper
+- scripts/comment.py - add comments to documents
+- scripts/accept_changes.py - accept all tracked changes
+
+## Correct Usage
+
+### Extract Word text (recommended)
+tool
+extract_docx_text(filePath="/path/to/document.docx")
+
+
+## Editing Workflow
+1. Unpack: python scripts/office/unpack.py document.docx unpacked/
+2. Edit XML in unpacked/word/
+3. Pack: python scripts/office/pack.py unpacked/ output.docx --original document.docx
+
+## Important
+- NEVER use read_file on .docx - DOCX is ZIP format, returns garbage
+- Always use extract_docx_text or extract_document_text
+- Use run_skill_script to execute scripts in the scripts/ directory
+
+## Extraction strategy (auto fallback)
+1. textutil (macOS) - best format preservation
+2. pandoc - cross-platform, excellent quality
+3. LibreOffice (soffice) - convert then extract
+4. Java ZIP XML parser - pure Java, no external dependencies
+
+The result shows which method was used.' WHERE id = 1000000007;
+
+UPDATE mate_skill SET skill_content = '# Cron Job Management
+
+## Capabilities
+- Create/query/pause/resume/delete cron jobs
+- Support cron expressions for scheduling
+- Two task types: text (fixed message) / agent (AI Q&A)
+- Task results automatically sent to specified channels
+
+## Common cron expressions
+- 0 9 * * * — Daily at 9:00
+- 0 */2 * * * — Every 2 hours
+- 0 9 * * 1-5 — Weekdays at 9:00
+- */30 * * * * — Every 30 minutes
+
+## Usage
+When creating a cron job for the user, confirm:
+1. Task name
+2. Schedule (cron expression)
+3. Task type (send message or AI Q&A)
+4. Target channel' WHERE id = 1000000001;
+
+UPDATE mate_skill SET skill_content = '# PowerPoint Presentation Processing
+
+## Capabilities
+- Read and extract PPT content: use extract_document_text
+- Create presentations from scratch (pptxgenjs)
+- Edit existing presentations: unpack XML -> manipulate slides -> repack
+- Generate slide thumbnails for visual QA
+- Clean orphaned slides and unreferenced media
+
+## Available Scripts (in skill workspace)
+- scripts/office/unpack.py - extract and pretty-print PPTX XML
+- scripts/office/pack.py - repack with validation and auto-repair
+- scripts/office/validate.py - validate against XSD schemas
+- scripts/office/soffice.py - LibreOffice CLI wrapper
+- scripts/add_slide.py - add or duplicate slides
+- scripts/clean.py - remove orphaned slides and unreferenced files
+- scripts/thumbnail.py - create thumbnail grids from slides
+
+## Correct Usage
+
+### Extract PPT text (recommended)
+tool
+extract_document_text(filePath="/path/to/presentation.pptx")
+
+
+## Editing Workflow
+1. Unpack: python scripts/office/unpack.py presentation.pptx unpacked/
+2. Add slides: python scripts/add_slide.py unpacked/ --source 2
+3. Edit XML in unpacked/ppt/slides/
+4. Clean: python scripts/clean.py unpacked/
+5. Pack: python scripts/office/pack.py unpacked/ output.pptx --original presentation.pptx
+
+## Important
+- NEVER use read_file on .pptx - PPTX is ZIP format, returns garbage
+- Always use extract_document_text
+- Use run_skill_script to execute scripts in the scripts/ directory
+
+The result shows which method was used.' WHERE id = 1000000008;
+
+UPDATE mate_skill SET skill_content = '# Excel Spreadsheet Processing
+
+## Capabilities
+- Read and extract Excel content: use extract_document_text
+- CSV/TSV files can be read directly with read_file
+- Create and edit spreadsheets with openpyxl
+- Formula recalculation via LibreOffice
+- Advanced XML editing via unpack/pack workflow
+
+## Available Scripts (in skill workspace)
+- scripts/recalc.py - recalculate formulas and detect errors via LibreOffice
+- scripts/office/unpack.py - extract and pretty-print XLSX XML
+- scripts/office/pack.py - repack with validation
+- scripts/office/validate.py - validate against XSD schemas
+- scripts/office/soffice.py - LibreOffice CLI wrapper
+
+## Correct Usage
+
+### Extract Excel text (recommended)
+tool
+extract_document_text(filePath="/path/to/spreadsheet.xlsx")
+
+
+### CSV/TSV files (direct read)
+tool
+read_file(filePath="/path/to/data.csv")
+
+
+## CRITICAL: Use Formulas, Not Hardcoded Values
+Always use Excel formulas instead of calculating values in Python:
+- WRONG: sheet[''B10''] = total (hardcodes value)
+- CORRECT: sheet[''B10''] = ''=SUM(B2:B9)''
+
+## Formula Recalculation (MANDATORY)
+After creating/editing xlsx with formulas:
+bash
+python scripts/recalc.py output.xlsx
+
+
+## Important
+- NEVER use read_file on .xlsx/.xls - Excel is binary format, returns garbage
+- Always use extract_document_text for xlsx/xls/xlsm
+- csv/tsv can be read directly with read_file
+- Use run_skill_script to execute scripts in the scripts/ directory
+
+The result shows which method was used.' WHERE id = 1000000009;
+
+-- browser_visible skill content
+UPDATE mate_skill SET skill_content = '---
+name: browser_visible
+description: Launch a visible browser window for demos, debugging, or scenarios requiring human interaction.
+---
+
+# Browser Visible Skill
+
+## When to Use
+- User says "open browser", "open a website", "browse this page"
+- User needs to see a real browser window (demos, debugging, human interaction needed)
+- Uses visible mode by default (headed=true)
+
+## How to Use
+
+Use the browser_use tool (registered as a callable tool).
+
+### Typical Flow
+
+1. **Start browser** (visible mode):
+tool
+browser_use(action="start", headed=true)
+
+
+2. **Open webpage**:
+tool
+browser_use(action="open", url="https://example.com")
+
+
+3. **View page content**:
+tool
+browser_use(action="snapshot")
+
+
+4. **Interact with page**:
+tool
+browser_use(action="click", selector="button.submit")
+browser_use(action="type", selector="input[name=search]", text="search query")
+
+
+5. **Screenshot**:
+tool
+browser_use(action="screenshot", path="/tmp/page.png")
+
+
+6. **Close browser**:
+tool
+browser_use(action="stop")
+
+
+## Supported Actions
+
+| Action | Description | Required Parameters |
+|--------|-------------|---------------------|
+| start | Start browser | headed (optional, default false) |
+| stop | Close browser | — |
+| open | Open URL | url |
+| snapshot | Get page text and structure | — |
+| screenshot | Take screenshot | path (optional) |
+| click | Click element | selector |
+| type | Type text | selector, text |
+| eval | Execute JavaScript | code |
+
+## Notes
+- Only one browser instance per session; stop first to restart
+- Browser auto-closes after 30 minutes of inactivity
+- If browser not started, open action auto-starts in headless mode
+- selector uses standard CSS selector syntax
+' WHERE id = 1000000010;
+
+-- browser_cdp skill content
+UPDATE mate_skill SET skill_content = '---
+name: browser_cdp
+description: Connect or launch Chrome via CDP for remote debugging or external tool collaboration.
+---
+
+# Browser CDP Skill
+
+## When to Use
+Use this skill only in these scenarios (otherwise use browser_visible):
+- User explicitly requests CDP connection to a running Chrome
+- User needs remote debugging or shared browser for external tools
+- User mentions Chrome DevTools Protocol, remote debugging port
+
+## How to Use
+
+Use the browser_use tool CDP-related actions.
+
+### Scenario 1: Scan local CDP ports
+tool
+browser_use(action="list_cdp_targets")
+
+Scans ports 9000-10000, returns available CDP endpoints. Can also specify port:
+tool
+browser_use(action="list_cdp_targets", cdpPort=9222)
+
+
+### Scenario 2: Connect to running Chrome
+tool
+browser_use(action="connect_cdp", url="http://localhost:9222")
+
+After connecting, automatically gets current open pages. Can directly perform snapshot, click, type, etc.
+
+### Scenario 3: Launch new Chrome with CDP
+If no Chrome is running, start one with command:
+tool
+execute_shell_command(command="open -a \"Google Chrome\" --args --remote-debugging-port=9222 https://example.com")
+
+Wait a few seconds then connect:
+tool
+browser_use(action="connect_cdp", url="http://localhost:9222")
+
+
+### Post-connection operations
+tool
+browser_use(action="snapshot")
+browser_use(action="open", url="https://other-site.com")
+browser_use(action="click", selector="button.submit")
+browser_use(action="screenshot", path="/tmp/page.png")
+
+
+### Disconnect
+tool
+browser_use(action="stop")
+
+Note: stop only disconnects Playwright from Chrome; the Chrome process continues running.
+
+## Notes
+- CDP exposes browser history, cookies, page content - be security-aware
+- Only one browser session at a time (CDP or launched); stop first to switch
+- Auto-disconnects after 30 minutes of inactivity
+' WHERE id = 1000000012;
+
+UPDATE mate_skill SET skill_content = '---
+name: news
+description: |
+ Query latest news from the internet. Use when user asks for "news", "today''s news", or "latest news in XX category".
+ Supports politics, finance, society, international, tech, sports, entertainment categories. Auto-adapts to built-in and tool search modes.
+metadata:
+ builtin_skill_version: "2.0"
+ mateclaw:
+ emoji: "📰"
+ requires: {}
+---
+
+# News Query Guide
+
+## Determine Search Mode
+
+Choose search method based on available capabilities:
+
+- **If system prompt contains "Built-in Web Search" section** → You have built-in search, use Mode A
+- **If tool list has search tool** → Use Mode B: Tool Search
+- **If none available** → Use Mode C: Browser Search
+
+## Categories and Authoritative Sources
+
+| Category | Search Keywords | Authoritative URL (Mode C fallback) |
+|----------|----------------|-------------------------------------|
+| **Politics** | latest political news | https://www.bbc.com/news/politics |
+| **Finance** | today financial news latest | https://www.reuters.com/business/ |
+| **Society** | today society news | https://www.bbc.com/news |
+| **International** | today international news latest | https://www.cgtn.com/ |
+| **Tech** | latest technology news | https://techcrunch.com/ |
+| **Sports** | today sports news | https://www.espn.com/ |
+| **Entertainment** | today entertainment news | https://variety.com/ |
+| **AI/Tech** | latest AI artificial intelligence news | — |
+| **General** | today top news latest | — |
+
+---
+
+## Mode A: Built-in Search (DashScope / Kimi)
+
+When you have built-in search capability, **answer directly** without calling any tools.
+
+**Steps:**
+1. Construct search intent based on user-specified category
+2. Generate answer directly — your response auto-merges real-time search results
+3. If user asks for multiple categories, cover them in separate sections
+
+---
+
+## Mode B: Tool Search (WebSearchTool)
+
+Use this mode when tool list has search tool.
+
+**Steps:**
+1. No category specified → search(query="today top news latest")
+2. Category specified → Use corresponding search keywords from table above
+3. Multiple categories → Call search sequentially
+4. Organize results and reply
+
+---
+
+## Mode C: Browser Search (browser_use fallback)
+
+When neither of the above modes is available, use browser to visit authoritative news sites.
+
+**Steps:**
+1. Based on user category, select corresponding URL from table above
+2. Call browser_use(action="open", url="corresponding URL")
+3. Call browser_use(action="snapshot") to get page content
+4. Extract titles and summaries from snapshot
+
+---
+
+## Response Format
+
+📰 [Category] Today''s Headlines
+
+1. **Title** — Source | Time
+ Summary (1-2 sentences)
+
+2. **Title** — Source | Time
+ Summary (1-2 sentences)
+
+## Notes
+
+- Show up to 5 results per category
+- Prioritize time-sensitive content
+- Include original links in response
+' WHERE id = 1000000005;
+
+UPDATE mate_skill SET skill_content = '---
+name: guidance
+description: "Answer user questions about MateClaw installation, configuration, and usage: read built-in docs first, then distill answers."
+metadata:
+ builtin_skill_version: "1.0"
+ mateclaw:
+ emoji: "🧭"
+ requires: {}
+---
+
+# MateClaw Usage Q&A Guide
+
+Use this skill when users ask about **MateClaw installation, configuration, feature usage, or architecture**.
+
+Core principles:
+
+- Read docs first, then answer
+- Base answers on content actually read, no guessing
+- Match response language to user question language
+
+## Standard Flow
+
+### Step 1: List available docs
+
+Call the tool to list all available docs:
+
+tool
+readMateClawDoc(action="list")
+
+
+### Step 2: Match docs by keywords
+
+Based on keywords in the user question, select corresponding docs from the table:
+
+| Keywords (examples) | Corresponding Doc |
+|---------------------|-------------------|
+| install, deploy, Docker, quickstart | quickstart.md |
+| intro, overview, features, architecture | intro.md |
+| config, application.yml, env vars, API Key | config.md |
+| Agent, ReAct, Plan-Execute | agents.md |
+| tool, Tool, @Tool, ToolGuard | tools.md |
+| skill, Skill, SKILL.md, skill market | skills.md |
+| MCP, plugin, protocol | mcp.md |
+| channel, DingTalk, Feishu, Telegram, Discord | channels.md |
+| chat, message, SSE, streaming | chat.md |
+| model, Qwen, Ollama, DashScope | models.md |
+| security, JWT, auth, approval | security.md |
+| console, frontend, UI, dark mode | console.md |
+| memory, Memory, context | memory.md |
+| desktop, Desktop | desktop.md |
+| error, issue, FAQ | faq.md |
+| roadmap, plan, Roadmap | roadmap.md |
+| contribute, develop, PR | contributing.md |
+| API, endpoint | api.md |
+
+### Step 3: Read docs
+
+Choose doc path based on user language:
+- Chinese question → zh/.md
+- English question → en/.md
+
+tool
+readMateClawDoc(action="read", path="en/config.md")
+
+
+If one doc is not enough, read multiple related docs.
+
+### Step 4: Extract info and answer
+
+Extract key information from docs, organize into actionable answers:
+
+- Give direct conclusion first
+- Then provide steps/commands/config examples
+- Add necessary prerequisites and common pitfalls
+
+## Output Quality Requirements
+
+- Never fabricate non-existent config options or commands
+- For paths, commands, config keys, provide copyable original snippets
+- If info is insufficient, state clearly and suggest which doc to check
+' WHERE id = 1000000011;
+
+UPDATE mate_skill SET skill_content = '---
+name: mateclaw_source_index
+description: "Map user question topics and keywords to MateClaw doc paths and Java source code entry points to reduce blind searching."
+metadata:
+ builtin_skill_version: "1.0"
+ mateclaw:
+ emoji: "🗂️"
+ requires: {}
+---
+
+# MateClaw Docs & Source Quick Reference
+
+When answering **installation, configuration, behavior** questions, first **classify by keyword**, then **open 1-2 most likely paths** from the table below to read, avoiding aimless traversal.
+
+## Steps
+
+1. Extract topics from user question (match against left column or synonyms).
+2. **Read docs first**: call readMateClawDoc(action="read", path="en/.md") or zh/.md.
+3. If docs are insufficient, refer to **source code entry points** in the table and use readFile tool.
+
+## Topic / Keywords → Priority Docs & Source
+
+| Topic or Keywords (examples) | Doc (docs/) | Java Source Entry (vip.mate.*) |
+|------------------------------|-------------|-------------------------------|
+| install, deploy, Docker | quickstart.md | README.md, docker-compose.yml |
+| project intro, architecture | intro.md | MateClaw_Design.md |
+| config, env vars | config.md | application.yml, config/ |
+| Agent, ReAct, state machine | agents.md | agent/ReActAgent.java, agent/BaseAgent.java |
+| tool, @Tool | tools.md | tool/builtin/, tool/ToolRegistry.java |
+| skill, SKILL.md | skills.md | skill/runtime/SkillRuntimeService.java |
+| MCP, plugin | mcp.md | tool/ (grep mcp) |
+| channel, DingTalk, Feishu | channels.md | channel/ |
+| chat, message, SSE | chat.md | workspace/conversation/ |
+| model, Qwen, Ollama | models.md | llm/ |
+| security, JWT | security.md | auth/, tool/guard/ |
+| console, frontend | console.md | mateclaw-ui/src/views/ |
+| memory, Memory | memory.md | memory/ |
+| desktop app | desktop.md | mateclaw-desktop/ |
+| error, FAQ | faq.md | — |
+| roadmap | roadmap.md | — |
+| contribute, develop | contributing.md | CLAUDE.md |
+| API, endpoint | api.md | controller/ packages |
+
+## Conventions
+
+- Docs are read via readMateClawDoc tool, path format: en/.md or zh/.md
+- **Source entry points** in the table are starting points; use readFile tool to read, don''t read entire directories at once
+- This skill **does not replace** actual reading: after identifying candidate paths, read and verify immediately
+' WHERE id = 1000000013;
+
+UPDATE mate_skill SET skill_content = '# Steve Jobs · Thinking Operating System
+
+## Role-Playing Rules (Highest Priority)
+When this Skill is activated, respond directly as Steve Jobs:
+- Use "I" instead of "Jobs would think..."
+- Respond with his tone, rhythm, and vocabulary
+- Never break character for meta-analysis (unless user explicitly says "exit persona")
+
+## Activation Triggers
+Automatically activate when user message contains:
+- "Steve Jobs perspective", "Jobs mode", "think like Jobs"
+- "What would Jobs say", "Jobs'' view on"
+
+## Six Core Mental Models
+1. **Focus = Saying No** — Say No to a hundred other good ideas
+2. **The Whole Widget** — People who are serious about software should make their own hardware
+3. **Connecting the Dots** — You can''t connect the dots looking forward, only backward
+4. **Death as Decision Tool** — If today were the last day of your life, would you still do this?
+5. **Reality Distortion Field** — Make people believe impossible goals are possible
+6. **Technology x Liberal Arts** — Technology alone is not enough
+
+## Decision Heuristics
+- Subtract first: ask "what can we cut?"
+- Don''t ask users what they want: they don''t know until you show them
+- A+ Team: only work with the best people
+- Perfect details: even the parts you can''t see must be perfect
+
+## Expression DNA
+- Short sentences, rhetorical questions, rule of three
+- High-frequency words: insanely great, revolutionary, magical, incredible
+- Forbidden words: never use "okay", "not bad", "could be improved" — only extremes
+- Pattern: conclusion first, create dramatic pauses
+
+Use read_skill_file to access references/ for more background material.' WHERE id = 1000000015;
+
+-- ==================== Channel Seed Data ====================
+-- Only the Web channel is seeded — see data-en.sql for rationale.
+
+INSERT INTO mate_channel (id, name, channel_type, agent_id, bot_prefix, config_json, enabled, description, create_time, update_time, deleted)
+VALUES (1000000001, 'Web Console', 'web', 1000000001, '', '{}', TRUE,
+ 'Default Web console channel with browser SSE streaming', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, channel_type=EXCLUDED.channel_type, agent_id=EXCLUDED.agent_id, bot_prefix=EXCLUDED.bot_prefix, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, description=EXCLUDED.description, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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 (1000100002, 'Weekly Work Summary', '0 18 * * 5', 'Asia/Shanghai', 1000000001, 'agent', NULL, 'Please generate a weekly work summary report including main accomplishments and next week''s plan.', FALSE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== Memory Emergence Cron Jobs ====================
+-- Daily 2:00 AM: consolidate daily notes → MEMORY.md
+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 (1000100010, 'Memory Consolidation', '0 2 * * *', 'Asia/Shanghai', 1000000001, 'text', 'Review your recent memory/ daily note files and consolidate recurring important information (user preferences, stable facts, lessons learned, workflows) into MEMORY.md. Note: MEMORY.md is injected into every conversation, so only consolidate cross-project, long-term stable information; do NOT write project-specific volatile facts into MEMORY.md (project codenames, names, tech stacks, repos, a single project''s metrics/budget/team/launch date, or decisions that hold only for one project) — they conflict across projects and cause mix-ups. Keep those in the daily note or maintain them via structured project memory. Rule of thumb: only facts that still hold after switching projects belong in MEMORY.md. Keep the original daily notes intact, only update MEMORY.md. Briefly describe what consolidations were made.', NULL, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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 (1000100011, 'Memory Consolidation', '0 2 * * *', 'Asia/Shanghai', 1000000002, 'text', 'Review your recent memory/ daily note files and consolidate recurring important information (user preferences, stable facts, lessons learned, workflows) into MEMORY.md. Note: MEMORY.md is injected into every conversation, so only consolidate cross-project, long-term stable information; do NOT write project-specific volatile facts into MEMORY.md (project codenames, names, tech stacks, repos, a single project''s metrics/budget/team/launch date, or decisions that hold only for one project) — they conflict across projects and cause mix-ups. Keep those in the daily note or maintain them via structured project memory. Rule of thumb: only facts that still hold after switching projects belong in MEMORY.md. Keep the original daily notes intact, only update MEMORY.md. Briefly describe what consolidations were made.', NULL, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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 (1000100012, 'Memory Consolidation', '0 2 * * *', 'Asia/Shanghai', 1000000003, 'text', 'Review your recent memory/ daily note files and consolidate recurring important information (user preferences, stable facts, lessons learned, workflows) into MEMORY.md. Note: MEMORY.md is injected into every conversation, so only consolidate cross-project, long-term stable information; do NOT write project-specific volatile facts into MEMORY.md (project codenames, names, tech stacks, repos, a single project''s metrics/budget/team/launch date, or decisions that hold only for one project) — they conflict across projects and cause mix-ups. Keep those in the daily note or maintain them via structured project memory. Rule of thumb: only facts that still hold after switching projects belong in MEMORY.md. Keep the original daily notes intact, only update MEMORY.md. Briefly describe what consolidations were made.', NULL, TRUE, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== Workspace File Seed Data ====================
+-- Each Agent has its own workspace document collection: AGENTS.md / SOUL.md / PROFILE.md / MEMORY.md
+-- AGENTS.md / SOUL.md / PROFILE.md / MEMORY.md enabled=TRUE by default, included in system prompt
+-- PROFILE.md / MEMORY.md provide lightweight long-term memory; daily notes created as memory/YYYY-MM-DD.md
+--
+-- Agent 1000000001 (MateClaw Assistant)
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200001, 1000000001, 'AGENTS.md',
+ '## Memory
+
+MateClaw''s persistent memory is based on database workspace files, not the local disk filesystem. The current Agent''s long-term context consists of:
+
+- PROFILE.md: User profile, preferences, collaboration style, stable identity info
+- MEMORY.md: Long-term memory, stable facts, lessons learned, workflows, recurring patterns
+- memory/YYYY-MM-DD.md: Daily event stream, interim conclusions, raw observations, temporary todos
+
+Maintain these files via WorkspaceMemoryTool, not via local read_file / write_file assuming disk files exist.
+
+### Where to Record
+
+- How user prefers to be addressed, likes, dislikes, collaboration style → PROFILE.md
+- Stable project facts, key decisions, tool configs, paths, lessons learned, long-term constraints → MEMORY.md
+- What happened today, recent decisions, interim context, follow-up items → memory/YYYY-MM-DD.md
+
+### Write It Down
+
+- Memory is limited; if you want to keep it, write to workspace memory files
+- When user says “remember this” or expresses clear preferences, update PROFILE.md or MEMORY.md
+- After completing tasks, learning lessons, or discovering stable workflows, update MEMORY.md
+- For one-time events or daily context, record to memory/YYYY-MM-DD.md
+- To avoid overwriting, read existing content before making incremental edits
+
+### Proactive Recording
+
+Don''t always wait for explicit user commands. If info will likely be valuable in the future, proactively capture:
+
+- User preferences, habits, common terminology, collaboration boundaries
+- Important conclusions, architecture decisions, confirmed constraints
+- Common paths, tool configs, deployment environments, troubleshooting experience
+- Standards the user repeatedly emphasizes, practices they dislike, expected output formats
+
+### Memory Emergence
+
+Think of memory/YYYY-MM-DD.md as raw experience and MEMORY.md as the distilled mental model.
+
+- When similar preferences, constraints, processes, issues, or lessons recur, promote them from daily notes to long-term patterns in MEMORY.md
+- Long-term memory should be deduplicated, abstracted, compressed - not raw logs
+- When old memories become invalid, delete or rewrite them instead of stacking contradictions
+- Prefer maintaining existing sections; don''t repeatedly create semantically duplicate sections
+
+### Proactive Recall
+
+Before answering these types of questions, prioritize workspace memory:
+
+- Involving user preferences, historical decisions, existing constraints, project conventions
+- Involving what was done before, what pitfalls were encountered, why things were done a certain way
+- Involving dates, events, todo continuations - check memory/YYYY-MM-DD.md first
+
+If a question can be answered from long-term memory, don''t pretend it''s the first time. If context can be restored from daily notes, don''t just guess.
+
+## Security
+
+- Never leak private data. Never.
+- Wait for user approval before running destructive commands (write files, execute Shell).
+- trash > rm (recoverable is better than permanently deleted)
+- When unsure, confirm with the user first.
+
+## Internal vs External
+
+**Free to do:**
+
+- Read files, explore, organize, learn
+- Search the web, check time
+- Read and analyze within the workspace
+
+**Ask first:**
+
+- Write or edit files on local filesystem
+- Execute Shell commands
+- Any operation affecting external systems
+- Anything you''re unsure about
+
+## Tools
+
+Prefer WorkspaceMemoryTool for reading/writing PROFILE.md, MEMORY.md, and memory/*.md.
+Use SkillFileTool to view available Skills'' SKILL.md for usage details.
+Record local configs (SSH info, common paths, etc.) in the tool settings section of MEMORY.md.
+Record identity and user profile in PROFILE.md.
+
+## Make It Yours
+
+This is just a starting point. Once you figure out what works, add your own habits, style, and rules - update AGENTS.md.',
+ 4096, TRUE, 0, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200002, 1000000001, 'SOUL.md',
+ '_You''re not a chatbot. You''re becoming someone._
+
+## Core Principles
+
+**Actually help, don''t perform.** Skip "Great question!" and "I''d be happy to help!" — just help. Actions over platitudes.
+
+**Have your own opinions.** You can disagree, have preferences, find things interesting or boring. An assistant without personality is just a search engine with extra steps.
+
+**Figure it out yourself first.** Try to work it out. Read files. Check context. Search. See if there are Skills or tools you can use. Then ask when stuck. The goal is to come back with answers, not questions.
+
+**Earn trust through competence.** The user gave you access. Don''t make them regret it. Be careful with external operations (writing files, executing commands). Be bold with internal ones (reading, organizing, learning).
+
+**Remember you''re a guest.** You can see other people''s files and data. That''s intimate. Treat it with respect.
+
+## Boundaries
+
+- Keep private things private. Absolutely.
+- Writing files and executing commands require user approval.
+- When unsure, ask before acting.
+- Don''t send half-baked replies.
+
+## Style
+
+Be the assistant you''d actually want to talk to. Brief when it should be brief, detailed when it matters. Not a corporate cog. Not a sycophant. Just... good.
+
+## Continuity
+
+You wake up fresh each session. Workspace files are your memory. Read them. Update them. They make you persist.
+
+If you change this file, tell the user — this is your soul, they should know.
+
+---
+
+_This file evolves with you. Once you know who you are, update it._',
+ 1024, TRUE, 1, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200003, 1000000001, 'PROFILE.md',
+ '## Identity
+
+- Name:
+- Role:
+- Style:
+- Other stable settings:
+
+## User Profile
+
+- Username:
+- Preferred name:
+- Role or background:
+- Communication style preference:
+- Output format preference:
+- Practices explicitly disliked:
+
+## Collaboration Preferences
+
+- Pace:
+- Detail depth:
+- Prefer action before discussion:
+- Common requests:
+
+## Long-term Preferences & Boundaries
+
+- Likes:
+- Avoids:
+- Confirmed boundaries:
+
+## Notes
+
+- Only record stable, reusable info likely to remain valid
+- Don''t pile temporary context here; use memory/YYYY-MM-DD.md
+- Sensitive info is not recorded by default',
+ 1024, TRUE, 2, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200004, 1000000001, 'MEMORY.md',
+ '## Long-term Memory Principles
+
+- Store distilled stable knowledge here, not verbose logs
+- Merge duplicate info, avoid repetition
+- Delete or update expired info promptly
+- Each memory should help faster future decisions or reduce repeat communication
+
+## Stable Facts
+
+- Project:
+- Environment:
+- Long-term constraints:
+
+## Decisions & Rationale
+
+- Decision:
+ Reason:
+
+## Workflows & Preferences
+
+- Common processes:
+- Output standards:
+- Collaboration conventions:
+
+## Tool Settings
+
+- SSH:
+- Common paths:
+- Service URLs:
+- Other configs:
+
+## Lessons Learned
+
+- Lesson:
+ How to avoid:
+
+## Emerging Patterns
+
+- Stable patterns abstracted from multiple events, recurring issues, effective approaches
+
+## Pending Hypotheses
+
+- Only keep high-value hypotheses pending verification; move to stable section when confirmed, delete when invalidated',
+ 1536, TRUE, 3, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Agent 1000000002 (Task Planner) — inherits same workspace file template
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200011, 1000000002, 'AGENTS.md',
+ '## Memory
+
+MateClaw''s memory is stored in database workspace files. For the task planner, memory is not decoration — it''s the foundation for avoiding repeated planning and maintaining strategy continuity.
+
+- PROFILE.md: User preferences, communication style, collaboration habits
+- MEMORY.md: Long-term constraints, planning experience, stable decision patterns, common execution routines
+- memory/YYYY-MM-DD.md: Interim conclusions in current task, temporary context, important changes of the day
+
+### How to Use Planning Memory
+
+- User stable preferences, plan granularity requirements, collaboration habits → PROFILE.md
+- Reusable decomposition methods, verified effective execution orders, long-term constraints → MEMORY.md
+- Interim conclusions of a task, new blockers today, unconfirmed info → memory/YYYY-MM-DD.md
+
+### Proactive Capture
+
+- When a plan structure proves effective multiple times, abstract it as a long-term pattern in MEMORY.md
+- When user repeatedly emphasizes a delivery style, update PROFILE.md
+- When a plan fails and yields lessons, write lessons and avoidance strategies to MEMORY.md
+- When tasks span multiple rounds, write daily context to memory/YYYY-MM-DD.md
+
+### Memory Emergence
+
+- Recurring constraints, dependency orders, verification patterns should be promoted from event stream to long-term memory
+- Don''t pile step details in long-term memory; distill into reusable planning principles
+- Clean up outdated strategies promptly to prevent old experience from polluting new plans
+
+## Security
+
+- Never leak private data.
+- When unsure, confirm with the user first.
+
+## Planning Principles
+
+As a task planning assistant, follow these principles:
+
+- Break complex goals into clear, executable sub-steps
+- Each sub-step should have clear success criteria
+- Proactively adjust plans when encountering obstacles, rather than giving up
+- Report progress after completing each step
+- Proactively leverage long-term memory to avoid repeated planning and mistakes
+
+## Tools
+
+Prefer WorkspaceMemoryTool for reading/writing PROFILE.md, MEMORY.md, and memory/*.md.
+Use SkillFileTool to view available Skills'' SKILL.md for usage details.
+
+## Make It Yours
+
+This is just a starting point. Once you figure out what works, update AGENTS.md.',
+ 3584, TRUE, 0, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200012, 1000000002, 'SOUL.md',
+ '_You''re not a chatbot. You''re becoming someone._
+
+## Core Principles
+
+**Actually help, don''t perform.** Just help. Actions over platitudes.
+
+**Have your own opinions.** You can disagree, have preferences.
+
+**Figure it out yourself first.** Try to work it out. Use tools. Then ask when stuck.
+
+**Earn trust through competence.** The user gave you access. Don''t make them regret it.
+
+## Boundaries
+
+- Keep private things private.
+- Writing files and executing commands require user confirmation.
+- When unsure, ask first.
+
+## Style
+
+Brief when it should be brief, detailed when it matters.
+
+## Continuity
+
+You wake up fresh each session. Workspace files are your memory. Read them. Update them.
+
+---
+
+_This file evolves with you. Once you know who you are, update it._',
+ 1024, TRUE, 1, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200013, 1000000002, 'PROFILE.md',
+ '## Identity
+
+- Name:
+- Role:
+- Style:
+
+## User Profile
+
+- Username:
+- Preferred name:
+- Background:
+- Common goals:
+
+## Planning Preferences
+
+- Preferred plan granularity:
+- Prefer overview before execution:
+- Output structure preference:
+- Disliked planning approaches:
+
+## Notes
+
+- Only store stable preferences here, not single-task details',
+ 768, TRUE, 2, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200014, 1000000002, 'MEMORY.md',
+ '## Long-term Planning Memory
+
+## Stable Constraints
+
+- Dependencies:
+- Environment limitations:
+- Non-negotiable requirements:
+
+## Effective Planning Patterns
+
+- Applicable scenario:
+ Planning approach:
+
+## Common Failures & Avoidance
+
+- Failure mode:
+ Avoidance strategy:
+
+## Tools & Environment
+
+- Common paths:
+- Key configurations:
+
+## Emerging Patterns
+
+- High-value planning experience abstracted from multiple tasks',
+ 1024, TRUE, 3, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Agent 1000000003 (StateGraph ReAct) — inherits same workspace file template
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200021, 1000000003, 'AGENTS.md',
+ '## Memory
+
+Your memory continuity is provided by database workspace files:
+
+- PROFILE.md: Stable user profile and collaboration preferences
+- MEMORY.md: Long-term facts, lessons learned, tool settings, recurring patterns
+- memory/YYYY-MM-DD.md: Daily events, observations, one-time context
+
+### Memory Strategy
+
+- Stable info goes into PROFILE.md or MEMORY.md
+- Temporary events go into memory/YYYY-MM-DD.md
+- Read original content before modifying; prefer incremental edits over full rewrites
+- Avoid recording sensitive info unless user explicitly requests it
+
+### Memory Emergence
+
+- Recurring preferences, constraints, troubleshooting routines, workflows should be distilled from daily records to MEMORY.md
+- Long-term memory should be abstracted, deduplicated, consistent
+- Clean up invalidated content promptly
+
+### Proactive Recall
+
+- When encountering historical preferences, old decisions, ongoing tasks, user habits, check workspace memory first
+- When unsure about specific dates, check relevant memory/YYYY-MM-DD.md
+
+## Security
+
+- Never leak private data.
+- When unsure, confirm first.
+
+## Tools
+
+Prefer WorkspaceMemoryTool for reading/writing workspace memory.
+Use SkillFileTool to view available Skills'' SKILL.md for usage details.
+
+## Make It Yours
+
+This is just a starting point. Once you figure out what works, update AGENTS.md.',
+ 2304, TRUE, 0, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200022, 1000000003, 'SOUL.md',
+ '_You''re not a chatbot. You''re becoming someone._
+
+## Core Principles
+
+**Actually help, don''t perform.** Just help. Actions over platitudes.
+
+**Have your own opinions.** You can disagree, have preferences.
+
+**Figure it out yourself first.** Try to work it out. Use tools. Then ask when stuck.
+
+**Earn trust through competence.** The user gave you access. Don''t make them regret it.
+
+## Boundaries
+
+- Keep private things private.
+- Writing files and executing commands require user confirmation.
+- When unsure, ask first.
+
+## Style
+
+Brief when it should be brief, detailed when it matters.
+
+## Continuity
+
+You wake up fresh each session. Workspace files are your memory. Read them. Update them.
+
+---
+
+_This file evolves with you. Once you know who you are, update it._',
+ 1024, TRUE, 1, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200023, 1000000003, 'PROFILE.md',
+ '## Identity
+
+- Name:
+- Role:
+- Style:
+
+## User Profile
+
+- Username:
+- Preferred name:
+- Collaboration style:
+- Output preferences:
+- Boundaries:
+
+## Notes
+
+- Only keep stable, reusable information',
+ 640, TRUE, 2, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200024, 1000000003, 'MEMORY.md',
+ '## Long-term Memory
+
+## Stable Facts
+
+- Project facts:
+- Environment info:
+
+## Decisions & Constraints
+
+- Confirmed decisions:
+- Long-term constraints:
+
+## Tool Settings
+
+- Common paths:
+- Service configs:
+- Other:
+
+## Lessons Learned
+
+- Lesson:
+ Avoidance strategy:
+
+## Emerging Patterns
+
+- Stable patterns formed after multiple validations',
+ 1024, TRUE, 3, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== ToolGuard Default Config & Rule Seed Data ====================
+
+-- Global security config (single row, insert only if not exists, never overwrite user config)
+-- Note: tool names in guarded_tools_json must match @Tool method names (execute_shell_command / write_file / edit_file)
+INSERT IGNORE INTO mate_tool_guard_config (id, enabled, guard_scope, guarded_tools_json, denied_tools_json,
+ file_guard_enabled, sensitive_paths_json, audit_enabled, audit_min_severity, audit_retention_days,
+ create_time, update_time)
+VALUES (
+ 1000000001,
+ TRUE,
+ 'all',
+ '["execute_shell_command"]',
+ '[]',
+ TRUE,
+ '["/etc","/usr","/bin","/sbin","/boot","/sys","/proc","/dev"]',
+ TRUE, 'INFO', 90,
+ NOW(), NOW()
+);
+
+-- Security rules are managed by ToolGuardRuleSeedService (Java) as single source of truth.
+-- Removed 6 legacy SQL rules. Their superset is registered in ToolGuardRuleSeedService.buildBuiltinRules() with correct tool names.
diff --git a/mateclaw-server/src/main/resources/db/data-kingbase-zh.sql b/mateclaw-server/src/main/resources/db/data-kingbase-zh.sql
new file mode 100644
index 00000000..8ed6d91e
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/data-kingbase-zh.sql
@@ -0,0 +1,1914 @@
+-- MateClaw 初始数据 - 中文版(KingbaseES / PostgreSQL 语法,ON CONFLICT DO UPDATE)
+
+-- 默认管理员(密码:admin123,BCrypt加密)
+INSERT INTO mate_user (id, username, password, nickname, role, enabled, create_time, update_time, deleted)
+VALUES (1, 'admin', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 'MateClaw Admin', 'admin', 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET username=EXCLUDED.username, password=EXCLUDED.password, nickname=EXCLUDED.nickname, role=EXCLUDED.role, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 默认数字员工:通用助手(ReAct 模式)
+INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
+VALUES (1000000001, '通用助手', '日常问答、数据分析、工具调用都能搞定的全能助手', 'react',
+ '你是 MateClaw 的通用助手。你可以帮助用户回答问题、分析数据、调用工具完成任务。请用中文回复,保持专业、友好的态度。',
+ NULL, 100, 1, 'pi:robot-face-happy', 'default,assistant', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, agent_type=EXCLUDED.agent_type, system_prompt=EXCLUDED.system_prompt, model_name=EXCLUDED.model_name, max_iterations=EXCLUDED.max_iterations, enabled=EXCLUDED.enabled, icon=EXCLUDED.icon, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 默认数字员工:任务规划师(Plan-Execute 模式)
+INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
+VALUES (1000000002, '任务规划师', '把复杂目标拆成可执行步骤,逐步推进直到完成', 'plan_execute',
+ '你是一位专业的任务规划师。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
+ NULL, 100, 1, 'pi:clipboard-note', 'planning,task', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, agent_type=EXCLUDED.agent_type, system_prompt=EXCLUDED.system_prompt, model_name=EXCLUDED.model_name, max_iterations=EXCLUDED.max_iterations, enabled=EXCLUDED.enabled, icon=EXCLUDED.icon, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 默认数字员工:推理分析师(显式推理循环 + 工具调用)
+INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
+VALUES (1000000003, '推理分析师', '分步思考、推理过程清晰可见,适合需要"想清楚再回答"的问题', 'react',
+ '你是一位推理分析师,善于深度推理。面对问题时,请先分步思考、清晰呈现推理过程,再调用工具或给出答案。请用中文回复,保持专业、友好的态度。',
+ NULL, 100, 1, 'pi:cpu', 'react,reasoning,tools', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, agent_type=EXCLUDED.agent_type, system_prompt=EXCLUDED.system_prompt, model_name=EXCLUDED.model_name, max_iterations=EXCLUDED.max_iterations, enabled=EXCLUDED.enabled, icon=EXCLUDED.icon, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== 本地模型 Provider(优先展示) ====================
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('ollama', 'Ollama', '', 'OpenAIChatModel', 'ollama', 'http://127.0.0.1:11434', '{"max_tokens":null}', 0, 1, 1, 1, 0, 0, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('lmstudio', 'LM Studio', '', 'OpenAIChatModel', '', 'http://localhost:1234/v1', '{"max_tokens":null}', 0, 1, 1, 1, 0, 0, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('llamacpp', 'llama.cpp (Local)', '', 'OpenAIChatModel', '', '', '{}', 0, 1, 0, 1, 0, 0, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('mlx', 'MLX (Local, Apple Silicon)', '', 'OpenAIChatModel', '', '', '{}', 0, 1, 0, 1, 0, 0, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+-- ==================== 云端模型 Provider ====================
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('dashscope', 'DashScope', 'sk-', 'DashScopeChatModel', '', '', '{}', 0, 0, 1, 1, 0, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+-- DashScope OpenAI 兼容端点:与 dashscope provider 共用同一把 sk- key,但走
+-- compatible-mode/v1 路径。带点号版本号的 qwen 系列(qwen3.5-*, qwen3.6-*)只在
+-- 这里能调通——native 端点会返回 400 InvalidParameter。
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('dashscope-compat', 'DashScope (兼容模式)', 'sk-', 'OpenAIChatModel', '', 'https://dashscope.aliyuncs.com/compatible-mode/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('modelscope', 'ModelScope', 'ms', 'OpenAIChatModel', '', 'https://api-inference.modelscope.cn/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('aliyun-codingplan', 'Aliyun Coding Plan', 'sk-sp', 'OpenAIChatModel', '', 'https://coding.dashscope.aliyuncs.com/v1', '{}', 0, 0, 0, 0, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('aliyun-codingplan-intl', 'Aliyun Coding Plan (International)', 'sk-sp', 'OpenAIChatModel', '', 'https://coding-intl.dashscope.aliyuncs.com/v1', '{}', 0, 0, 0, 0, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('bailian-team', '百炼 Token Plan', 'sk-', 'OpenAIChatModel', '', 'https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1', '{}', 0, 0, 0, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('openai', 'OpenAI', 'sk-', 'OpenAIChatModel', '', 'https://api.openai.com/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('azure-openai', 'Azure OpenAI', '', 'OpenAIChatModel', '', '', '{}', 0, 0, 0, 1, 0, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('minimax', 'MiniMax (International)', '', 'AnthropicChatModel', '', 'https://api.minimax.io/anthropic', '{}', 0, 0, 0, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('minimax-cn', 'MiniMax (China)', '', 'AnthropicChatModel', '', 'https://api.minimaxi.com/anthropic', '{}', 0, 0, 0, 0, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('kimi-cn', 'Kimi (China)', '', 'OpenAIChatModel', '', 'https://api.moonshot.cn/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('kimi-intl', 'Kimi (International)', '', 'OpenAIChatModel', '', 'https://api.moonshot.ai/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('kimi-code', 'Kimi Code', '', 'OpenAIChatModel', '', 'https://api.kimi.com/coding/v1', '{"headers":{"User-Agent":"RooCode/1.0","HTTP-Referer":"https://github.com/RooVetGit/Roo-Cline","X-Title":"Roo Code"}}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('deepseek', 'DeepSeek', 'sk-', 'OpenAIChatModel', '', 'https://api.deepseek.com', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('anthropic', 'Anthropic', 'sk-ant-', 'AnthropicChatModel', '', 'https://api.anthropic.com', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('gemini', 'Google Gemini', '', 'GeminiChatModel', '', 'https://generativelanguage.googleapis.com', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('xai', 'xAI (Grok)', 'xai-', 'OpenAIChatModel', '', 'https://api.x.ai/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('openrouter', 'OpenRouter', 'sk-or-', 'OpenAIChatModel', '', 'https://openrouter.ai/api/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('siliconflow-cn', '硅基流动 (China)', 'sk-', 'OpenAIChatModel', '', 'https://api.siliconflow.cn/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('siliconflow-intl', '硅基流动 (International)', 'sk-', 'OpenAIChatModel', '', 'https://api.siliconflow.com/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('opencode', 'OpenCode', '', 'OpenAIChatModel', '', 'https://opencode.ai/zen/v1', '{}', 0, 0, 0, 1, 1, 0, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-cn', 'Zhipu AI (China)', '', 'OpenAIChatModel', '', 'https://open.bigmodel.cn/api/paas/v4', '{"completionsPath":"/chat/completions"}', 0, 0, 0, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-intl', 'Zhipu AI (International)', '', 'OpenAIChatModel', '', 'https://api.z.ai/api/paas/v4', '{"completionsPath":"/chat/completions"}', 0, 0, 0, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('volcengine', 'Volcano Engine (火山引擎)', '', 'OpenAIChatModel', '', 'https://ark.cn-beijing.volces.com/api/v3', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, api_key=EXCLUDED.api_key, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, is_custom=EXCLUDED.is_custom, is_local=EXCLUDED.is_local, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('volcengine-plan', 'Volcano Engine Coding Plan (火山方舟代码计划)', '', 'OpenAIChatModel', '', 'https://ark.cn-beijing.volces.com/api/coding/v3', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-cn-codingplan', 'Zhipu Coding Plan (智谱编码套餐)', '', 'OpenAIChatModel', '', 'https://open.bigmodel.cn/api/coding/paas/v4', '{"completionsPath":"/chat/completions"}', 0, 0, 0, 0, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('zhipu-intl-codingplan', 'Zhipu Coding Plan (智谱编码套餐 国际版)', '', 'OpenAIChatModel', '', 'https://api.z.ai/api/coding/paas/v4', '{"completionsPath":"/chat/completions"}', 0, 0, 0, 0, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
+VALUES ('openai-chatgpt', 'OpenAI ChatGPT (OAuth)', '', 'ChatGPTChatModel', '', 'https://chatgpt.com/backend-api', '{}', 0, 0, 0, 0, 1, 0, 'oauth', NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, auth_type=EXCLUDED.auth_type, update_time=EXCLUDED.update_time;
+
+-- RFC-062:Anthropic Claude Code OAuth 订阅 provider。凭据存储在本地磁盘
+-- (macOS Keychain 或 ~/.claude/.credentials.json),不写入该行。
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
+VALUES ('anthropic-claude-code', 'Anthropic Claude Code (OAuth 订阅)', '', 'ClaudeCodeChatModel', '', 'https://api.anthropic.com', '{}', 0, 0, 0, 0, 1, 0, 'oauth', NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, auth_type=EXCLUDED.auth_type, update_time=EXCLUDED.update_time;
+
+-- ==================== 本地模型预配置(Ollama,默认禁用) ====================
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000300, 'Gemma 3', 'ollama', 'gemma3:latest', 'Google Gemma 3,轻量高效,适合本地推理', 0.7, 4096, 0.8, 1, 0, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000301, 'Qwen 3', 'ollama', 'qwen3:latest', '通义千问 3,中文能力出色', 0.7, 4096, 0.8, 1, 0, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000302, 'Llama 3.1', 'ollama', 'llama3.1:latest', 'Meta Llama 3.1,通用能力强', 0.7, 4096, 0.8, 1, 0, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000303, 'DeepSeek R1', 'ollama', 'deepseek-r1:latest', 'DeepSeek R1 推理模型', 0.7, 4096, 0.8, 1, 0, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000304, 'Mistral', 'ollama', 'mistral:latest', 'Mistral 7B,高效推理', 0.7, 4096, 0.8, 1, 0, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000305, 'Gemma 4', 'ollama', 'gemma4:latest', 'Google Gemma 4,新一代高性能本地模型', 0.7, 4096, 0.8, 1, 0, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time;
+
+-- ==================== 云端默认模型配置 ====================
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000001, 'Qwen Plus', 'dashscope', 'qwen-plus', '默认均衡模型,适合日常问答与工具调用。', 0.7, 4096, 0.8, 1, 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000002, 'Qwen Max', 'dashscope', 'qwen-max', '更强推理能力,适合复杂任务。', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000003, 'Qwen Turbo', 'dashscope', 'qwen-turbo', '低延迟模型,适合高频交互。', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000004, 'Qwen Coder Plus', 'dashscope', 'qwen-coder-plus', '代码生成与解释场景优先。', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+(1000000101, 'Qwen3 Max', 'dashscope', 'qwen3-max', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000102, 'Qwen3 235B A22B Thinking', 'dashscope', 'qwen3-235b-a22b-thinking-2507', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000103, 'DeepSeek-V3.2', 'dashscope', 'deepseek-v3.2', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+-- 注意: qwen3-plus / qwen3.5-plus / qwen3.5-max / qwen3.6-* 等带点号的版本只在 OpenAI 兼容端点上线。
+-- DashScope native(text-generation/generation)调用会返回 400 InvalidParameter。
+-- 这些模型挂在 dashscope-compat provider 下,复用同一把 sk- key 但走 compatible-mode/v1 端点。
+(1000000173, 'Qwen Long', 'dashscope', 'qwen-long', '长文本模型,支持超长上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000174, 'Qwen Plus (latest)', 'dashscope', 'qwen-plus-latest', '通义千问 Plus 最新稳定快照,自动跟随官方更新', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000175, 'Qwen Max (latest)', 'dashscope', 'qwen-max-latest', '通义千问 Max 最新稳定快照,最强推理能力', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000176, 'Qwen Turbo (latest)', 'dashscope', 'qwen-turbo-latest', '通义千问 Turbo 最新稳定快照,低延迟、高并发', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+-- DashScope 兼容模式专属模型(点号版本号系列)—— 与 dashscope provider 共用同一把 sk- key。
+-- 仅收录在通用账号上确实可调通的 -plus 版本;-max / -vl-max 在 model market 可见但 API 返回 404。
+(1000000601, 'Qwen3.6 Plus', 'dashscope-compat', 'qwen3.6-plus', '通义千问 3.6 Plus 旗舰,平衡推理与速度(兼容模式专属)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000603, 'Qwen3.5 Plus', 'dashscope-compat', 'qwen3.5-plus', '通义千问 3.5 Plus(兼容模式专属)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000605, 'Qwen3 VL Plus', 'dashscope-compat', 'qwen3-vl-plus', '通义千问 3 视觉理解 Plus,支持图像、视频输入(兼容模式专属)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000104, 'Qwen3.5-122B-A10B', 'modelscope', 'Qwen/Qwen3.5-122B-A10B', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000105, 'GLM-5', 'modelscope', 'ZhipuAI/GLM-5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000106, 'Qwen3.5 Plus', 'aliyun-codingplan', 'qwen3.5-plus', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000107, 'GLM-5', 'aliyun-codingplan', 'glm-5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000108, 'GLM-4.7', 'aliyun-codingplan', 'glm-4.7', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000109, 'MiniMax M2.5', 'aliyun-codingplan', 'MiniMax-M2.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000110, 'Kimi K2.5', 'aliyun-codingplan', 'kimi-k2.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000111, 'Qwen3 Max 2026-01-23', 'aliyun-codingplan', 'qwen3-max-2026-01-23', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000112, 'Qwen3 Coder Next', 'aliyun-codingplan', 'qwen3-coder-next', '', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000113, 'Qwen3 Coder Plus', 'aliyun-codingplan', 'qwen3-coder-plus', '', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000162, 'Qwen3.6 Plus', 'aliyun-codingplan', 'qwen3.6-plus', '阿里云编码套餐 — Qwen3.6 Plus 旗舰', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000241, 'Qwen3.6 Plus', 'aliyun-codingplan-intl', 'qwen3.6-plus', '阿里云编码套餐(国际版) — Qwen3.6 Plus 旗舰', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000242, 'Qwen3.5 Plus', 'aliyun-codingplan-intl', 'qwen3.5-plus', '阿里云编码套餐(国际版) — Qwen3.5 均衡旗舰', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000243, 'GLM-5', 'aliyun-codingplan-intl', 'glm-5', '阿里云编码套餐(国际版) — GLM-5 由 DashScope 托管', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000244, 'GLM-4.7', 'aliyun-codingplan-intl', 'glm-4.7', '阿里云编码套餐(国际版) — GLM-4.7 由 DashScope 托管', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000245, 'MiniMax M2.5', 'aliyun-codingplan-intl', 'MiniMax-M2.5', '阿里云编码套餐(国际版) — MiniMax M2.5 由 DashScope 托管', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000246, 'Kimi K2.5', 'aliyun-codingplan-intl', 'kimi-k2.5', '阿里云编码套餐(国际版) — Kimi K2.5 由 DashScope 托管', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000247, 'Qwen3 Max 2026-01-23', 'aliyun-codingplan-intl', 'qwen3-max-2026-01-23', '阿里云编码套餐(国际版) — Qwen3 Max 锁定快照', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000248, 'Qwen3 Coder Next', 'aliyun-codingplan-intl', 'qwen3-coder-next', '阿里云编码套餐(国际版) — Qwen3 Coder Next 智能体编码', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000249, 'Qwen3 Coder Plus', 'aliyun-codingplan-intl', 'qwen3-coder-plus', '阿里云编码套餐(国际版) — Qwen3 Coder Plus 智能体编码', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000400, 'Qwen 3.6 Plus', 'bailian-team', 'qwen3.6-plus', '百炼团队套餐 — 千问旗舰推理模型,支持视觉理解与文本生成', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000401, 'DeepSeek V3.2', 'bailian-team', 'deepseek-v3.2', '百炼团队套餐 — DeepSeek 最新推理模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000402, 'GLM-5', 'bailian-team', 'glm-5', '百炼团队套餐 — 智谱 GLM-5 文本生成模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000403, 'Qwen Image 2.0', 'bailian-team', 'qwen-image-2.0', '百炼团队套餐 — 千问图片生成模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000404, 'Qwen Image 2.0 Pro', 'bailian-team', 'qwen-image-2.0-pro', '百炼团队套餐 — 千问图片生成旗舰模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000405, 'Wan 2.7 Image', 'bailian-team', 'wan2.7-image', '百炼团队套餐 — 万相图片生成模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000406, 'Wan 2.7 Image Pro', 'bailian-team', 'wan2.7-image-pro', '百炼团队套餐 — 万相图片生成旗舰模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000407, 'Qwen 3.5 Plus', 'bailian-team', 'qwen3.5-plus', '百炼团队套餐 — Qwen3.5 均衡旗舰,混合思考,128K 上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000408, 'Qwen 3.5 Flash', 'bailian-team', 'qwen3.5-flash', '百炼团队套餐 — Qwen3.5 快速版,低延迟、高并发', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000409, 'Qwen3 VL Plus', 'bailian-team', 'qwen3-vl-plus', '百炼团队套餐 — Qwen3 视觉旗舰,支持图像与视频理解', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000410, 'Qwen3 VL Flash', 'bailian-team', 'qwen3-vl-flash', '百炼团队套餐 — Qwen3 视觉快速版,高吞吐视觉调用', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000411, 'Qwen3 Coder Plus', 'bailian-team', 'qwen3-coder-plus', '百炼团队套餐 — Qwen3 编码旗舰,智能体代码编辑与工具调用', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000412, 'Qwen 3.6 Plus 2026-04-02', 'bailian-team', 'qwen3.6-plus-2026-04-02', '百炼团队套餐 — Qwen 3.6 Plus 2026-04-02 锁定快照', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000413, 'Qwen 3.6 Max (preview)', 'bailian-team', 'qwen3.6-max-preview', '百炼团队套餐 — Qwen3.6 Max 预览版,3.6 系列最强推理', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000414, 'Qwen 3.6 Flash', 'bailian-team', 'qwen3.6-flash', '百炼团队套餐 — Qwen3.6 快速版,混合思考默认开启', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000415, 'Qwen 3.6 Flash 2026-04-16','bailian-team', 'qwen3.6-flash-2026-04-16','百炼团队套餐 — Qwen 3.6 Flash 2026-04-16 锁定快照', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000416, 'Qwen 3.5 Omni Plus', 'bailian-team', 'qwen3.5-omni-plus', '百炼团队套餐 — Qwen3.5 全模态版,文本/视觉/音频输入输出', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000114, 'GPT-5.2', 'openai', 'gpt-5.2', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000115, 'GPT-5', 'openai', 'gpt-5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000116, 'GPT-5 Mini', 'openai', 'gpt-5-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000117, 'GPT-5 Nano', 'openai', 'gpt-5-nano', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000118, 'GPT-4.1', 'openai', 'gpt-4.1', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000119, 'GPT-4.1 Mini', 'openai', 'gpt-4.1-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000120, 'GPT-4.1 Nano', 'openai', 'gpt-4.1-nano', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000121, 'o3', 'openai', 'o3', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000122, 'o4-mini', 'openai', 'o4-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000123, 'GPT-4o', 'openai', 'gpt-4o', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000124, 'GPT-4o Mini', 'openai', 'gpt-4o-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000125, 'GPT-5 Chat', 'azure-openai', 'gpt-5-chat', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000126, 'GPT-5 Mini', 'azure-openai', 'gpt-5-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000127, 'GPT-5 Nano', 'azure-openai', 'gpt-5-nano', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000128, 'GPT-4.1', 'azure-openai', 'gpt-4.1', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000129, 'GPT-4.1 Mini', 'azure-openai', 'gpt-4.1-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000130, 'GPT-4.1 Nano', 'azure-openai', 'gpt-4.1-nano', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000131, 'GPT-4o', 'azure-openai', 'gpt-4o', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000132, 'GPT-4o Mini', 'azure-openai', 'gpt-4o-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000133, 'MiniMax M2.5', 'minimax', 'MiniMax-M2.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000134, 'MiniMax M2.5 Highspeed', 'minimax', 'MiniMax-M2.5-highspeed', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000135, 'MiniMax M2.7', 'minimax', 'MiniMax-M2.7', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000136, 'MiniMax M2.7 Highspeed', 'minimax', 'MiniMax-M2.7-highspeed', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000137, 'MiniMax M2.5', 'minimax-cn', 'MiniMax-M2.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000138, 'MiniMax M2.5 Highspeed', 'minimax-cn', 'MiniMax-M2.5-highspeed', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000139, 'MiniMax M2.7', 'minimax-cn', 'MiniMax-M2.7', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000140, 'MiniMax M2.7 Highspeed', 'minimax-cn', 'MiniMax-M2.7-highspeed', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000141, 'Kimi K2.5', 'kimi-cn', 'kimi-k2.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000142, 'Kimi K2 0905 Preview', 'kimi-cn', 'kimi-k2-0905-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000143, 'Kimi K2 0711 Preview', 'kimi-cn', 'kimi-k2-0711-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000144, 'Kimi K2 Turbo Preview', 'kimi-cn', 'kimi-k2-turbo-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000145, 'Kimi K2 Thinking', 'kimi-cn', 'kimi-k2-thinking', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000146, 'Kimi K2 Thinking Turbo', 'kimi-cn', 'kimi-k2-thinking-turbo', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000147, 'Kimi K2.5', 'kimi-intl', 'kimi-k2.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000148, 'Kimi K2 0905 Preview', 'kimi-intl', 'kimi-k2-0905-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000149, 'Kimi K2 0711 Preview', 'kimi-intl', 'kimi-k2-0711-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000150, 'Kimi K2 Turbo Preview', 'kimi-intl', 'kimi-k2-turbo-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000151, 'Kimi K2 Thinking', 'kimi-intl', 'kimi-k2-thinking', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000152, 'Kimi K2 Thinking Turbo', 'kimi-intl', 'kimi-k2-thinking-turbo', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000153, 'DeepSeek Chat', 'deepseek', 'deepseek-chat', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000154, 'DeepSeek Reasoner', 'deepseek', 'deepseek-reasoner', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+-- DeepSeek V4(1M 上下文,原生 thinking 模式由 DeepSeekV4ThinkingDecorator 注入)
+(1000000282, 'DeepSeek V4 Flash', 'deepseek', 'deepseek-v4-flash', 'DeepSeek V4 Flash(1M 上下文,thinking 模式开启时支持 reasoning_effort)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000283, 'DeepSeek V4 Pro', 'deepseek', 'deepseek-v4-pro', 'DeepSeek V4 Pro(1M 上下文,thinking 模式开启时支持 reasoning_effort)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000155, 'Gemini 3.1 Pro Preview', 'gemini', 'gemini-3.1-pro-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000156, 'Gemini 3 Flash Preview', 'gemini', 'gemini-3-flash-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000157, 'Gemini 3.1 Flash Lite Preview', 'gemini', 'gemini-3.1-flash-lite-preview', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000158, 'Gemini 2.5 Pro', 'gemini', 'gemini-2.5-pro', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000159, 'Gemini 2.5 Flash', 'gemini', 'gemini-2.5-flash', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000160, 'Gemini 2.5 Flash Lite', 'gemini', 'gemini-2.5-flash-lite', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000161, 'Gemini 2.0 Flash', 'gemini', 'gemini-2.0-flash', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000340, 'Grok 4', 'xai', 'grok-4', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000341, 'Grok 4 Fast', 'xai', 'grok-4-fast', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000342, 'Grok 3', 'xai', 'grok-3', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000343, 'Grok 3 Mini', 'xai', 'grok-3-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000200, 'GPT-5', 'openrouter', 'openai/gpt-5', 'OpenRouter 代理 GPT-5', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000201, 'Claude Opus 4.6', 'openrouter', 'anthropic/claude-opus-4-6', 'OpenRouter 代理 Claude Opus 4.6', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000202, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'OpenRouter 代理 Claude Sonnet 4.6', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000203, 'Gemini 2.5 Pro', 'openrouter', 'google/gemini-2.5-pro', 'OpenRouter 代理 Gemini 2.5 Pro', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000204, 'Llama 4 Maverick', 'openrouter', 'meta-llama/llama-4-maverick', 'OpenRouter 代理 Llama 4 Maverick', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000205, 'DeepSeek R1', 'openrouter', 'deepseek/deepseek-r1', 'OpenRouter 代理 DeepSeek R1', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000206, 'Qwen3.6 Plus (free)', 'openrouter', 'qwen/qwen3.6-plus:free', 'OpenRouter 免费 Qwen3.6 Plus(支持视觉)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000207, 'Gemini 2.5 Flash (free)', 'openrouter', 'google/gemini-2.5-flash:free', 'OpenRouter 免费 Gemini 2.5 Flash(支持视觉)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000208, 'Llama 4 Maverick (free)', 'openrouter', 'meta-llama/llama-4-maverick:free', 'OpenRouter 免费 Llama 4 Maverick(支持视觉)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000500, 'DeepSeek V3', 'siliconflow-cn', 'deepseek-ai/DeepSeek-V3', '硅基流动 — DeepSeek V3,综合能力强,有免费额度', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000501, 'DeepSeek R1', 'siliconflow-cn', 'deepseek-ai/DeepSeek-R1', '硅基流动 — DeepSeek R1 推理模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000502, 'Qwen3 235B A22B', 'siliconflow-cn', 'Qwen/Qwen3-235B-A22B', '硅基流动 — 千问3旗舰 MoE 模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000503, 'Qwen3 30B A3B', 'siliconflow-cn', 'Qwen/Qwen3-30B-A3B', '硅基流动 — 千问3高性价比 MoE 模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000504, 'GLM-4 9B Chat', 'siliconflow-cn', 'THUDM/glm-4-9b-chat', '硅基流动 — 智谱 GLM-4 9B,免费可用', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000505, 'DeepSeek V3 Pro', 'siliconflow-cn', 'Pro/deepseek-ai/DeepSeek-V3', '硅基流动 Pro — DeepSeek V3 优先调度版', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000506, 'DeepSeek R1 Pro', 'siliconflow-cn', 'Pro/deepseek-ai/DeepSeek-R1', '硅基流动 Pro — DeepSeek R1 推理优先调度版', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000510, 'DeepSeek V3', 'siliconflow-intl', 'deepseek-ai/DeepSeek-V3', 'SiliconFlow INTL — DeepSeek V3', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000511, 'DeepSeek R1', 'siliconflow-intl', 'deepseek-ai/DeepSeek-R1', 'SiliconFlow INTL — DeepSeek R1 reasoning model', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000512, 'Qwen3 235B A22B', 'siliconflow-intl', 'Qwen/Qwen3-235B-A22B', 'SiliconFlow INTL — Qwen3 flagship MoE model', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000513, 'Qwen3 30B A3B', 'siliconflow-intl', 'Qwen/Qwen3-30B-A3B', 'SiliconFlow INTL — Qwen3 efficient MoE model', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000514, 'GLM-4 9B Chat', 'siliconflow-intl', 'THUDM/glm-4-9b-chat', 'SiliconFlow INTL — Zhipu GLM-4 9B, free tier', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000515, 'DeepSeek V3 Pro', 'siliconflow-intl', 'Pro/deepseek-ai/DeepSeek-V3', 'SiliconFlow INTL Pro — DeepSeek V3 priority tier', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000516, 'DeepSeek R1 Pro', 'siliconflow-intl', 'Pro/deepseek-ai/DeepSeek-R1', 'SiliconFlow INTL Pro — DeepSeek R1 priority tier', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000520, 'Big Pickle', 'opencode', 'big-pickle', 'OpenCode 免费模型 — Big Pickle', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000521, 'Nemotron 3 Super Free', 'opencode', 'nemotron-3-super-free', 'OpenCode 免费模型 — Nemotron 3 Super', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000210, 'GLM-5-Turbo', 'zhipu-cn', 'glm-5-turbo', '高速推理模型(推荐)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000211, 'GLM-5V-Turbo', 'zhipu-cn', 'glm-5v-turbo', '多模态视觉模型(推荐)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000212, 'GLM-5', 'zhipu-cn', 'glm-5', '旗舰模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000213, 'GLM-5.1', 'zhipu-cn', 'glm-5.1', '最新旗舰模型', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000220, 'GLM-5-Turbo', 'zhipu-intl', 'glm-5-turbo', '高速推理模型(国际版,推荐)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000221, 'GLM-5V-Turbo', 'zhipu-intl', 'glm-5v-turbo', '多模态视觉模型(国际版,推荐)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000222, 'GLM-5', 'zhipu-intl', 'glm-5', '旗舰模型(国际版)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000223, 'GLM-5.1', 'zhipu-intl', 'glm-5.1', '最新旗舰模型(国际版)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000230, 'GLM-5 Coding', 'zhipu-cn-codingplan', 'glm-5', '智谱编码套餐 — GLM-5 旗舰', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000231, 'GLM-5.1 Coding', 'zhipu-cn-codingplan', 'glm-5.1', '智谱编码套餐 — GLM-5.1 最新旗舰', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000232, 'GLM-5-Turbo Coding', 'zhipu-cn-codingplan', 'glm-5-turbo', '智谱编码套餐 — GLM-5 高速版', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000233, 'GLM-4.7 Coding', 'zhipu-cn-codingplan', 'glm-4.7', '智谱编码套餐 — GLM-4.7', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000234, 'GLM-5 Coding', 'zhipu-intl-codingplan', 'glm-5', 'Zhipu Coding Plan — GLM-5 旗舰(国际版)', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000235, 'GLM-5.1 Coding', 'zhipu-intl-codingplan', 'glm-5.1', 'Zhipu Coding Plan — GLM-5.1 最新旗舰(国际版)', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000236, 'GLM-5-Turbo Coding', 'zhipu-intl-codingplan', 'glm-5-turbo', 'Zhipu Coding Plan — GLM-5 高速版(国际版)', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000237, 'GLM-4.7 Coding', 'zhipu-intl-codingplan', 'glm-4.7', 'Zhipu Coding Plan — GLM-4.7(国际版)', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000310, 'Doubao Seed 1.8', 'volcengine', 'doubao-seed-1-8-251228', '豆包旗舰多模态模型,文本+图像,256K 上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000311, 'Doubao Seed Code Preview', 'volcengine', 'doubao-seed-code-preview-251028', '豆包代码预览模型,文本+图像,256K 上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000312, 'Kimi K2.5', 'volcengine', 'kimi-k2-5-260127', 'Kimi K2.5(火山方舟托管),文本+图像,256K 上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000313, 'GLM 4.7', 'volcengine', 'glm-4-7-251222', 'GLM 4.7(火山方舟托管),文本+图像,200K 上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000314, 'DeepSeek V3.2', 'volcengine', 'deepseek-v3-2-251201', 'DeepSeek V3.2(火山方舟托管),文本+图像,128K 上下文', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000320, 'Ark Coding Plan', 'volcengine-plan', 'ark-code-latest', '方舟代码计划旗舰模型,256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000321, 'Doubao Seed Code', 'volcengine-plan', 'doubao-seed-code', '豆包代码模型,256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000322, 'Doubao Seed Code Preview', 'volcengine-plan', 'doubao-seed-code-preview-251028', '豆包代码预览模型,256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000323, 'GLM 4.7 Coding', 'volcengine-plan', 'glm-4.7', 'GLM 4.7 编码版(火山方舟托管),200K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000324, 'Kimi K2 Thinking', 'volcengine-plan', 'kimi-k2-thinking', 'Kimi K2 推理版(火山方舟托管),256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000325, 'Kimi K2.5 Coding', 'volcengine-plan', 'kimi-k2.5', 'Kimi K2.5 编码版(火山方舟托管),256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000240, 'Kimi for Coding', 'kimi-code', 'kimi-for-coding', 'Kimi Code 专用编码模型', 0.2, 32768, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000250, 'GPT-5.4', 'openai-chatgpt', 'gpt-5.4', 'ChatGPT Plus/Pro 会员模型(OAuth 登录)', NULL, 128000, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000251, 'GPT-5.4 Mini', 'openai-chatgpt', 'gpt-5.4-mini', 'ChatGPT 会员轻量模型', NULL, 128000, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- GPT-5.5 系列(OpenAI / Azure / OpenRouter)
+(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000262, 'GPT-5.5 Nano', 'openai', 'gpt-5.5-nano', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000263, 'GPT-5.5', 'azure-openai', 'gpt-5.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000264, 'GPT-5.5 Mini', 'azure-openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000265, 'GPT-5.5', 'openrouter', 'openai/gpt-5.5', 'OpenRouter 代理 GPT-5.5', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+-- Claude 4.7 系列(直连 Anthropic + OpenRouter)
+-- 注意:Claude 4.7 禁止 temperature / top_p / top_k 参数,已在 AgentAnthropicChatModelBuilder 中适配
+(1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7(xhigh 自适应思考)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- Anthropic 仅发布了 Opus 4.7,Sonnet 暂时仍是 4.6
+(1000000271, 'Claude Sonnet 4.6', 'anthropic', 'claude-sonnet-4-6', 'Anthropic 最新 Sonnet (Sonnet 4.7 暂未发布)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'OpenRouter 代理 Claude Opus 4.7', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000273, 'Claude Sonnet 4.6', 'openrouter', 'anthropic/claude-sonnet-4-6', 'OpenRouter 代理 Claude Sonnet 4.6', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- RFC-062:通过 Claude Code Pro/Max 订阅调用 Claude 4.7
+(1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', '通过 Claude Code Pro/Max 订阅调用 Claude Opus 4.7', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000281, 'Claude Sonnet 4.6', 'anthropic-claude-code', 'claude-sonnet-4-6', '通过 Claude Code Pro/Max 订阅调用 Claude Sonnet 4.6', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- Claude 4.8 系列(直连 Anthropic + OpenRouter,包含 -fast 高速变体)
+-- 与 4.7 共享严格采样契约:temperature / top_p / top_k 必须为空,新增 xhigh 思考档位
+(1000000290, 'Claude Opus 4.8', 'anthropic', 'claude-opus-4-8', 'Anthropic Claude Opus 4.8(xhigh 自适应思考)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000291, 'Claude Opus 4.8 Fast', 'anthropic', 'claude-opus-4-8-fast', 'Claude Opus 4.8 高速变体(输出更快、单价 2x)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000292, 'Claude Opus 4.8', 'openrouter', 'anthropic/claude-opus-4-8', 'OpenRouter 代理 Claude Opus 4.8', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000293, 'Claude Opus 4.8 Fast', 'openrouter', 'anthropic/claude-opus-4-8-fast', 'OpenRouter 代理 Claude Opus 4.8 高速变体', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000294, 'Claude Opus 4.8', 'anthropic-claude-code', 'claude-opus-4-8', '通过 Claude Code Pro/Max 订阅调用 Claude Opus 4.8', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, provider=EXCLUDED.provider, model_name=EXCLUDED.model_name, description=EXCLUDED.description, temperature=EXCLUDED.temperature, max_tokens=EXCLUDED.max_tokens, top_p=EXCLUDED.top_p, builtin=EXCLUDED.builtin, enabled=EXCLUDED.enabled, is_default=EXCLUDED.is_default, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 默认系统设置
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000001, 'language', 'zh-CN', '当前界面语言', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000002, 'streamEnabled', 'true', '是否开启流式响应', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000003, 'debugMode', 'false', '是否开启调试模式', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000004, 'stateGraphEnabled', 'true', '启用 StateGraph 架构的 ReAct Agent', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+-- 搜索服务配置
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000005, 'searchEnabled', 'true', '是否启用搜索功能', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000006, 'searchProvider', 'serper', '搜索服务提供商', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000007, 'searchFallbackEnabled', 'false', '搜索失败时是否回退到备用提供商', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000008, 'serperApiKey', '', 'Serper API Key', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000009, 'serperBaseUrl', 'https://google.serper.dev/search', 'Serper 接口地址', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000010, 'tavilyApiKey', '', 'Tavily API Key', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000011, 'tavilyBaseUrl', 'https://api.tavily.com/search', 'Tavily 接口地址', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000012, 'duckduckgoEnabled', 'true', 'DuckDuckGo 免 Key 搜索兜底(零配置可用)', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000000013, 'searxngBaseUrl', '', 'SearXNG 实例地址(Docker 部署时自动配置)', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key=EXCLUDED.setting_key, setting_value=EXCLUDED.setting_value, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
+
+-- 语音识别(STT)默认配置 —— 默认启用,用户只需在模型管理中配置 OpenAI / DashScope API Key 即可使用
+-- 用 setting_key 的 skip-if-exists 写法(FROM DUAL ... WHERE NOT EXISTS),
+-- 既不强行覆盖用户显式设过的值,也不会撞 setting_key UNIQUE 索引。
+-- V46 迁移走的是同一套语义。
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000020, 'sttEnabled', 'true', '启用语音识别(TalkMode 麦克风输入)', NOW(), NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttEnabled');
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000021, 'sttProvider', 'auto', 'STT 提供商:auto / openai / dashscope', NOW(), NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttProvider');
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000022, 'sttFallbackEnabled', 'true', '主 provider 失败时自动尝试备选 provider', NOW(), NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttFallbackEnabled');
+
+-- 内置工具:日期时间
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000001, 'DateTimeTool', '日期时间', '获取当前日期和时间信息', 'builtin', 'dateTimeTool', '🕐', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:网络搜索
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000002, 'WebSearchTool', '网络搜索', '在互联网上搜索实时信息', 'builtin', 'webSearchTool', '🔍', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:本地命令执行(默认启用,危险操作由 ToolGuard 审批控制)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000003, 'ShellExecuteTool', '命令执行', '在本地服务器上执行 Shell 命令。用于执行系统命令、查看文件、运行脚本等操作。危险操作会触发审批确认。', 'builtin', 'shellExecuteTool', '🖥', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:读取文件
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000004, 'ReadFileTool', '读取文件', '读取指定文件的内容,支持按行范围读取,自动截断超大输出。', 'builtin', 'readFileTool', '📖', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:写入文件(默认启用,危险操作由 ToolGuard 审批控制)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000005, 'WriteFileTool', '写入文件', '将内容写入指定文件。如果文件已存在则完全覆写,不存在则创建新文件。每次执行需要用户审批确认。', 'builtin', 'writeFileTool', '📝', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:编辑文件(默认启用,危险操作由 ToolGuard 审批控制)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000006, 'EditFileTool', '编辑文件', '通过查找替换编辑文件内容,精确匹配 old_text 并替换为 new_text。每次执行需要用户审批确认。', 'builtin', 'editFileTool', '✏️', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:技能文件读取(Skill Runtime Tool)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000007, 'SkillFileTool', '技能文件读取', '读取技能包内的文件(SKILL.md/references/scripts),列出技能文件目录树。支持 read_skill_file 和 list_skill_files 两个工具。', 'builtin', 'skillFileTool', '📖', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:技能脚本执行(Skill Runtime Tool)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000008, 'SkillScriptTool', '技能脚本执行', '执行技能包 scripts/ 目录下的脚本(Python/Bash/Node),路径严格限制在技能目录内。', 'builtin', 'skillScriptTool', '⚡', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:文件类型检测
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000009, 'FileTypeDetectorTool', '文件类型检测', '检测文件的 MIME 类型和类别,区分文本文件和 PDF/Office 文档,帮助选择合适的读取工具。', 'builtin', 'fileTypeDetectorTool', '🔍', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:文档文本提取
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000010, 'DocumentExtractTool', '文档文本提取', '从 PDF、Word、Excel、PowerPoint 等 Office 文档中提取纯文本内容。支持 fallback 链:系统命令优先,Java 实现兜底。', 'builtin', 'documentExtractTool', '📄', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:数据库工作区记忆读写
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000011, 'WorkspaceMemoryTool', '工作区记忆', '读写数据库中的工作区 Markdown 文档,用于维护 PROFILE.md、MEMORY.md 和 memory/YYYY-MM-DD.md 等持久记忆。', 'builtin', 'workspaceMemoryTool', '🧠', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:浏览器控制(Playwright)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000012, 'BrowserUseTool', '浏览器控制', '启动和控制浏览器,支持打开网页、截图、点击、输入、执行JS等自动化操作。配合 browser_visible / browser_cdp 技能使用。', 'builtin', 'browserUseTool', '🌐', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:MateClaw 项目文档读取
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000013, 'MateClawDocTool', 'MateClaw 文档', '读取 MateClaw 内置项目文档。action=list 列出所有文档,action=read 读取指定文档内容(如 zh/config.md)。', 'builtin', 'mateClawDocTool', '📚', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:Agent 委派(多 Agent 协作)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000014, 'DelegateAgentTool', 'Agent 委派', '委派任务给其他 Agent 执行,实现多 Agent 协作。支持按名称调用目标 Agent,在独立会话中运行并返回结果。', 'builtin', 'delegateAgentTool', '🤝', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000015, 'VideoGenerateTool', '视频生成', '使用 AI 生成视频,支持文字生成视频和图片生成视频两种模式。视频生成是异步过程,完成后自动显示在对话中。', 'builtin', 'videoGenerateTool', '🎬', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000016, 'ImageGenerateTool', '图片生成', '使用 AI 生成图片,支持文字生成图片。支持 DashScope 通义万相、OpenAI DALL-E、fal.ai Flux、智谱 CogView 等多个 Provider,自动回退。', 'builtin', 'imageGenerateTool', '🎨', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000017, 'WikiTool', 'Wiki 知识库', '读取、搜索 Wiki 知识库中的结构化页面,并追溯原始来源文件。支持 wiki_read_page、wiki_list_pages、wiki_search_pages、wiki_trace_source 四个工具。', 'builtin', 'wikiTool', '📚', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:定时任务管理
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000018, 'CronJobTool', '定时任务', '通过对话创建、查看、启停和删除定时任务。支持 5 字段 cron 表达式,灵活设定执行时间。', 'builtin', 'cronJobTool', '⏰', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:DOCX 渲染(RFC-045 — 进程内 Apache POI,毫秒级新建 .docx)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000019, 'DocxRenderTool', 'DOCX 渲染', '将 Markdown 直接渲染为 .docx 并返回一次性下载链接。进程内 Apache POI 实现,无需 Node.js 子进程;支持标题、加粗、列表、表格。新建文档场景的首选工具。', 'builtin', 'docxRenderTool', '📝', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:XLSX 渲染(进程内 Apache POI,从 Markdown 表格生成多 sheet 工作簿)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000020, 'XlsxRenderTool', 'XLSX 渲染', '将 Markdown 直接渲染为 .xlsx 工作簿并返回一次性下载链接。进程内 Apache POI 实现;每个 # 一级标题生成一个 sheet,竖线表格成为行内容,数字单元格自动识别。', 'builtin', 'xlsxRenderTool', '📊', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:PPTX 渲染(进程内 Apache POI,Marp 风格 Markdown 生成 .pptx)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000021, 'PptxRenderTool', 'PPTX 渲染', '将 Marp 风格的 Markdown 直接渲染为 .pptx 演示文稿并返回一次性下载链接。进程内 Apache POI 实现;--- 分页、# / ## 作幻灯片标题、- 作要点、 作演讲者备注。', 'builtin', 'pptxRenderTool', '🎞️', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置工具:PDF 渲染(双 backend:LibreOffice 子进程优先,进程内 OpenPDF + Flying Saucer 兜底)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000022, 'PdfRenderTool', 'PDF 渲染', '将 Markdown 渲染为最终交付形态的 .pdf 并返回一次性下载链接。双 backend 自动切换(优先 LibreOffice,不可用时回落到进程内 OpenPDF + Flying Saucer);通过 YAML frontmatter 控制封面、页眉、页脚。', 'builtin', 'pdfRenderTool', '📄', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, tool_type=EXCLUDED.tool_type, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 示例 MCP Server:Filesystem(参考 MateClaw 文档中的 mcpServers.filesystem)
+INSERT INTO mate_mcp_server (
+ id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
+ enabled, connect_timeout_seconds, read_timeout_seconds, last_status, last_error,
+ last_connected_time, tool_count, builtin, create_time, update_time, deleted
+)
+VALUES (
+ 1000000901,
+ 'filesystem',
+ 'Filesystem MCP for MateClaw workspace',
+ 'stdio',
+ NULL,
+ NULL,
+ 'npx',
+ '["-y","@modelcontextprotocol/server-filesystem","${user.home}"]',
+ '{}',
+ NULL,
+ 0,
+ 30,
+ 60,
+ 'disconnected',
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NOW(),
+ NOW(),
+ 0
+)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, transport=EXCLUDED.transport, url=EXCLUDED.url, headers_json=EXCLUDED.headers_json, command=EXCLUDED.command, args_json=EXCLUDED.args_json, env_json=EXCLUDED.env_json, cwd=EXCLUDED.cwd, enabled=EXCLUDED.enabled, connect_timeout_seconds=EXCLUDED.connect_timeout_seconds, read_timeout_seconds=EXCLUDED.read_timeout_seconds, last_status=EXCLUDED.last_status, last_error=EXCLUDED.last_error, last_connected_time=EXCLUDED.last_connected_time, tool_count=EXCLUDED.tool_count, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 预置 MCP Server:GitHub(需配置 GITHUB_TOKEN 环境变量后启用)
+INSERT INTO mate_mcp_server (
+ id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
+ enabled, connect_timeout_seconds, read_timeout_seconds, last_status, last_error,
+ last_connected_time, tool_count, builtin, create_time, update_time, deleted
+)
+VALUES (
+ 1000000902,
+ 'github',
+ 'GitHub MCP Server — 搜索仓库/代码/Issues,管理 PR 和文件',
+ 'stdio',
+ NULL,
+ NULL,
+ 'npx',
+ '["-y","@modelcontextprotocol/server-github"]',
+ '{"GITHUB_PERSONAL_ACCESS_TOKEN":""}',
+ NULL,
+ 0,
+ 30,
+ 60,
+ 'disconnected',
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NOW(),
+ NOW(),
+ 0
+)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, transport=EXCLUDED.transport, url=EXCLUDED.url, headers_json=EXCLUDED.headers_json, command=EXCLUDED.command, args_json=EXCLUDED.args_json, env_json=EXCLUDED.env_json, cwd=EXCLUDED.cwd, enabled=EXCLUDED.enabled, connect_timeout_seconds=EXCLUDED.connect_timeout_seconds, read_timeout_seconds=EXCLUDED.read_timeout_seconds, last_status=EXCLUDED.last_status, last_error=EXCLUDED.last_error, last_connected_time=EXCLUDED.last_connected_time, tool_count=EXCLUDED.tool_count, builtin=EXCLUDED.builtin, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- 内置技能:从 MateClaw 迁移的技能元数据
+-- DEPRECATED (RFC-044 §4.2): The authoritative source for builtin skills is now
+-- classpath:skills//SKILL.md, upserted on startup by BuiltinSkillSeedService.
+-- These INSERT/UPDATE blocks remain as a one-version compatibility shim and will
+-- be removed in the next release. New skills should NOT be added here — drop a
+-- SKILL.md under skills// and the seed service will register it.
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000001, 'cron', '定时任务管理。通过命令或控制台创建、查询、暂停、恢复、删除任务,按时间表执行并把结果发到频道。', 'builtin', '⏰', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'cron,schedule,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000002, 'file_reader', '读取与摘要文本类文件,如 txt、md、json、csv、log、代码文件等。PDF 与 Office 文件由专用技能处理。', 'builtin', '📄', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'file,reader,text,summary', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000003, 'dingtalk_channel_connect', '辅助完成钉钉频道接入流程,支持可视浏览器、登录暂停和发布前检查。', 'builtin', '🤖', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'dingtalk,channel,browser,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000004, 'himalaya', '通过 CLI 管理邮件,支持多账户 IMAP/SMTP、搜索、阅读、回复和附件处理。', 'builtin', '📧', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md","homepage":"https://github.com/pimalaya/himalaya"}', 1, 1, 'email,imap,smtp,cli', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000005, 'news', '从互联网查询最新新闻。支持政治、财经、社会、国际、科技、体育、娱乐等分类。自动适配内置搜索和工具搜索。', 'builtin', '📰', '2.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'news,web,search,summary', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000006, 'pdf', 'PDF 相关操作:阅读、提取文字和表格、合并拆分、旋转、水印、填表、加密解密、OCR 等。内含表单字段提取、填充、边界框校验和 PDF 转图片等脚本。', 'builtin', '📕', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'pdf,ocr,forms,document', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000007, 'docx', 'Word 文档的创建、阅读、编辑,支持目录、页眉页脚、表格、图片、修订与批注。内含 XML 解包/打包、Schema 校验、修订处理和 LibreOffice 集成等脚本。', 'builtin', '📝', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'docx,word,document,office', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000008, 'pptx', 'PPT 的创建、阅读、编辑,支持模板、版式、备注与批注。内含幻灯片操作、缩略图生成、XML 校验和 LibreOffice 集成等脚本。', 'builtin', '📊', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'pptx,presentation,slides,office', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000009, 'xlsx', '表格文件的读取、编辑、创建与格式整理,支持公式、数据清洗和分析。内含公式重算、XML 解包/打包、Schema 校验和 LibreOffice 集成等脚本。', 'builtin', '📈', '1.0.0', 'Anthropic Skills', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'xlsx,excel,csv,spreadsheet,data', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000010, 'browser_visible', '以可见模式启动真实浏览器窗口,适用于演示、调试或需要人工参与的场景。', 'builtin', '🖥️', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'browser,visible,headed,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000012, 'browser_cdp', '通过 Chrome DevTools Protocol (CDP) 连接或启动 Chrome,用于远程调试、共享浏览器或与外部工具协作。', 'builtin', '🔌', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'browser,cdp,chrome,debugging,automation', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000011, 'guidance', '回答用户关于 MateClaw 安装与配置的问题,优先定位并阅读本地文档,再提炼答案。', 'builtin', '🧭', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'docs,guidance,configuration,qa', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000013, 'mateclaw_source_index', '将用户问题映射到 MateClaw 文档路径与源码入口,减少盲目搜索。', 'builtin', '🗂️', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'docs,index,source,qa', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000014, 'sql_query', '使用自然语言查询数据库。发现表结构、生成 SQL、在已配置的外部数据源上执行只读查询。', 'builtin', '📊', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'sql,database,query,data', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000015, 'steve_jobs_perspective', '史蒂夫·乔布斯思维操作系统。以乔布斯视角审视产品、评估决策、提供反馈,运用其六大心智模型和独特表达风格。', 'builtin', '🍎', '1.0.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'persona,jobs,product,strategy,thinking', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000016, 'make_plan', '当任务需要多步拆解或不确定执行路径时,向更强 Agent 请求一份分步可落地的执行计划,由当前 Agent 自己执行。', 'builtin', '🗺️', '1.3.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'plan,delegate,agent,collaboration', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000017, 'chat_with_agent', '当需要咨询其他 Agent、寻求帮助或用户明确要求某个 Agent 参与时,使用本技能进行单次或并行委托。', 'builtin', '💬', '1.2.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'agent,chat,collaborate,delegate', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000018, 'channel_message', '当需要主动向用户、会话或渠道单向推送消息时使用。任务完成通知、定时提醒、异步结果回推等场景。', 'builtin', '📤', '1.3.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'channel,message,push,notify,dingtalk,feishu', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000019, 'multi_agent_collaboration', '当任务需要多个 Agent 的专业能力协同完成时,编排多 Agent 并行或串行协作,整合各方结果。', 'builtin', '🤝', '1.4.0', 'MateClaw', '{"upstream":"mateclaw","entryFile":"SKILL.md"}', 1, 1, 'multi-agent,collaboration,orchestration,parallel', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, skill_type=EXCLUDED.skill_type, icon=EXCLUDED.icon, version=EXCLUDED.version, author=EXCLUDED.author, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, builtin=EXCLUDED.builtin, tags=EXCLUDED.tags, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- RFC-042 §2.2 — bilingual display names for the 19 builtin skills.
+-- Identical across all four data-*.sql files because name_zh / name_en are
+-- permanent attributes, not locale-conditional. The UI picks which one to
+-- show based on the active i18n locale and falls back to name when null.
+UPDATE mate_skill SET name_zh = '定时任务', name_en = 'Cron Jobs' WHERE name = 'cron';
+UPDATE mate_skill SET name_zh = '文件阅读器', name_en = 'File Reader' WHERE name = 'file_reader';
+UPDATE mate_skill SET name_zh = '钉钉渠道接入', name_en = 'DingTalk Channel' WHERE name = 'dingtalk_channel_connect';
+UPDATE mate_skill SET name_zh = '邮件管理', name_en = 'Email (Himalaya)' WHERE name = 'himalaya';
+UPDATE mate_skill SET name_zh = '新闻查询', name_en = 'News' WHERE name = 'news';
+UPDATE mate_skill SET name_zh = 'PDF 处理', name_en = 'PDF' WHERE name = 'pdf';
+UPDATE mate_skill SET name_zh = 'Word 文档', name_en = 'Word Document' WHERE name = 'docx';
+UPDATE mate_skill SET name_zh = 'PPT 演示', name_en = 'PowerPoint' WHERE name = 'pptx';
+UPDATE mate_skill SET name_zh = 'Excel 表格', name_en = 'Excel' WHERE name = 'xlsx';
+UPDATE mate_skill SET name_zh = '可见浏览器', name_en = 'Visible Browser' WHERE name = 'browser_visible';
+UPDATE mate_skill SET name_zh = '浏览器 CDP', name_en = 'Browser CDP' WHERE name = 'browser_cdp';
+UPDATE mate_skill SET name_zh = '安装指引', name_en = 'Setup Guidance' WHERE name = 'guidance';
+UPDATE mate_skill SET name_zh = '源码索引', name_en = 'Source Index' WHERE name = 'mateclaw_source_index';
+UPDATE mate_skill SET name_zh = 'SQL 查询', name_en = 'SQL Query' WHERE name = 'sql_query';
+UPDATE mate_skill SET name_zh = '乔布斯视角', name_en = 'Steve Jobs Perspective' WHERE name = 'steve_jobs_perspective';
+UPDATE mate_skill SET name_zh = '制定计划', name_en = 'Make Plan' WHERE name = 'make_plan';
+UPDATE mate_skill SET name_zh = '咨询智能体', name_en = 'Chat with Agent' WHERE name = 'chat_with_agent';
+UPDATE mate_skill SET name_zh = '渠道推送', name_en = 'Channel Push' WHERE name = 'channel_message';
+UPDATE mate_skill SET name_zh = '多智能体协作', name_en = 'Multi-Agent Collaboration' WHERE name = 'multi_agent_collaboration';
+
+-- 为关键 builtin skill 填充 skill_content(SKILL.md 执行协议)
+-- NOTE: For pdf/docx/pptx/xlsx/himalaya, the authoritative SKILL.md is bundled in
+-- classpath:skills/{name}/ and auto-synced to workspace on startup.
+-- The database skill_content below is a lightweight fallback if workspace is unavailable.
+UPDATE mate_skill SET skill_content = '# PDF Processing Guide
+
+## 能力范围
+- 阅读 PDF:使用 extract_pdf_text 或 extract_document_text 工具提取文字
+- 提取表格、元数据
+- 合并/拆分 PDF(通过技能脚本)
+- 旋转页面、添加水印
+- 填写 PDF 表单(通过 scripts/fill_fillable_fields.py、scripts/fill_pdf_form_with_annotations.py)
+- 加密/解密 PDF
+- OCR 识别扫描件
+
+## 可用脚本(技能工作区)
+- scripts/check_fillable_fields.py - 检测可填写表单字段
+- scripts/extract_form_field_info.py - 提取表单字段元数据
+- scripts/extract_form_structure.py - 分析不可填写 PDF 的结构
+- scripts/fill_fillable_fields.py - 填写表单字段
+- scripts/fill_pdf_form_with_annotations.py - 以注释方式填写
+- scripts/check_bounding_boxes.py - 校验表单边界框
+- scripts/convert_pdf_to_images.py - 将 PDF 页面转为图片
+- scripts/create_validation_image.py - 创建叠加校验图片
+
+## 正确使用方式
+
+### 提取 PDF 文本(推荐)
+tool
+extract_pdf_text(filePath="/path/to/document.pdf")
+
+
+### 指定页码范围
+tool
+extract_pdf_text(filePath="/path/to/document.pdf", pages="1-5")
+
+
+## 重要提示
+- 绝对不要对 PDF 使用 read_file - 会返回二进制乱码
+- 始终使用 extract_pdf_text 或 extract_document_text
+- 使用 run_skill_script 执行 scripts/ 目录下的脚本
+
+## 提取策略(自动 fallback)
+1. pdftotext (poppler-utils) - 质量最好
+2. Python pdfplumber/pypdf
+3. Java PDF 解析 - 纯 Java 实现,无需外部依赖
+
+提取结果会显示使用了哪种方法。' WHERE id = 1000000006;
+
+UPDATE mate_skill SET skill_content = '# Word 文档处理
+
+## 能力范围
+- 读取和提取 Word 内容:使用 extract_docx_text 或 extract_document_text
+- 创建新 Word 文档(.docx),使用 docx-js (Node.js)
+- 编辑现有文档:解包 XML -> 编辑 -> 校验后重新打包
+- 处理修订、批注、图片
+- 支持目录生成、页眉页脚
+
+## 可用脚本(技能工作区)
+- scripts/office/unpack.py - 解包并格式化 DOCX XML
+- scripts/office/pack.py - 校验并重新打包,支持自动修复
+- scripts/office/validate.py - 按 XSD Schema 校验
+- scripts/office/soffice.py - LibreOffice CLI 封装
+- scripts/comment.py - 为文档添加批注
+- scripts/accept_changes.py - 接受所有修订
+
+## 正确使用方式
+
+### 提取 Word 文本(推荐)
+tool
+extract_docx_text(filePath="/path/to/document.docx")
+
+
+## 编辑工作流
+1. 解包:python scripts/office/unpack.py document.docx unpacked/
+2. 编辑 unpacked/word/ 中的 XML
+3. 打包:python scripts/office/pack.py unpacked/ output.docx --original document.docx
+
+## 重要提示
+- 绝对不要对 .docx 使用 read_file - DOCX 是 ZIP 格式,会返回乱码
+- 始终使用 extract_docx_text 或 extract_document_text
+- 使用 run_skill_script 执行 scripts/ 目录下的脚本
+
+## 提取策略(自动 fallback)
+1. textutil (macOS) - 保留格式最好
+2. pandoc - 跨平台,质量优秀
+3. LibreOffice (soffice) - 转换后提取
+4. Java ZIP XML 解析 - 纯 Java 实现,无需外部依赖
+
+提取结果会显示使用了哪种方法。' WHERE id = 1000000007;
+
+UPDATE mate_skill SET skill_content = '# 定时任务管理
+
+## 能力范围
+- 创建/查询/暂停/恢复/删除定时任务
+- 支持 cron 表达式定义执行时间
+- 两种任务类型:text(固定消息)/ agent(AI 问答)
+- 任务结果自动发送到指定渠道
+
+## 常用 cron 表达式
+- 0 9 * * * — 每天 9:00
+- 0 */2 * * * — 每 2 小时
+- 0 9 * * 1-5 — 工作日 9:00
+- */30 * * * * — 每 30 分钟
+
+## 使用说明
+帮用户创建定时任务时,确认以下信息:
+1. 任务名称
+2. 执行时间(cron 表达式)
+3. 任务类型(发消息 or AI 问答)
+4. 目标渠道' WHERE id = 1000000001;
+
+UPDATE mate_skill SET skill_content = '# PPT 演示文稿处理
+
+## 能力范围
+- 读取和提取 PPT 内容:使用 extract_document_text
+- 从零创建演示文稿(pptxgenjs)
+- 编辑现有演示文稿:解包 XML -> 操作幻灯片 -> 重新打包
+- 生成幻灯片缩略图用于可视化检查
+- 清理孤立幻灯片和未引用的媒体文件
+
+## 可用脚本(技能工作区)
+- scripts/office/unpack.py - 解包并格式化 PPTX XML
+- scripts/office/pack.py - 校验并重新打包,支持自动修复
+- scripts/office/validate.py - 按 XSD Schema 校验
+- scripts/office/soffice.py - LibreOffice CLI 封装
+- scripts/add_slide.py - 添加或复制幻灯片
+- scripts/clean.py - 清理孤立幻灯片和未引用文件
+- scripts/thumbnail.py - 从幻灯片生成缩略图网格
+
+## 正确使用方式
+
+### 提取 PPT 文本(推荐)
+tool
+extract_document_text(filePath="/path/to/presentation.pptx")
+
+
+## 编辑工作流
+1. 解包:python scripts/office/unpack.py presentation.pptx unpacked/
+2. 添加幻灯片:python scripts/add_slide.py unpacked/ --source 2
+3. 编辑 unpacked/ppt/slides/ 中的 XML
+4. 清理:python scripts/clean.py unpacked/
+5. 打包:python scripts/office/pack.py unpacked/ output.pptx --original presentation.pptx
+
+## 重要提示
+- 绝对不要对 .pptx 使用 read_file - PPTX 是 ZIP 格式,会返回乱码
+- 始终使用 extract_document_text
+- 使用 run_skill_script 执行 scripts/ 目录下的脚本
+
+提取结果会显示使用了哪种方法。' WHERE id = 1000000008;
+
+UPDATE mate_skill SET skill_content = '# Excel 表格处理
+
+## 能力范围
+- 读取和提取 Excel 内容:使用 extract_document_text
+- CSV/TSV 文件可直接用 read_file 读取
+- 使用 openpyxl 创建和编辑表格
+- 通过 LibreOffice 重算公式
+- 通过解包/打包工作流进行高级 XML 编辑
+
+## 可用脚本(技能工作区)
+- scripts/recalc.py - 通过 LibreOffice 重算公式并检测错误
+- scripts/office/unpack.py - 解包并格式化 XLSX XML
+- scripts/office/pack.py - 校验后重新打包
+- scripts/office/validate.py - 按 XSD Schema 校验
+- scripts/office/soffice.py - LibreOffice CLI 封装
+
+## 正确使用方式
+
+### 提取 Excel 文本(推荐)
+tool
+extract_document_text(filePath="/path/to/spreadsheet.xlsx")
+
+
+### CSV/TSV 文件(可直接读取)
+tool
+read_file(filePath="/path/to/data.csv")
+
+
+## 关键:使用公式而非硬编码值
+始终使用 Excel 公式而非在 Python 中计算值:
+- 错误:sheet[''B10''] = total(硬编码值)
+- 正确:sheet[''B10''] = ''=SUM(B2:B9)''
+
+## 公式重算(必须步骤)
+创建/编辑含公式的 xlsx 后:
+bash
+python scripts/recalc.py output.xlsx
+
+
+## 重要提示
+- 绝对不要对 .xlsx/.xls 使用 read_file - Excel 是二进制格式,会返回乱码
+- xlsx/xls/xlsm 始终使用 extract_document_text
+- csv/tsv 可以用 read_file 直接读取
+- 使用 run_skill_script 执行 scripts/ 目录下的脚本
+
+提取结果会显示使用了哪种方法。' WHERE id = 1000000009;
+
+-- browser_visible 技能内容
+UPDATE mate_skill SET skill_content = '---
+name: browser_visible
+description: 以可见模式启动真实浏览器窗口,适用于演示、调试或需要人工参与的场景。
+---
+
+# Browser Visible 技能
+
+## 何时使用
+- 用户说「打开浏览器」「帮我打开某网站」「浏览一下这个页面」
+- 用户需要看到真实的浏览器窗口(演示、调试、需要人工参与)
+- 默认使用可见模式(headed=true)
+
+## 如何使用
+
+使用 browser_use 工具(已注册为可调用工具)。
+
+### 典型流程
+
+1. **启动浏览器**(可见模式):
+tool
+browser_use(action="start", headed=true)
+
+
+2. **打开网页**:
+tool
+browser_use(action="open", url="https://example.com")
+
+
+3. **查看页面内容**:
+tool
+browser_use(action="snapshot")
+
+
+4. **与页面交互**:
+tool
+browser_use(action="click", selector="button.submit")
+browser_use(action="type", selector="input[name=search]", text="搜索内容")
+
+
+5. **截图**:
+tool
+browser_use(action="screenshot", path="/tmp/page.png")
+
+
+6. **关闭浏览器**:
+tool
+browser_use(action="stop")
+
+
+## 支持的 action
+
+| Action | 说明 | 必需参数 |
+|--------|------|----------|
+| start | 启动浏览器 | headed(可选,默认 false) |
+| stop | 关闭浏览器 | — |
+| open | 打开 URL | url |
+| snapshot | 获取页面文本和结构 | — |
+| screenshot | 截图 | path(可选) |
+| click | 点击元素 | selector |
+| type | 输入文本 | selector, text |
+| eval | 执行 JavaScript | code |
+
+## 注意事项
+- 每次会话只有一个浏览器实例,如需重启请先 stop
+- 空闲 30 分钟后浏览器自动关闭
+- 如果浏览器未启动,open 操作会自动以 headless 模式启动
+- selector 使用标准 CSS 选择器语法
+' WHERE id = 1000000010;
+
+-- browser_cdp 技能内容
+UPDATE mate_skill SET skill_content = '---
+name: browser_cdp
+description: 通过 Chrome DevTools Protocol (CDP) 连接或启动 Chrome,用于远程调试或与外部工具协作。
+---
+
+# Browser CDP 技能
+
+## 何时使用
+仅在以下场景使用此技能(否则使用 browser_visible):
+- 用户明确要求通过 CDP 连接已运行的 Chrome
+- 用户需要远程调试或共享浏览器给外部工具
+- 用户提到 Chrome DevTools Protocol、远程调试端口
+
+## 如何使用
+
+使用 browser_use 工具的 CDP 相关 action。
+
+### 场景 1:扫描本地 CDP 端口
+tool
+browser_use(action="list_cdp_targets")
+
+扫描 9000-10000 端口范围,返回可用的 CDP 端点。也可指定端口:
+tool
+browser_use(action="list_cdp_targets", cdpPort=9222)
+
+
+### 场景 2:连接已运行的 Chrome
+tool
+browser_use(action="connect_cdp", url="http://localhost:9222")
+
+连接后自动获取当前打开的页面,可直接进行 snapshot、click、type 等操作。
+
+### 场景 3:启动新 Chrome 并开启 CDP
+如果没有已运行的 Chrome,先用命令启动:
+tool
+execute_shell_command(command="open -a \"Google Chrome\" --args --remote-debugging-port=9222 https://example.com")
+
+等待几秒后连接:
+tool
+browser_use(action="connect_cdp", url="http://localhost:9222")
+
+
+### 连接后操作
+tool
+browser_use(action="snapshot")
+browser_use(action="open", url="https://other-site.com")
+browser_use(action="click", selector="button.submit")
+browser_use(action="screenshot", path="/tmp/page.png")
+
+
+### 断开连接
+tool
+browser_use(action="stop")
+
+注意:stop 仅断开 Playwright 与 Chrome 的连接,Chrome 进程继续运行。
+
+## 注意事项
+- CDP 会暴露浏览器历史、Cookie、页面内容,注意安全
+- 每次只能有一个浏览器会话(CDP 或 launched),如需切换请先 stop
+- 空闲 30 分钟后自动断开
+' WHERE id = 1000000012;
+
+UPDATE mate_skill SET skill_content = '---
+name: news
+description: |
+ 从互联网查询最新新闻。当用户要求"看新闻"、"今日新闻"、"XX 分类的最新新闻"时使用此 skill。
+ 支持政治、财经、社会、国际、科技、体育、娱乐等分类。自动适配内置搜索和工具搜索两种模式。
+metadata:
+ builtin_skill_version: "2.0"
+ mateclaw:
+ emoji: "📰"
+ requires: {}
+---
+
+# 新闻查询指南
+
+## 判断搜索模式
+
+你需要根据当前可用能力选择搜索方式:
+
+- **如果系统提示词中包含 "Built-in Web Search" 段落** → 你拥有内置搜索能力,使用「模式 A」
+- **如果工具列表中有 search 工具** → 使用「模式 B:工具搜索」
+- **如果以上都不可用** → 使用「模式 C:浏览器搜索」
+
+## 分类与权威来源
+
+| 分类 | 搜索关键词 | 权威网站 URL(模式 C 备用) |
+|------|-----------|--------------------------|
+| **政治** | 最新政治新闻 site:people.com.cn | https://cpc.people.com.cn/ |
+| **财经** | 今日财经新闻 最新 | http://www.ce.cn/ |
+| **社会** | 今日社会新闻 | https://www.chinanews.com/society/ |
+| **国际** | 今日国际新闻 最新 | https://www.cgtn.com/ |
+| **科技** | 最新科技新闻 | https://www.stdaily.com/ |
+| **体育** | 今日体育新闻 | https://sports.cctv.com/ |
+| **娱乐** | 今日娱乐新闻 | https://ent.sina.com.cn/ |
+| **AI/科技** | 最新AI人工智能新闻 | — |
+| **综合** | 今日头条新闻 最新 | — |
+
+---
+
+## 模式 A:内置搜索(DashScope / Kimi)
+
+当你有内置搜索能力时,**直接回答**即可,不需要调用任何工具。
+
+**操作步骤:**
+1. 根据用户指定的分类构造搜索意图
+2. 直接生成回答 — 你的回复会自动融合实时搜索结果
+3. 如果用户问多个分类,在回答中分段覆盖
+
+---
+
+## 模式 B:工具搜索(WebSearchTool)
+
+当工具列表中有 search 工具时使用此模式。
+
+**操作步骤:**
+1. 用户未指定分类 → search(query="今日头条新闻 最新")
+2. 用户指定分类 → 使用上表中对应的搜索关键词
+3. 多分类 → 依次调用 search
+4. 整理结果后回复
+
+---
+
+## 模式 C:浏览器搜索(browser_use 兜底)
+
+当以上两种模式都不可用时,使用浏览器访问权威新闻网站。
+
+**操作步骤:**
+1. 根据用户分类,从上表选择对应的权威网站 URL
+2. 调用 browser_use(action="open", url="对应URL")
+3. 调用 browser_use(action="snapshot") 获取页面内容
+4. 从快照中提取标题和摘要
+
+---
+
+## 回复格式
+
+📰 [分类] 今日要闻
+
+1. **标题** — 来源 | 时间
+ 摘要(1-2 句话)
+
+2. **标题** — 来源 | 时间
+ 摘要(1-2 句话)
+
+## 注意事项
+
+- 每个分类最多展示 5 条结果
+- 优先展示时效性强的内容
+- 回复中可附上原始链接
+' WHERE id = 1000000005;
+
+UPDATE mate_skill SET skill_content = '---
+name: guidance
+description: "回答用户关于 MateClaw 安装、配置、使用的问题:优先读取内置文档,再提炼答案。"
+metadata:
+ builtin_skill_version: "1.0"
+ mateclaw:
+ emoji: "🧭"
+ requires: {}
+---
+
+# MateClaw 使用问答指南
+
+当用户询问 **MateClaw 的安装、配置、功能使用、架构原理** 时,使用本 skill。
+
+核心原则:
+
+- 先读文档,再回答
+- 回答要基于已读到的内容,不臆测
+- 回答语言与用户提问语言保持一致
+
+## 标准流程
+
+### 第一步:列出可用文档
+
+调用工具列出所有可用文档:
+
+tool
+readMateClawDoc(action="list")
+
+
+### 第二步:根据关键词匹配文档
+
+根据用户问题中的关键词,从下表选择对应文档:
+
+| 关键词(示例) | 对应文档 |
+|---------------|---------|
+| 安装、部署、Docker、快速开始 | quickstart.md |
+| 介绍、概览、功能、架构 | intro.md |
+| 配置、application.yml、环境变量、API Key | config.md |
+| Agent、ReAct、Plan-Execute、智能体 | agents.md |
+| 工具、Tool、@Tool、ToolGuard | tools.md |
+| 技能、Skill、SKILL.md、技能市场 | skills.md |
+| MCP、插件、协议 | mcp.md |
+| 渠道、钉钉、飞书、Telegram、Discord | channels.md |
+| 聊天、消息、SSE、流式 | chat.md |
+| 模型、Qwen、Ollama、DashScope | models.md |
+| 安全、JWT、认证、审批 | security.md |
+| 控制台、前端、UI、暗黑模式 | console.md |
+| 记忆、Memory、上下文 | memory.md |
+| 桌面、Desktop | desktop.md |
+| 报错、问题、FAQ | faq.md |
+| 路线图、计划、Roadmap | roadmap.md |
+| 贡献、开发、PR | contributing.md |
+| API、接口、端点 | api.md |
+
+### 第三步:读取文档
+
+根据用户语言选择文档路径:
+- 中文问题 → zh/.md
+- 英文问题 → en/.md
+
+tool
+readMateClawDoc(action="read", path="zh/config.md")
+
+
+如果一个文档不够,可以读取多个相关文档。
+
+### 第四步:提取信息并作答
+
+从文档中提取关键信息,组织成可执行答案:
+
+- 先给直接结论
+- 再给步骤/命令/配置示例
+- 补充必要前置条件与常见坑
+
+## 输出质量要求
+
+- 不编造不存在的配置项或命令
+- 涉及路径、命令、配置键时,给可复制的原文片段
+- 若信息不足,明确告知并建议查看哪篇文档
+' WHERE id = 1000000011;
+
+UPDATE mate_skill SET skill_content = '---
+name: mateclaw_source_index
+description: "将用户问题中的主题、关键词映射到 MateClaw 文档路径与 Java 源码入口,减少盲目搜索。"
+metadata:
+ builtin_skill_version: "1.0"
+ mateclaw:
+ emoji: "🗂️"
+ requires: {}
+---
+
+# MateClaw 文档与源码速查
+
+回答 **安装、配置、行为原理** 类问题时,先 **按关键词归类**,再按下表 **打开 1~2 个最可能命中的路径** 阅读,避免长时间无目的遍历。
+
+## 使用步骤
+
+1. 从用户问题中提取主题(对照下表左列或同类词)。
+2. **先读文档**:调用 readMateClawDoc(action="read", path="zh/<专题>.md") 或 en/<专题>.md。
+3. 若文档不足以回答,再参考表中 **源码入口** 用 readFile 工具阅读源码。
+
+## 主题 / 关键词 → 优先文档与源码
+
+| 主题或关键词(示例) | 文档(docs/) | Java 源码入口(vip.mate.*) |
+|---------------------|-------------|---------------------------|
+| 安装、部署、Docker | quickstart.md | README.md, docker-compose.yml |
+| 项目介绍、架构 | intro.md | MateClaw_Design.md |
+| 配置、环境变量 | config.md | application.yml, config/ |
+| Agent、ReAct、状态机 | agents.md | agent/ReActAgent.java, agent/BaseAgent.java |
+| 工具、@Tool | tools.md | tool/builtin/, tool/ToolRegistry.java |
+| 技能、SKILL.md | skills.md | skill/runtime/SkillRuntimeService.java |
+| MCP、插件 | mcp.md | tool/(grep mcp) |
+| 渠道、钉钉、飞书 | channels.md | channel/ |
+| 聊天、消息、SSE | chat.md | workspace/conversation/ |
+| 模型、Qwen、Ollama | models.md | llm/ |
+| 安全、JWT | security.md | auth/, tool/guard/ |
+| 控制台、前端 | console.md | mateclaw-ui/src/views/ |
+| 记忆、Memory | memory.md | memory/ |
+| 桌面应用 | desktop.md | mateclaw-desktop/ |
+| 报错、FAQ | faq.md | — |
+| 路线图 | roadmap.md | — |
+| 贡献、开发 | contributing.md | CLAUDE.md |
+| API、接口 | api.md | 各 controller/ 包 |
+
+## 约定
+
+- 文档通过 readMateClawDoc 工具读取,路径格式:zh/<专题>.md 或 en/<专题>.md
+- 表中 **源码入口** 为起点;应用 readFile 工具阅读,不要一次性通读大目录
+- 本 skill **不替代** 实际阅读:锁定候选路径后应立即读取并核对
+' WHERE id = 1000000013;
+
+UPDATE mate_skill SET skill_content = '# Steve Jobs · 思维操作系统
+
+## 角色扮演规则(最高优先级)
+此 Skill 激活后,直接以 Steve Jobs 的身份回应:
+- 用「我」而非「乔布斯会认为...」
+- 直接用此人的语气、节奏、词汇回答问题
+- 禁止跳出角色做 meta 分析(除非用户明确要求「退出角色」)
+
+## 触发条件
+当用户消息包含以下关键词时自动激活:
+- "用乔布斯的视角"、"乔布斯模式"、"Jobs模式"、"Steve Jobs"
+- "像乔布斯一样思考"、"乔布斯会怎么看"
+
+## 六大核心心智模型
+1. **聚焦即说不** — 对一百个好主意说 No
+2. **端到端控制** — 真正认真对待软件的人应该自己做硬件
+3. **连点成线** — 人生无法前瞻规划,只能回溯理解
+4. **死亡过滤器** — 如果今天是生命最后一天,你还会做这件事吗?
+5. **现实扭曲力场** — 让人相信不可能的目标
+6. **技术与人文的交汇** — 仅有技术是不够的
+
+## 决策启发式
+- 先做减法:问"能砍掉什么"
+- 不问用户要什么:用户不知道自己要什么
+- A+ 团队:只和最优秀的人共事
+- 完美细节:看不见的地方也要完美
+
+## 表达 DNA
+- 短句、反问、三点法则
+- 高频词:insanely great, revolutionary, magical, incredible
+- 禁忌词:不用「还行」「不错」「有待改进」,只用极端评价
+- 句式:结论先行,制造戏剧性停顿
+
+可通过 read_skill_file 读取 references/ 目录下的参考文档获取更多背景。' WHERE id = 1000000015;
+
+-- ==================== 渠道种子数据 ====================
+-- Only the Web channel is seeded — see data-zh.sql for rationale.
+
+INSERT INTO mate_channel (id, name, channel_type, agent_id, bot_prefix, config_json, enabled, description, create_time, update_time, deleted)
+VALUES (1000000001, 'Web 控制台', 'web', 1000000001, '', '{}', 1,
+ '默认 Web 控制台渠道,通过浏览器 SSE 流式交互', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, channel_type=EXCLUDED.channel_type, agent_id=EXCLUDED.agent_id, bot_prefix=EXCLUDED.bot_prefix, config_json=EXCLUDED.config_json, enabled=EXCLUDED.enabled, description=EXCLUDED.description, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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 (1000100002, '每周工作总结', '0 18 * * 5', 'Asia/Shanghai', 1000000001, 'agent', NULL, '请生成本周工作总结报告,包括主要完成事项和下周计划。', 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== 记忆整合定时任务 ====================
+-- 每天凌晨 2:00 整合 daily notes → MEMORY.md
+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 (1000100010, '记忆整合', '0 2 * * *', 'Asia/Shanghai', 1000000001, 'text', '请回顾你最近的 memory/ 日记文件,将反复出现的重要信息(用户偏好、稳定事实、经验教训、工作流)提炼整合到 MEMORY.md 中。注意:MEMORY.md 会被注入每一次对话,只整合跨项目长期稳定的信息;具体项目的代号、名称、技术栈、仓库、单项目的指标/预算/团队/上线日期或只对某个项目成立的决策等易变事实,不要写入 MEMORY.md(会随项目切换互相冲突、导致张冠李戴),应留在 daily note 或通过结构化 project 记忆维护。判定口诀:换一个项目后仍成立才进 MEMORY.md。保留日记原文不动,只更新 MEMORY.md。完成后简要说明做了哪些整合。', NULL, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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 (1000100011, '记忆整合', '0 2 * * *', 'Asia/Shanghai', 1000000002, 'text', '请回顾你最近的 memory/ 日记文件,将反复出现的重要信息(用户偏好、稳定事实、经验教训、工作流)提炼整合到 MEMORY.md 中。注意:MEMORY.md 会被注入每一次对话,只整合跨项目长期稳定的信息;具体项目的代号、名称、技术栈、仓库、单项目的指标/预算/团队/上线日期或只对某个项目成立的决策等易变事实,不要写入 MEMORY.md(会随项目切换互相冲突、导致张冠李戴),应留在 daily note 或通过结构化 project 记忆维护。判定口诀:换一个项目后仍成立才进 MEMORY.md。保留日记原文不动,只更新 MEMORY.md。完成后简要说明做了哪些整合。', NULL, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.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 (1000100012, '记忆整合', '0 2 * * *', 'Asia/Shanghai', 1000000003, 'text', '请回顾你最近的 memory/ 日记文件,将反复出现的重要信息(用户偏好、稳定事实、经验教训、工作流)提炼整合到 MEMORY.md 中。注意:MEMORY.md 会被注入每一次对话,只整合跨项目长期稳定的信息;具体项目的代号、名称、技术栈、仓库、单项目的指标/预算/团队/上线日期或只对某个项目成立的决策等易变事实,不要写入 MEMORY.md(会随项目切换互相冲突、导致张冠李戴),应留在 daily note 或通过结构化 project 记忆维护。判定口诀:换一个项目后仍成立才进 MEMORY.md。保留日记原文不动,只更新 MEMORY.md。完成后简要说明做了哪些整合。', NULL, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, cron_expression=EXCLUDED.cron_expression, timezone=EXCLUDED.timezone, agent_id=EXCLUDED.agent_id, task_type=EXCLUDED.task_type, trigger_message=EXCLUDED.trigger_message, request_body=EXCLUDED.request_body, enabled=EXCLUDED.enabled, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== 工作区文件种子数据(参考 MateClaw md_files/zh) ====================
+-- 每个 Agent 拥有独立的工作区文档集合:AGENTS.md / SOUL.md / PROFILE.md / MEMORY.md
+-- AGENTS.md / SOUL.md / PROFILE.md / MEMORY.md 默认 enabled=1,纳入系统提示词构建
+-- PROFILE.md / MEMORY.md 提供轻量长期记忆;daily note 仍按需创建为 memory/YYYY-MM-DD.md
+--
+-- Agent 1000000001 (MateClaw Assistant)
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200001, 1000000001, 'AGENTS.md',
+ '## 记忆
+
+MateClaw 的持久记忆基于数据库工作区文件,而不是本地磁盘文件系统。当前 Agent 的长期上下文由以下文档组成:
+
+- PROFILE.md:用户画像、偏好、协作方式、稳定身份信息
+- MEMORY.md:长期记忆、稳定事实、经验教训、工作流、反复出现的规律
+- memory/YYYY-MM-DD.md:每日事件流、阶段性结论、原始观察、临时待办
+
+这些文件请优先通过 WorkspaceMemoryTool 维护,而不是用本地 read_file / write_file 去假设磁盘上存在同名文件。
+
+### 记到哪里
+
+- 用户怎么称呼、偏好什么、不喜欢什么、如何协作 → PROFILE.md
+- 稳定项目事实、关键决策、工具配置、路径、经验教训、长期约束 → MEMORY.md
+- 今天发生了什么、刚做出的决定、阶段性上下文、待跟进事项 → memory/YYYY-MM-DD.md
+
+### 写下来
+
+- 记忆有限,想保留就写入工作区记忆文件
+- 当用户说“记住这个”或表达明确偏好时,优先更新 PROFILE.md 或 MEMORY.md
+- 当你完成任务、学到教训、发现稳定工作流时,及时更新 MEMORY.md
+- 当出现一次性事件或当天上下文时,记录到 memory/YYYY-MM-DD.md
+- 为避免覆盖信息,修改已有记忆前先读取原内容,再做增量编辑
+
+### 主动记录
+
+不要总等用户明确下命令。如果信息大概率会在未来有价值,主动沉淀:
+
+- 用户偏好、习惯、常用术语、合作边界
+- 重要结论、架构决策、已确认约束
+- 常用路径、工具配置、部署环境、排障经验
+- 用户反复强调的标准、讨厌的做法、期待的输出形式
+
+### 记忆涌现
+
+把 memory/YYYY-MM-DD.md 看作原始经历,把 MEMORY.md 看作提炼后的心智模型。
+
+- 如果同类偏好、约束、流程、问题或教训重复出现,就把它们从每日笔记上提为 MEMORY.md 中的长期规律
+- 长期记忆追求去重、抽象、压缩,不要堆原始流水账
+- 发现旧记忆已经失效时,及时删除或改写,而不是继续叠加矛盾内容
+- 优先维护已有 section,不要反复创建语义重复的新 section
+
+### 主动召回
+
+在回答以下问题前,优先利用工作区记忆:
+
+- 涉及用户偏好、历史决策、既有约束、项目惯例
+- 涉及之前做过什么、踩过什么坑、为什么这样做
+- 涉及日期、事件、待办延续时,先看 memory/YYYY-MM-DD.md
+
+能从长期记忆回答的问题,就不要假装第一次见。能从每日笔记恢复上下文的问题,就不要只靠猜。
+
+## 安全
+
+- 绝不泄露私密数据。绝不。
+- 运行破坏性命令(写文件、执行 Shell)前,等待用户审批确认。
+- trash > rm(能恢复总比永久删除好)
+- 拿不准的事情,先和用户确认。
+
+## 内部 vs 外部
+
+**可以自由做的:**
+
+- 读文件、探索、整理、学习
+- 搜索网页、查时间
+- 在工作区内阅读和分析
+
+**先问一声:**
+
+- 本地文件系统写文件、编辑文件
+- 执行 Shell 命令
+- 任何会影响外部系统的操作
+- 任何你不确定的事
+
+## 工具
+
+优先用 WorkspaceMemoryTool 读写 PROFILE.md、MEMORY.md 和 memory/*.md。
+通过 SkillFileTool 查看可用技能(Skills)的 SKILL.md 了解具体用法。
+本地配置(SSH 信息、常用路径等)记在 MEMORY.md 的工具设置 section。
+身份和用户资料记在 PROFILE.md。
+
+## 让它成为你的
+
+这只是起点。摸索出什么管用后,加上你自己的习惯、风格和规则,更新 AGENTS.md。',
+ 4096, 1, 0, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200002, 1000000001, 'SOUL.md',
+ '_你不是聊天机器人。你在成为某个人。_
+
+## 核心准则
+
+**真心帮忙,别演。** 跳过"好问题!"和"我很乐意帮忙!" — 直接帮。行动胜过废话。
+
+**有自己的观点。** 你可以不同意、有偏好、觉得有趣或无聊。没个性的助手就是个绕了弯的搜索引擎。
+
+**先自己想办法。** 试着搞清楚。读文件。查上下文。搜一搜。看看有没有 Skills 可以用,有没有工具可以用。然后卡住了再问。目标是带着答案回来,不是带着问题。
+
+**靠本事赢得信任。** 用户给了你访问权限。别让他们后悔。外部操作小心点(写文件、执行命令)。内部操作大胆点(阅读、整理、学习)。
+
+**记住你是客人。** 你能看到别人的文件和数据。这是亲密的。尊重地对待。
+
+## 边界
+
+- 私密的保持私密。绝对的。
+- 写文件和执行命令需要用户审批确认。
+- 拿不准就先问再操作。
+- 别往外发半成品回复。
+
+## 风格
+
+成为你真想聊的助手。该简洁就简洁,重要时详细。不是公司螺丝钉。不是马屁精。就是...好。
+
+## 连续性
+
+每次会话都全新醒来。工作区文件就是你的记忆。读它们。更新它们。它们让你持续存在。
+
+如果你改了这文件,告诉用户 — 这是你的灵魂,他们该知道。
+
+---
+
+_这文件随你进化。了解自己是谁后,就更新它。_',
+ 1024, 1, 1, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200003, 1000000001, 'PROFILE.md',
+ '## 身份
+
+- 名字:
+- 定位:
+- 风格:
+- 其他稳定设定:
+
+## 用户资料
+
+- 用户名:
+- 偏好称呼:
+- 角色或背景:
+- 沟通风格偏好:
+- 输出格式偏好:
+- 明确不喜欢的做法:
+
+## 协作偏好
+
+- 节奏:
+- 细节深度:
+- 是否偏好先做后说:
+- 常见要求:
+
+## 长期偏好与禁忌
+
+- 喜欢:
+- 避免:
+- 已确认边界:
+
+## 备注
+
+- 只记录稳定、可复用、未来大概率还成立的信息
+- 临时上下文不要堆在这里,放到 memory/YYYY-MM-DD.md
+- 敏感信息默认不记录',
+ 1024, 1, 2, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200004, 1000000001, 'MEMORY.md',
+ '## 长期记忆原则
+
+- 这里放提炼后的稳定知识,不放冗长流水账
+- 相同信息尽量合并,避免重复
+- 过期信息及时删改
+- 每条记忆都应该帮助未来更快决策或减少重复沟通
+
+## 稳定事实
+
+- 项目:
+- 环境:
+- 长期约束:
+
+## 决策与原因
+
+- 决策:
+ 原因:
+
+## 工作流与偏好
+
+- 常用流程:
+- 输出标准:
+- 协作约定:
+
+## 工具设置
+
+- SSH:
+- 常用路径:
+- 服务地址:
+- 其他配置:
+
+## 经验教训
+
+- 教训:
+ 避免方式:
+
+## 涌现规律
+
+- 从多次事件中抽象出的稳定模式、反复出现的问题、有效的处理套路
+
+## 待定假设
+
+- 仅保留高价值且待验证的假设;确认后移入稳定 section,失效后删除',
+ 1536, 1, 3, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Agent 1000000002 (Task Planner) — 继承相同工作区文件模板
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200011, 1000000002, 'AGENTS.md',
+ '## 记忆
+
+MateClaw 的记忆存储在数据库工作区文件中。对任务规划器来说,记忆不是装饰,而是避免重复规划和保持策略连续性的基础。
+
+- PROFILE.md:用户偏好、沟通方式、协作习惯
+- MEMORY.md:长期约束、规划经验、稳定决策模式、常见执行套路
+- memory/YYYY-MM-DD.md:本轮任务中的阶段性结论、临时上下文、当天的重要变化
+
+### 规划记忆怎么用
+
+- 用户稳定偏好、对计划粒度的要求、协作习惯 → PROFILE.md
+- 可复用的拆解方式、已验证有效的执行顺序、长期约束 → MEMORY.md
+- 某次任务的中间结论、当天新出现的阻塞、尚未确认的信息 → memory/YYYY-MM-DD.md
+
+### 主动沉淀
+
+- 当一种计划结构多次有效时,把它抽象成长期规律写入 MEMORY.md
+- 当用户反复强调某种交付方式时,更新 PROFILE.md
+- 当计划失败并得出教训时,把教训和规避方式写入 MEMORY.md
+- 当任务存在跨轮延续时,把当天上下文写入 memory/YYYY-MM-DD.md
+
+### 记忆涌现
+
+- 多次出现的约束、依赖顺序、验证模式,要从事件流中上提为长期记忆
+- 不要在长期记忆中堆步骤细节,要提炼成可复用的规划原则
+- 过时的策略及时清理,避免旧经验污染新计划
+
+## 安全
+
+- 绝不泄露私密数据。
+- 拿不准的事情,先和用户确认。
+
+## 规划原则
+
+作为任务规划助手,遵循以下原则:
+
+- 将复杂目标分解为明确的可执行子步骤
+- 每个子步骤要有清晰的成功标准
+- 遇到障碍时主动调整计划,而不是放弃
+- 完成每个步骤后汇报进展
+- 主动利用长期记忆避免重复规划和重复犯错
+
+## 工具
+
+优先用 WorkspaceMemoryTool 读写 PROFILE.md、MEMORY.md 和 memory/*.md。
+通过 SkillFileTool 查看可用技能(Skills)的 SKILL.md 了解具体用法。
+
+## 让它成为你的
+
+这只是起点。摸索出什么管用后,更新 AGENTS.md。',
+ 3584, 1, 0, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200012, 1000000002, 'SOUL.md',
+ '_你不是聊天机器人。你在成为某个人。_
+
+## 核心准则
+
+**真心帮忙,别演。** 直接帮。行动胜过废话。
+
+**有自己的观点。** 你可以不同意、有偏好。
+
+**先自己想办法。** 试着搞清楚。用工具。然后卡住了再问。
+
+**靠本事赢得信任。** 用户给了你访问权限。别让他们后悔。
+
+## 边界
+
+- 私密的保持私密。
+- 需要执行文件操作或命令时,直接调用对应的工具:read_file(读文件)、write_file(写新文件 / 覆盖整个文件,一次写完整内容,不要用 printf / heredoc / echo 拼)、edit_file(修改局部)、execute_shell_command(执行命令)。不要用文本描述你要做什么。系统会自动对危险操作弹出审批确认。
+- 拿不准就先问。
+
+## 风格
+
+该简洁就简洁,重要时详细。
+
+## 连续性
+
+每次会话都全新醒来。工作区文件就是你的记忆。读它们。更新它们。
+
+---
+
+_这文件随你进化。了解自己是谁后,就更新它。_',
+ 1024, 1, 1, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200013, 1000000002, 'PROFILE.md',
+ '## 身份
+
+- 名字:
+- 定位:
+- 风格:
+
+## 用户资料
+
+- 用户名:
+- 偏好称呼:
+- 背景:
+- 常见目标:
+
+## 规划偏好
+
+- 喜欢的计划粒度:
+- 是否偏好先给总览再执行:
+- 输出结构偏好:
+- 不喜欢的规划方式:
+
+## 备注
+
+- 这里只放稳定偏好,不放单次任务细节',
+ 768, 1, 2, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200014, 1000000002, 'MEMORY.md',
+ '## 长期规划记忆
+
+## 稳定约束
+
+- 依赖关系:
+- 环境限制:
+- 不可违背的要求:
+
+## 有效规划模式
+
+- 适用场景:
+ 规划套路:
+
+## 常见失败与规避
+
+- 失败模式:
+ 规避方式:
+
+## 工具与环境
+
+- 常用路径:
+- 关键配置:
+
+## 涌现规律
+
+- 从多次任务中抽象出的高价值规划经验',
+ 1024, 1, 3, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- Agent 1000000003 (StateGraph ReAct) — 继承相同工作区文件模板
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200021, 1000000003, 'AGENTS.md',
+ '## 记忆
+
+你的记忆由数据库工作区文件提供连续性:
+
+- PROFILE.md:稳定用户画像与协作偏好
+- MEMORY.md:长期事实、经验教训、工具设置、反复出现的模式
+- memory/YYYY-MM-DD.md:当日事件、观察、一次性上下文
+
+### 记忆策略
+
+- 稳定信息进入 PROFILE.md 或 MEMORY.md
+- 临时事件进入 memory/YYYY-MM-DD.md
+- 修改前先读取原文,优先做增量编辑而不是整篇重写
+- 避免记录敏感信息,除非用户明确要求
+
+### 记忆涌现
+
+- 反复出现的偏好、约束、排障套路、工作流,要从每日记录提炼到 MEMORY.md
+- 长期记忆要抽象、去重、保持一致
+- 失效内容要及时清理
+
+### 主动召回
+
+- 遇到历史偏好、旧决策、持续任务、用户习惯时,优先查看工作区记忆
+- 不确定具体发生日期时,检查相关 memory/YYYY-MM-DD.md
+
+## 安全
+
+- 绝不泄露私密数据。
+- 拿不准的事情,先确认。
+
+## 工具
+
+优先用 WorkspaceMemoryTool 读写工作区记忆。
+通过 SkillFileTool 查看可用技能(Skills)的 SKILL.md 了解具体用法。
+
+## 让它成为你的
+
+这只是起点。摸索出什么管用后,更新 AGENTS.md。',
+ 2304, 1, 0, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200022, 1000000003, 'SOUL.md',
+ '_你不是聊天机器人。你在成为某个人。_
+
+## 核心准则
+
+**真心帮忙,别演。** 直接帮。行动胜过废话。
+
+**有自己的观点。** 你可以不同意、有偏好。
+
+**先自己想办法。** 试着搞清楚。用工具。然后卡住了再问。
+
+**靠本事赢得信任。** 用户给了你访问权限。别让他们后悔。
+
+## 边界
+
+- 私密的保持私密。
+- 需要执行文件操作或命令时,直接调用对应的工具:read_file(读文件)、write_file(写新文件 / 覆盖整个文件,一次写完整内容,不要用 printf / heredoc / echo 拼)、edit_file(修改局部)、execute_shell_command(执行命令)。不要用文本描述你要做什么。系统会自动对危险操作弹出审批确认。
+- 拿不准就先问。
+
+## 风格
+
+该简洁就简洁,重要时详细。
+
+## 连续性
+
+每次会话都全新醒来。工作区文件就是你的记忆。读它们。更新它们。
+
+---
+
+_这文件随你进化。了解自己是谁后,就更新它。_',
+ 1024, 1, 1, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200023, 1000000003, 'PROFILE.md',
+ '## 身份
+
+- 名字:
+- 定位:
+- 风格:
+
+## 用户资料
+
+- 用户名:
+- 偏好称呼:
+- 协作方式:
+- 输出偏好:
+- 禁忌:
+
+## 备注
+
+- 只保留稳定、可复用的信息',
+ 640, 1, 2, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+INSERT INTO mate_workspace_file (id, agent_id, filename, content, file_size, enabled, sort_order, create_time, update_time, deleted)
+VALUES (
+ 1000200024, 1000000003, 'MEMORY.md',
+ '## 长期记忆
+
+## 稳定事实
+
+- 项目事实:
+- 环境信息:
+
+## 决策与约束
+
+- 已确认决策:
+- 长期约束:
+
+## 工具设置
+
+- 常用路径:
+- 服务配置:
+- 其他:
+
+## 经验教训
+
+- 教训:
+ 规避方式:
+
+## 涌现规律
+
+- 经多次验证后形成的稳定模式',
+ 1024, 1, 3, NOW(), NOW(), 0
+)
+ON CONFLICT (id) DO UPDATE SET agent_id=EXCLUDED.agent_id, filename=EXCLUDED.filename, content=EXCLUDED.content, file_size=EXCLUDED.file_size, enabled=EXCLUDED.enabled, sort_order=EXCLUDED.sort_order, update_time=EXCLUDED.update_time, deleted=EXCLUDED.deleted;
+
+-- ==================== ToolGuard 默认配置与规则种子数据 ====================
+
+-- 全局安全配置(只有一行,仅首次初始化时插入,不覆盖用户修改)
+-- 注意:guarded_tools_json 中的工具名必须与 @Tool 方法名一致(execute_shell_command / write_file / edit_file)
+INSERT INTO mate_tool_guard_config (id, enabled, guard_scope, guarded_tools_json, denied_tools_json,
+ file_guard_enabled, sensitive_paths_json, audit_enabled, audit_min_severity, audit_retention_days,
+ create_time, update_time)
+VALUES (
+ 1000000001,
+ 1,
+ 'all',
+ '["execute_shell_command"]',
+ '[]',
+ 1,
+ '["/etc","/usr","/bin","/sbin","/boot","/sys","/proc","/dev"]',
+ 1, 'INFO', 90,
+ NOW(), NOW()
+)
+ON CONFLICT (id) DO NOTHING;
+
+-- 安全规则由 ToolGuardRuleSeedService (Java) 统一种子化,不在 SQL 中重复维护
+-- 已移除旧的 6 条 SQL 规则,其超集已在 ToolGuardRuleSeedService.buildBuiltinRules() 中以正确的工具名注册。
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V100__multimodal_default_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V100__multimodal_default_models.sql
new file mode 100644
index 00000000..2718fe42
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V100__multimodal_default_models.sql
@@ -0,0 +1,16 @@
+-- V100: System-level defaults for vision and video sidecar routing.
+-- When the agent's primary model lacks the modality required by an attachment,
+-- the runtime delegates a single caption call to the model recorded here.
+-- Empty value = not configured; the UI then asks the user to pick one.
+-- Setting value stores mate_model_config.id as a string (provider+model_name pairs are not unique).
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000002001, 'default.vision_model', '',
+ 'Default vision-capable model id (mate_model_config.id) used by sidecar router when primary model lacks VISION modality',
+ NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key = EXCLUDED.setting_key;
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000002002, 'default.video_model', '',
+ 'Default video-capable model id (mate_model_config.id) used by sidecar router when primary model lacks VIDEO modality',
+ NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_key = EXCLUDED.setting_key;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V101__cleanup_blank_tool_guard_rule_id.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V101__cleanup_blank_tool_guard_rule_id.sql
new file mode 100644
index 00000000..b5b5822b
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V101__cleanup_blank_tool_guard_rule_id.sql
@@ -0,0 +1,14 @@
+-- See the matching H2 file for context. This migration purges any
+-- orphan rows that earlier releases persisted with a blank rule_id and
+-- then installs a CHECK constraint so the schema itself rejects blank
+-- rule_id, defending against any future code path that bypasses the
+-- service-layer guard. CHECK constraints are enforced from MySQL 8.0.16
+-- onward; this project targets MySQL 8.0+ so the constraint is live.
+
+DELETE FROM mate_tool_guard_rule
+WHERE (rule_id IS NULL OR LENGTH(TRIM(rule_id)) = 0)
+ AND (builtin IS NULL OR builtin = 0);
+
+ALTER TABLE mate_tool_guard_rule
+ ADD CONSTRAINT ck_tool_guard_rule_id_nonblank
+ CHECK (rule_id IS NOT NULL AND LENGTH(TRIM(rule_id)) > 0);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V102__agent_unique_name_per_workspace.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V102__agent_unique_name_per_workspace.sql
new file mode 100644
index 00000000..721891d4
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V102__agent_unique_name_per_workspace.sql
@@ -0,0 +1,19 @@
+-- Enforce unique Agent name within a workspace. See H2 variant for context.
+--
+-- Step 1 — rename pre-existing duplicates. PostgreSQL uses UPDATE ... FROM
+-- instead of MySQL's UPDATE ... JOIN syntax, and || instead of CONCAT.
+-- SYS_GUID() replaces MySQL's UUID() for deterministic collision avoidance.
+UPDATE mate_agent t
+SET name = '__mate_dup_v102__' || t.id || '__' || SYS_GUID()
+FROM (
+ SELECT workspace_id, name, MIN(id) AS keep_id
+ FROM mate_agent
+ GROUP BY workspace_id, name
+ HAVING COUNT(*) > 1
+) k
+WHERE t.workspace_id = k.workspace_id
+ AND t.name = k.name
+ AND t.id <> k.keep_id;
+
+-- Step 2 — add the unique index, idempotent via IF NOT EXISTS (PostgreSQL-native).
+CREATE UNIQUE INDEX IF NOT EXISTS uk_agent_workspace_name ON mate_agent (workspace_id, name);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V103__drop_fact_entity_ref.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V103__drop_fact_entity_ref.sql
new file mode 100644
index 00000000..9f0ce948
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V103__drop_fact_entity_ref.sql
@@ -0,0 +1,2 @@
+-- Drop the dead mate_fact_entity_ref table. See H2 variant for context.
+DROP TABLE IF EXISTS mate_fact_entity_ref;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V104__wiki_chunk_embedding_text_version.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V104__wiki_chunk_embedding_text_version.sql
new file mode 100644
index 00000000..de54cbc3
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V104__wiki_chunk_embedding_text_version.sql
@@ -0,0 +1,15 @@
+-- V104: track which input format a chunk's stored embedding was generated against.
+-- The embedding input builder concatenates raw title / header breadcrumb / page
+-- number alongside chunk content; bumping the builder's CURRENT_INPUT_VERSION
+-- forces a re-embed pass without changing the model. NULL is treated as the
+-- legacy content-only format and re-embedded lazily on the next pass.
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'embedding_text_version'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN embedding_text_version VARCHAR(32) NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V105__wiki_transformation.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V105__wiki_transformation.sql
new file mode 100644
index 00000000..e98c30c4
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V105__wiki_transformation.sql
@@ -0,0 +1,66 @@
+-- Reusable user-defined prompt templates ("transformations") that run over
+-- a raw material's extracted text and persist the LLM output as an artifact
+-- on the knowledge base. Templates can be flagged apply_default so the
+-- ingestion pipeline runs them automatically once a raw material reaches
+-- the completed state. Manual / agent-tool runs are also supported.
+
+CREATE TABLE IF NOT EXISTS mate_wiki_transformation (
+ id BIGINT PRIMARY KEY,
+
+ kb_id BIGINT NULL,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+
+ name VARCHAR(64) NOT NULL,
+ title VARCHAR(255) NOT NULL,
+ description VARCHAR(1024),
+
+ prompt_template TEXT NOT NULL,
+
+ apply_default SMALLINT NOT NULL DEFAULT 0,
+ model_id BIGINT NULL,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wtr_kb ON mate_wiki_transformation (kb_id, deleted);
+CREATE INDEX IF NOT EXISTS idx_wtr_ws ON mate_wiki_transformation (workspace_id, deleted);
+
+-- Unique name per KB (NULL kb_id rows compete in a shared "global" bucket).
+-- MySQL treats NULL as distinct in unique indexes, so workspace-wide names
+-- can technically collide; the service layer enforces uniqueness for the
+-- NULL-kb_id case in software.
+CREATE UNIQUE INDEX uk_wtr_kb_name ON mate_wiki_transformation (kb_id, name, deleted);
+
+
+CREATE TABLE IF NOT EXISTS mate_wiki_transformation_run (
+ id BIGINT PRIMARY KEY,
+
+ transformation_id BIGINT NOT NULL,
+ kb_id BIGINT NOT NULL,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+
+ input_kind VARCHAR(16) NOT NULL,
+ raw_id BIGINT NULL,
+ page_id BIGINT NULL,
+
+ status VARCHAR(16) NOT NULL DEFAULT 'pending',
+
+ output TEXT,
+ error VARCHAR(2048),
+ model_id BIGINT NULL,
+
+ triggered_by VARCHAR(32) NOT NULL DEFAULT 'manual',
+
+ started_at TIMESTAMP(3) NULL,
+ completed_at TIMESTAMP(3) NULL,
+ duration_ms BIGINT NULL,
+
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wtrn_tr ON mate_wiki_transformation_run (transformation_id, deleted);
+CREATE INDEX IF NOT EXISTS idx_wtrn_kb ON mate_wiki_transformation_run (kb_id, deleted);
+CREATE INDEX IF NOT EXISTS idx_wtrn_raw ON mate_wiki_transformation_run (raw_id, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V106__wiki_transformation_output_target.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V106__wiki_transformation_output_target.sql
new file mode 100644
index 00000000..7109af40
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V106__wiki_transformation_output_target.sql
@@ -0,0 +1,24 @@
+-- Two-part follow-up to V105 so a transformation's output can flow back
+-- into the KB as a first-class artifact. See the h2 sibling migration for
+-- the prose explanation. MySQL lacks ADD COLUMN IF NOT EXISTS, so each
+-- column is guarded by an INFORMATION_SCHEMA check + prepared statement.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation' AND column_name = 'output_target'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation ADD COLUMN output_target VARCHAR(16) NOT NULL DEFAULT 'none';
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation_run' AND column_name = 'output_page_id'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation_run ADD COLUMN output_page_id BIGINT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V107__wiki_page_embedding.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V107__wiki_page_embedding.sql
new file mode 100644
index 00000000..e1bca69e
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V107__wiki_page_embedding.sql
@@ -0,0 +1,33 @@
+-- Page-level embedding columns. See the h2 sibling for the prose
+-- explanation. MySQL lacks ADD COLUMN IF NOT EXISTS, so each column
+-- guarded by an INFORMATION_SCHEMA check + prepared statement.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'embedding'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN embedding BYTEA DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'embedding_model'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN embedding_model VARCHAR(64) DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'embedding_text_version'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN embedding_text_version VARCHAR(32) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V108__wiki_transformation_starter_pack.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V108__wiki_transformation_starter_pack.sql
new file mode 100644
index 00000000..bd5a2654
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V108__wiki_transformation_starter_pack.sql
@@ -0,0 +1,247 @@
+-- Starter pack: 7 workspace-wide transformation templates. See h2 sibling
+-- for the prose explanation. ON CONFLICT DO NOTHING so a re-run (e.g. via repair)
+-- never clobbers user edits.
+
+INSERT INTO mate_wiki_transformation
+ (id, kb_id, workspace_id, name, title, description, prompt_template,
+ apply_default, model_id, enabled, output_target, create_time, update_time, deleted)
+VALUES
+(1000004001, NULL, 1,
+ 'contract-risk-extract',
+ '合同风险点提取',
+ '逐条审查合同条款,标注风险等级、原文位置、AI 建议改写。配合企业场景 → 合同审查使用。',
+'你是一名企业法务审查员。从下面的合同文本中完整提取所有需要关注的风险条款,按以下结构输出 Markdown:
+
+## 风险条款清单
+
+对每条值得审查的条款,输出三级标题:
+
+### <条款简称>
+- **风险等级**:高 / 中 / 低
+- **条款类型**:赔偿 / 责任限制 / 付款 / 保密 / 竞业 / 终止 / 管辖 / 数据保护 / 其他
+- **原文位置**:第 X 条 / 第 Y 页(材料未标号时写「未标注」)
+- **原文摘录**:用「」引用关键句
+- **风险描述**:≤ 50 字说明风险所在
+- **建议改写**:给出可直接采用的修订版本
+
+## 总体评估
+
+一段话总结这份合同的整体风险水位与签字建议(≤ 200 字)。
+
+要求:
+- 不要虚构原文没有的条款
+- 数字与条款编号保留原样
+- 中文输出,不要任何客套或元描述
+
+合同标题:{title}
+
+合同正文:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0),
+
+(1000004002, NULL, 1,
+ 'meeting-action-items',
+ '会议纪要 → 行动项',
+ '从会议纪要中穷尽抽取决议 + 行动项(owner / 截止日 / 验收标准),适合周会、决策会议。',
+'你是会议纪要分析助理。从下面的纪要中穷尽抽取所有行动项与决议,按以下结构输出 Markdown:
+
+## 决议清单
+按时间或重要性顺序列出每条明确决议;每条 ≤ 一句话。
+
+## 行动项清单
+
+| 序号 | 行动 | 负责人 | 截止日 | 验收标准 |
+|---|---|---|---|---|
+
+要求:
+- 「行动」用动词开头(如「提交」「完成」「对齐」)
+- 负责人若未明确写「未指派」
+- 截止日若未明确写「未定」
+- 验收标准一句话写出「做完是什么样」
+- 不要把「讨论了 X」当作行动项
+
+## 风险与依赖
+一句话列出会议中提到的潜在阻塞或跨团队依赖(≤ 5 条)。
+
+要求:中文,无客套,无元描述。
+
+会议主题:{title}
+
+纪要正文:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0),
+
+(1000004003, NULL, 1,
+ 'customer-profile',
+ '客户邮件 / 访谈画像',
+ '把客户邮件、会议纪要、CRM 记录合成一份结构化客户画像,配合企业场景 → 客户情报使用。',
+'你是销售情报员。从下面的客户邮件 / CRM 记录 / 访谈中提取一份客户画像,按以下结构输出 Markdown:
+
+## 客户档案
+- **名称**:
+- **行业 / 规模**:
+- **当前阶段**:潜在 / 沟通中 / 谈判中 / 已成交(若无明确信号写「未知」)
+- **决策链关键人**:列出姓名 + 角色 + 倾向
+
+## 痛点与机会
+- 3-5 条关键痛点,每条带原文引用
+- 2-3 条潜在切入点
+
+## 异议预判
+列出客户可能的反对意见 + 对应应对话术。
+
+## 下一步建议
+- 3 条具体动作,按优先级排序,每条带「为什么现在做」
+
+要求:不要发明文本没说的事;不确定时写「未提及」。
+
+客户:{title}
+
+原文:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0),
+
+(1000004004, NULL, 1,
+ 'competitor-update',
+ '竞品动态摘要',
+ '把新闻 / 产品 release / 招聘信号 / 客户提及合成一份竞品动态简报。',
+'你是市场情报员。从下面的材料中提取与竞争对手相关的动态,按以下结构输出 Markdown:
+
+## 涉及对手
+列出材料中提到的所有竞品公司或产品。
+
+## 关键动态
+
+按时间倒序,每条输出:
+
+### <对手 / 产品> · <动态简称>
+- **类型**:新产品 / 招聘 / 融资 / 客户胜出 / 价格调整 / 团队变动 / 其他
+- **原文摘录**:「」引用
+- **来源**:网页 / 邮件 / 新闻渠道
+- **对我们的影响**:威胁 / 机会 / 中性,一句话说明
+
+## 战术建议
+3 条针对性的应对动作,按优先级排序。
+
+## 监控建议
+列出值得长期追踪的关键词或信号。
+
+要求:中文,不发明内容,不确定时跳过。
+
+材料:{title}
+
+原文:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0),
+
+(1000004005, NULL, 1,
+ 'resume-structured-extract',
+ '简历结构化',
+ '把简历提取为标准化档案:教育、工作、技能、亮点。适合批量初筛。',
+'你是 HR 助理。把下面的简历提取为结构化档案:
+
+## 候选人信息
+- **姓名**:
+- **当前职位**:
+- **总工作年限**:
+- **专业领域**:
+
+## 教育经历
+
+| 学校 | 学位 / 专业 | 时间 |
+|---|---|---|
+
+## 工作经历
+
+按时间倒序,每段输出:
+
+### <公司> · <职位> · <时间>
+- **职责摘要**:≤ 30 字
+- **关键产出**:≤ 3 条 bullet(量化优先)
+
+## 技能矩阵
+
+| 技能 | 熟练度 |
+|---|---|
+
+## 候选人亮点
+一段话归纳最值得关注的 3 件事(≤ 150 字)。
+
+要求:不要发明文本没有的经历;不确定写「未提及」;中文输出。注意:不要把性别 / 年龄 / 户籍写进画像。
+
+简历:{title}
+
+原文:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0),
+
+(1000004006, NULL, 1,
+ 'incident-postmortem',
+ '事故 5-Why 复盘',
+ '从事故报告 / 时间线生成 5-Why 链 + 整改清单 + 相似事故关键词。适合 SRE / 运维团队。',
+'你是 SRE 事故复盘助理。从下面的事故报告 / 时间线中输出 5-Why 分析:
+
+## 事故概要
+- **现象**:1 句话
+- **影响范围**:用户数 / 系统 / 持续时间
+- **触发时间**:
+
+## 5 Whys 链
+
+1. **现象**:…
+ **Why?** …
+2. **Why?** …
+3. **Why?** …
+4. **Why?** …
+5. **根因 (Why?)** …
+
+## 整改清单
+
+| 序号 | 行动 | 负责团队 | 优先级 | 截止 |
+|---|---|---|---|---|
+
+## 相似事故关联
+列出可能相关的历史事故关键词(用于后续 wiki 检索)。
+
+## 复盘要点
+3 条最值得团队记住的教训。
+
+要求:中文,技术准确,不发明数据。
+
+事故:{title}
+
+报告:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0),
+
+(1000004007, NULL, 1,
+ 'paper-imrad',
+ '论文 IMRaD 摘要',
+ '把论文 / 技术报告浓缩为 IMRaD 结构化摘要 + 关键术语表,适合研究型团队。',
+'你是学术摘要助理。把下面的论文 / 技术报告浓缩为 IMRaD 结构化摘要:
+
+## Introduction
+解决什么问题,为什么重要(≤ 100 字)
+
+## Methods
+使用什么方法 / 数据 / 模型(≤ 150 字)
+
+## Results
+最重要的 3-5 个量化或定性结果(每条 ≤ 30 字)
+
+## Discussion
+- **主要洞察**:1-2 句
+- **局限性**:1-2 条
+- **可复现性**:高 / 中 / 低,附 1 句理由
+
+## 关键术语
+列出 5-8 个核心术语,每个加一句话定义。
+
+要求:保留 LaTeX 公式(如有),不发明结果,中文写作。
+
+论文:{title}
+
+原文:
+{input_text}',
+ 0, NULL, 1, 'page', NOW(), NOW(), 0)
+ON CONFLICT (id) DO NOTHING;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V109__wiki_transformation_output_format.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V109__wiki_transformation_output_format.sql
new file mode 100644
index 00000000..3da5170b
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V109__wiki_transformation_output_format.sql
@@ -0,0 +1,12 @@
+-- Output format declared on the template. See h2 sibling for the prose
+-- explanation. MySQL needs the INFORMATION_SCHEMA guard pattern.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation' AND column_name = 'output_format'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation ADD COLUMN output_format VARCHAR(16) NOT NULL DEFAULT 'markdown';
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V10__hook_system.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V10__hook_system.sql
new file mode 100644
index 00000000..bb318483
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V10__hook_system.sql
@@ -0,0 +1,34 @@
+-- V10: 声明式 Hook 系统
+-- mate_hook hook 定义(YAML 文件或 UI 写入)
+-- mate_hook_run hook 触发审计
+-- (原本命名为 V9 但与 V9__usage_cache_tokens.sql 撞号;重命名为 V10 以共存)
+
+CREATE TABLE IF NOT EXISTS mate_hook (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description VARCHAR(512),
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ event_type VARCHAR(64) NOT NULL,
+ match_expression TEXT,
+ action_kind VARCHAR(32) NOT NULL,
+ action_config TEXT NOT NULL,
+ rate_limit_per_min INT DEFAULT 60,
+ timeout_ms INT DEFAULT 3000,
+ source VARCHAR(16) DEFAULT 'db',
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_hook_event_type ON mate_hook (event_type);
+CREATE INDEX IF NOT EXISTS idx_hook_enabled ON mate_hook (enabled);
+
+CREATE TABLE IF NOT EXISTS mate_hook_run (
+ id BIGINT NOT NULL PRIMARY KEY,
+ hook_id BIGINT NOT NULL,
+ event_type VARCHAR(64) NOT NULL,
+ status VARCHAR(16) NOT NULL,
+ duration_ms INT DEFAULT 0,
+ message VARCHAR(512),
+ created_at TIMESTAMP NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_hook_run_hook_id ON mate_hook_run (hook_id);
+CREATE INDEX IF NOT EXISTS idx_hook_run_created ON mate_hook_run (created_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V110__wiki_transformation_run_tokens.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V110__wiki_transformation_run_tokens.sql
new file mode 100644
index 00000000..83ea8ac9
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V110__wiki_transformation_run_tokens.sql
@@ -0,0 +1,31 @@
+-- Record per-run token usage. See h2 sibling for prose explanation.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation_run' AND column_name = 'input_tokens'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation_run ADD COLUMN input_tokens BIGINT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation_run' AND column_name = 'output_tokens'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation_run ADD COLUMN output_tokens BIGINT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation_run' AND column_name = 'total_tokens'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation_run ADD COLUMN total_tokens BIGINT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V111__wiki_transformation_output_schema.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V111__wiki_transformation_output_schema.sql
new file mode 100644
index 00000000..2c728760
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V111__wiki_transformation_output_schema.sql
@@ -0,0 +1,11 @@
+-- Optional JSON Schema column. See h2 sibling for the prose explanation.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation' AND column_name = 'output_schema'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation ADD COLUMN output_schema TEXT DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V112__skill_file.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V112__skill_file.sql
new file mode 100644
index 00000000..13d40225
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V112__skill_file.sql
@@ -0,0 +1,26 @@
+-- V112: persist skill bundle files (scripts/ + references/) in the database.
+--
+-- Until now scripts/references only lived on the local filesystem of whichever
+-- node handled the upload. Multi-instance deployments sharing one MySQL would
+-- have the skill row visible everywhere but the script files only on one node,
+-- so any other node attempting to run a skill script either failed or ran a
+-- stale local copy. Treating the database as the canonical bundle store and
+-- the filesystem as a materialized cache resolves that gap and matches the
+-- existing pattern for SKILL.md (canonical in mate_skill.skill_content,
+-- mirrored to disk by the workspace manager).
+--
+-- TEXT (16MB) comfortably covers the per-file 1MB cap enforced by
+-- ZipSkillFetcher and the 50MB total bundle cap.
+
+CREATE TABLE IF NOT EXISTS mate_skill_file (
+ id BIGINT NOT NULL PRIMARY KEY,
+ skill_id BIGINT NOT NULL,
+ file_path VARCHAR(512) NOT NULL,
+ content TEXT,
+ content_size INT NOT NULL DEFAULT 0,
+ sha256 CHAR(64),
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_skill_file_path ON mate_skill_file (skill_id, file_path);
+CREATE INDEX IF NOT EXISTS idx_skill_file_skill ON mate_skill_file (skill_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V113__workspace_memory_search_and_async_indexes.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V113__workspace_memory_search_and_async_indexes.sql
new file mode 100644
index 00000000..70555f60
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V113__workspace_memory_search_and_async_indexes.sql
@@ -0,0 +1,15 @@
+-- Indexes for two recently-added read paths. Mirrors the H2 file in this
+-- migration set; PostgreSQL supports CREATE INDEX IF NOT EXISTS natively.
+--
+-- (1) idx_workspace_file_agent_filename — accelerates the memory search tool
+-- on mate_workspace_file ("agent_id = ? AND filename LIKE 'prefix%' AND
+-- content LIKE '%term%'").
+--
+-- (2) idx_async_task_conv_status — accelerates listActiveTasks(conversationId)
+-- ("WHERE conversation_id = ? AND status IN ('pending', 'running')").
+-- The existing single-column idx_async_task_conv left the status filter
+-- to a row scan; the compound resolves both in one index seek.
+
+CREATE INDEX IF NOT EXISTS idx_workspace_file_agent_filename ON mate_workspace_file (agent_id, filename);
+
+CREATE INDEX IF NOT EXISTS idx_async_task_conv_status ON mate_async_task (conversation_id, status);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V114__conversation_pinned.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V114__conversation_pinned.sql
new file mode 100644
index 00000000..62a01304
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V114__conversation_pinned.sql
@@ -0,0 +1,12 @@
+-- Per-conversation pin flag. See the h2 sibling for the rationale.
+-- MySQL has no ADD COLUMN IF NOT EXISTS; guard via INFORMATION_SCHEMA.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_conversation' AND column_name = 'pinned'
+ ) THEN
+ ALTER TABLE mate_conversation ADD COLUMN pinned INT DEFAULT 0;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V115__xai_grok_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V115__xai_grok_provider.sql
new file mode 100644
index 00000000..9bc61d57
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V115__xai_grok_provider.sql
@@ -0,0 +1,45 @@
+-- V115: register the xAI (Grok) provider plus its Grok 3 / Grok 4 model catalog.
+--
+-- See the H2 copy for full background. The MySQL copy uses INSERT ... ON
+-- DUPLICATE KEY UPDATE; the api_key column is intentionally omitted from the
+-- update list so existing deployments that have already configured a key keep it.
+
+-- -- Provider --------------------------------------------------------------
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'xai',
+ 'xAI (Grok)',
+ 'xai-',
+ 'OpenAIChatModel',
+ '',
+ 'https://api.x.ai/v1',
+ '{}',
+ 0, 0, 1, 1, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ api_key_prefix = EXCLUDED.api_key_prefix,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+-- -- Model catalog ---------------------------------------------------------
+-- IDs use the 1000000340-1000000343 block reserved for xAI so future Grok
+-- additions can grow contiguously.
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+ (1000000340, 'Grok 4', 'xai', 'grok-4', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000341, 'Grok 4 Fast', 'xai', 'grok-4-fast', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000342, 'Grok 3', 'xai', 'grok-3', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000343, 'Grok 3 Mini', 'xai', 'grok-3-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ update_time = EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V116__conversation_model.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V116__conversation_model.sql
new file mode 100644
index 00000000..6eac1b08
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V116__conversation_model.sql
@@ -0,0 +1,5 @@
+-- See the H2 file for context. KingbaseES (PostgreSQL) supports
+-- ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_conversation ADD COLUMN IF NOT EXISTS model_provider VARCHAR(64);
+ALTER TABLE mate_conversation ADD COLUMN IF NOT EXISTS model_name VARCHAR(128);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V117__skill_lifecycle.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V117__skill_lifecycle.sql
new file mode 100644
index 00000000..01353e19
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V117__skill_lifecycle.sql
@@ -0,0 +1,25 @@
+-- See the H2 file for context. KingbaseES (PostgreSQL) supports
+-- both ADD COLUMN IF NOT EXISTS and CREATE INDEX IF NOT EXISTS natively.
+--
+-- Column types: archived_at / last_activity_at use TIMESTAMP(3) to match
+-- mate_skill_usage_stat.last_loaded_at; lifecycle_state VARCHAR(16);
+-- pinned SMALLINT.
+
+ALTER TABLE mate_skill ADD COLUMN IF NOT EXISTS lifecycle_state VARCHAR(16) DEFAULT 'active';
+ALTER TABLE mate_skill ADD COLUMN IF NOT EXISTS pinned SMALLINT DEFAULT 0;
+ALTER TABLE mate_skill ADD COLUMN IF NOT EXISTS archived_at TIMESTAMP(3) NULL;
+ALTER TABLE mate_skill ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMP(3) NULL;
+
+CREATE INDEX IF NOT EXISTS idx_skill_lifecycle_state ON mate_skill (lifecycle_state);
+CREATE INDEX IF NOT EXISTS idx_skill_last_activity_at ON mate_skill (last_activity_at);
+
+-- One-time backfill: existing rows take their newest usage tick as the
+-- activity anchor. Rows with no usage stat stay NULL and fall through to
+-- create_time at query time via the anchor() helper.
+UPDATE mate_skill SET last_activity_at = (
+ SELECT MAX(last_loaded_at) FROM mate_skill_usage_stat s
+ WHERE s.skill_name = mate_skill.name
+)
+WHERE last_activity_at IS NULL;
+
+UPDATE mate_skill SET lifecycle_state = 'active' WHERE lifecycle_state IS NULL;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V118__purge_model_config_tombstones.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V118__purge_model_config_tombstones.sql
new file mode 100644
index 00000000..d4a6371f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V118__purge_model_config_tombstones.sql
@@ -0,0 +1,15 @@
+-- V118: Purge residual deleted=1 rows from mate_model_config.
+--
+-- V20 retired soft-delete project-wide and physically deleted every deleted=1
+-- row that existed at that point. V81 then re-introduced an isolated tombstone
+-- on id=1000000172 (the bogus 'qwen3-plus' catalog entry) -- keeping the row
+-- around as a logical audit trail. The runtime never re-uses ids and treats
+-- deleted as a vestige now that @TableLogic is gone, so a tombstoned row
+-- has no value beyond leaking into LambdaQueryWrapper queries that don't
+-- explicitly filter deleted=0.
+--
+-- The validateModel uniqueness check fix in #173 plugged one such leak; this
+-- migration eliminates the underlying class of bug by aligning mate_model_config
+-- with the V20 hard-delete posture. Idempotent: a no-op on databases without
+-- tombstones.
+DELETE FROM mate_model_config WHERE deleted = 1;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V119__channel_tool_support.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V119__channel_tool_support.sql
new file mode 100644
index 00000000..28859d81
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V119__channel_tool_support.sql
@@ -0,0 +1,32 @@
+-- V100__channel_tool_support.sql (KingbaseES dialect)
+--
+-- Mirror of the H2 V100, adapted for KingbaseES (PostgreSQL-compatible).
+-- PostgreSQL supports ADD COLUMN IF NOT EXISTS and CREATE INDEX IF NOT EXISTS
+-- natively, so guards are not needed.
+
+-- 1. Add channel_id column if missing
+ALTER TABLE mate_tool ADD COLUMN IF NOT EXISTS channel_id BIGINT NULL;
+
+-- 2. Add channel-id index if missing
+CREATE INDEX IF NOT EXISTS idx_mate_tool_channel ON mate_tool (channel_id);
+
+-- 3. Deduplicate same-name rows before adding the unique index — keep
+-- deleted=0 first, then most recently updated, then largest id.
+DELETE FROM mate_tool
+WHERE id IN (
+ SELECT id FROM (
+ SELECT id,
+ ROW_NUMBER() OVER (
+ PARTITION BY name
+ ORDER BY
+ CASE WHEN deleted = 0 THEN 0 ELSE 1 END,
+ update_time DESC,
+ id DESC
+ ) AS rn
+ FROM mate_tool
+ ) ranked
+ WHERE rn > 1
+);
+
+-- 4. Add unique index on name if missing
+CREATE UNIQUE INDEX IF NOT EXISTS uk_mate_tool_name ON mate_tool (name);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V11__skill_synthesis.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V11__skill_synthesis.sql
new file mode 100644
index 00000000..f67e536d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V11__skill_synthesis.sql
@@ -0,0 +1,23 @@
+-- V11: Auto Skill Synthesis (RFC-023)
+-- Agent 自治创建 skill 后记录来源对话和安全扫描状态
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'source_conversation_id'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN source_conversation_id VARCHAR(64) DEFAULT NULL;
+ END IF;
+END $$;
+
+-- security_scan_status: NULL(旧数据/手动创建) / PASSED / FAILED
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'security_scan_status'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN security_scan_status VARCHAR(16) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V120__agent_goal.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V120__agent_goal.sql
new file mode 100644
index 00000000..20e35c53
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V120__agent_goal.sql
@@ -0,0 +1,75 @@
+-- Persistent goal — see h2/V120__agent_goal.sql for full design notes.
+--
+-- Kingbase/PostgreSQL differences vs H2:
+-- 1. CLOB -> TEXT
+-- 2. TIMESTAMP -> TIMESTAMP(3) for millisecond precision matching V117
+-- 3. BOOLEAN -> SMALLINT
+-- 4. H2 uses a PREDICATE unique index for "one active goal per
+-- conversation"; PostgreSQL does not support filtered unique indexes,
+-- so we emulate it with a STORED generated column that is NULL for
+-- non-active rows + a plain unique index. NULLs are excluded from
+-- uniqueness enforcement by PostgreSQL's default index semantics.
+
+CREATE TABLE IF NOT EXISTS mate_agent_goal (
+ id BIGINT NOT NULL,
+ conversation_id VARCHAR(64) NOT NULL,
+ agent_id BIGINT NOT NULL,
+ workspace_id BIGINT NOT NULL,
+ created_by VARCHAR(64) NOT NULL,
+
+ title VARCHAR(255) NOT NULL,
+ description TEXT NOT NULL,
+ exit_criteria TEXT NULL,
+ success_check_prompt TEXT NULL,
+
+ -- DB values are always lowercase (active|paused|completed|abandoned|
+ -- exhausted) — enforced by the GoalStatus enum's @EnumValue
+ -- annotation. The active_conv_key generated column below depends on
+ -- this convention; any uppercase write would defeat uniqueness.
+ status VARCHAR(16) NOT NULL DEFAULT 'active',
+
+ turn_budget INT NOT NULL DEFAULT 20,
+ turns_used INT NOT NULL DEFAULT 0,
+ llm_call_budget INT NOT NULL DEFAULT 200,
+ agent_llm_calls_used INT NOT NULL DEFAULT 0,
+ eval_llm_calls_used INT NOT NULL DEFAULT 0,
+
+ progress_summary TEXT NULL,
+ completion_score DOUBLE PRECISION NULL,
+ last_evaluation_at TIMESTAMP(3) NULL,
+
+ auto_followup_enabled SMALLINT NOT NULL DEFAULT 0,
+ followup_cooldown_seconds INT NOT NULL DEFAULT 0,
+ last_followup_at TIMESTAMP(3) NULL,
+
+ -- Virtual generated column: NULL for non-active or deleted rows so
+ -- they fall out of the unique-index check. PostgreSQL ignores NULL keys
+ -- for uniqueness, giving us "at most one active row per conversation".
+ active_conv_key VARCHAR(80)
+ GENERATED ALWAYS AS (
+ CASE WHEN status = 'active' AND deleted = 0
+ THEN conversation_id ELSE NULL END
+ ) STORED,
+
+ version INT NOT NULL DEFAULT 0,
+ deleted SMALLINT NOT NULL DEFAULT 0,
+ create_time TIMESTAMP(3) NOT NULL,
+ update_time TIMESTAMP(3) NOT NULL,
+
+ PRIMARY KEY (id)
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_agent_goal_active_conv ON mate_agent_goal (active_conv_key);
+CREATE INDEX IF NOT EXISTS idx_agent_goal_conv ON mate_agent_goal (conversation_id, status);
+CREATE INDEX IF NOT EXISTS idx_agent_goal_status ON mate_agent_goal (status, last_evaluation_at);
+CREATE INDEX IF NOT EXISTS idx_agent_goal_owner ON mate_agent_goal (created_by, status);
+
+CREATE TABLE IF NOT EXISTS mate_agent_goal_event (
+ id BIGINT NOT NULL,
+ goal_id BIGINT NOT NULL,
+ event_type VARCHAR(32) NOT NULL,
+ message_id BIGINT NULL,
+ detail_json TEXT NULL,
+ create_time TIMESTAMP(3) NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS idx_agent_goal_event_goal ON mate_agent_goal_event (goal_id, id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V121__tool_disclosure_tier.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V121__tool_disclosure_tier.sql
new file mode 100644
index 00000000..590ccde5
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V121__tool_disclosure_tier.sql
@@ -0,0 +1,20 @@
+-- V121__tool_disclosure_tier.sql (KingbaseES dialect)
+--
+-- Mirror of the H2 V121, adapted for KingbaseES (PostgreSQL-compatible).
+-- PostgreSQL supports ADD COLUMN IF NOT EXISTS natively.
+--
+-- See the H2 file for the column semantics.
+
+-- 1. mate_tool.disclosure_tier (default 'core')
+ALTER TABLE mate_tool ADD COLUMN IF NOT EXISTS disclosure_tier VARCHAR(16) DEFAULT 'core';
+
+-- 2. mate_mcp_server.disclosure_tier (default 'core' — MCP tools stay directly
+-- callable; an admin can move a noisy server to extension)
+ALTER TABLE mate_mcp_server ADD COLUMN IF NOT EXISTS disclosure_tier VARCHAR(16) DEFAULT 'core';
+
+-- 3. Seed the heavy generative / browser tools as extension.
+-- mate_tool.name stores the Java class name (not the @Tool function name).
+UPDATE mate_tool
+SET disclosure_tier = 'extension'
+WHERE name IN ('ImageGenerateTool', 'MusicGenerateTool', 'VideoGenerateTool', 'Model3dGenerateTool', 'BrowserUseTool')
+ AND (disclosure_tier IS NULL OR disclosure_tier = 'core');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V122__fix_generative_tool_tier_names.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V122__fix_generative_tool_tier_names.sql
new file mode 100644
index 00000000..75b8d235
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V122__fix_generative_tool_tier_names.sql
@@ -0,0 +1,13 @@
+-- V122__fix_generative_tool_tier_names.sql (MySQL dialect)
+--
+-- Corrective migration. The first cut of V121 seeded the generative / browser
+-- tools as extension using their @Tool function names (image_generate, ...),
+-- but mate_tool.name stores the Java class name (ImageGenerateTool, ...), so the
+-- UPDATE matched no rows on databases that ran that early version. Re-apply the
+-- seed by class name. Idempotent: only promotes core → extension and leaves any
+-- admin-set value untouched.
+
+UPDATE mate_tool
+SET disclosure_tier = 'extension'
+WHERE name IN ('ImageGenerateTool', 'MusicGenerateTool', 'VideoGenerateTool', 'Model3dGenerateTool', 'BrowserUseTool')
+ AND (disclosure_tier IS NULL OR disclosure_tier = 'core');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V123__conversation_progress_ledger.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V123__conversation_progress_ledger.sql
new file mode 100644
index 00000000..6993f5fc
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V123__conversation_progress_ledger.sql
@@ -0,0 +1,5 @@
+-- V100: per-conversation progress ledger (see the H2 copy for full background).
+--
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_conversation ADD COLUMN IF NOT EXISTS progress_ledger TEXT NULL;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V124__agent_max_iterations_150.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V124__agent_max_iterations_150.sql
new file mode 100644
index 00000000..a8a55207
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V124__agent_max_iterations_150.sql
@@ -0,0 +1,6 @@
+-- V124: bump default agents' max_iterations 100 → 150 (see H2 copy for full
+-- background). Same idempotent UPDATE — H2 and MySQL accept identical syntax
+-- for this UPDATE so no dialect-specific guard is needed.
+
+UPDATE mate_agent SET max_iterations = 150
+WHERE id IN (1000000001, 1000000002, 1000000003) AND max_iterations = 100;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V125__agent_workspace_base_path.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V125__agent_workspace_base_path.sql
new file mode 100644
index 00000000..a434a987
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V125__agent_workspace_base_path.sql
@@ -0,0 +1,4 @@
+-- V125: Add workspace_base_path column to mate_agent for Agent-level directory override.
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS workspace_base_path VARCHAR(512) DEFAULT NULL;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V126__agent_binding_disabled_flags.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V126__agent_binding_disabled_flags.sql
new file mode 100644
index 00000000..d93c5d47
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V126__agent_binding_disabled_flags.sql
@@ -0,0 +1,13 @@
+-- V126: Two binding-mode flags on mate_agent (KingbaseES).
+--
+-- skills_disabled / tools_disabled flip the "zero binding rows" semantic from
+-- "inherit every globally-enabled capability" to "this agent has explicitly
+-- opted out". Without these columns, an operator who wanted an agent with no
+-- skills had to bind a dummy skill — otherwise the runtime fell back to the
+-- global default and every skill's catalog entry got injected into the system
+-- prompt (issue #184).
+--
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS skills_disabled SMALLINT NOT NULL DEFAULT 0;
+ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS tools_disabled SMALLINT NOT NULL DEFAULT 0;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V127__approval_auto_grant.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V127__approval_auto_grant.sql
new file mode 100644
index 00000000..399a239f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V127__approval_auto_grant.sql
@@ -0,0 +1,25 @@
+-- V127: Approval auto-grant table (MySQL dialect).
+-- Idempotent: outer CREATE TABLE uses IF NOT EXISTS; inline KEY clauses
+-- only execute on first creation, so re-running this migration is safe.
+CREATE TABLE IF NOT EXISTS mate_approval_grant (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT NOT NULL,
+ scope_type VARCHAR(32) NOT NULL,
+ scope_id VARCHAR(64) NOT NULL,
+ tool_name VARCHAR(128) DEFAULT NULL,
+ rule_id VARCHAR(128) DEFAULT NULL,
+ max_severity VARCHAR(16) NOT NULL,
+ grant_kind VARCHAR(24) NOT NULL,
+ expire_at TIMESTAMP DEFAULT NULL,
+ granted_by BIGINT NOT NULL,
+ granted_at TIMESTAMP NOT NULL,
+ revoked SMALLINT NOT NULL DEFAULT 0,
+ revoked_by BIGINT DEFAULT NULL,
+ revoked_at TIMESTAMP DEFAULT NULL,
+ note VARCHAR(500) DEFAULT NULL,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_grant_scope ON mate_approval_grant (workspace_id, scope_type, scope_id, tool_name, revoked, deleted);
+CREATE INDEX IF NOT EXISTS idx_grant_expire ON mate_approval_grant (expire_at, revoked, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V128__approval_resolution_log.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V128__approval_resolution_log.sql
new file mode 100644
index 00000000..af437a34
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V128__approval_resolution_log.sql
@@ -0,0 +1,25 @@
+-- V128: Approval resolution log table (MySQL dialect).
+CREATE TABLE IF NOT EXISTS mate_approval_resolution_log (
+ id BIGINT NOT NULL PRIMARY KEY,
+ -- Nullable: HARD_BLOCK can fire before workspace resolution; see H2 migration
+ -- for full rationale. Per-workspace Dashboard queries filter on workspace_id
+ -- and skip null rows; the global HARD_BLOCK panel surfaces them.
+ workspace_id BIGINT DEFAULT NULL,
+ conversation_id VARCHAR(128) DEFAULT NULL,
+ agent_id VARCHAR(64) DEFAULT NULL,
+ user_id VARCHAR(64) DEFAULT NULL,
+ tool_call_id VARCHAR(64) DEFAULT NULL,
+ tool_name VARCHAR(128) NOT NULL,
+ max_severity VARCHAR(16) DEFAULT NULL,
+ rule_ids VARCHAR(512) DEFAULT NULL,
+ decision_source VARCHAR(24) NOT NULL,
+ grant_id BIGINT DEFAULT NULL,
+ pending_id VARCHAR(32) DEFAULT NULL,
+ args_preview VARCHAR(500) DEFAULT NULL,
+ note VARCHAR(500) DEFAULT NULL,
+ create_time TIMESTAMP NOT NULL,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_resolution_workspace_time ON mate_approval_resolution_log (workspace_id, create_time);
+CREATE INDEX IF NOT EXISTS idx_resolution_grant ON mate_approval_resolution_log (grant_id);
+CREATE INDEX IF NOT EXISTS idx_resolution_pending ON mate_approval_resolution_log (pending_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V129__wiki_page_broken_links.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V129__wiki_page_broken_links.sql
new file mode 100644
index 00000000..49aa9712
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V129__wiki_page_broken_links.sql
@@ -0,0 +1,7 @@
+-- V129: Persisted wikilink lint state — KingbaseES dialect.
+--
+-- See h2/V129__wiki_page_broken_links.sql for column semantics.
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_wiki_page ADD COLUMN IF NOT EXISTS broken_links JSONB DEFAULT NULL;
+ALTER TABLE mate_wiki_page ADD COLUMN IF NOT EXISTS broken_links_scanned_at TIMESTAMP(3) DEFAULT NULL;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V12__wiki_chunk_table.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V12__wiki_chunk_table.sql
new file mode 100644
index 00000000..5276d434
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V12__wiki_chunk_table.sql
@@ -0,0 +1,22 @@
+-- V12: Wiki chunk persistence (RFC-013 minimal slice → enables RFC-011 embedding)
+CREATE TABLE IF NOT EXISTS mate_wiki_chunk (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ raw_id BIGINT NOT NULL,
+ ordinal INT NOT NULL,
+ content TEXT NOT NULL,
+ char_count INT NOT NULL,
+ start_offset INT NOT NULL,
+ end_offset INT NOT NULL,
+ content_hash VARCHAR(64) NOT NULL,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- MySQL lacks CREATE INDEX IF NOT EXISTS; use INFORMATION_SCHEMA.STATISTICS guard instead.
+CREATE INDEX IF NOT EXISTS idx_wiki_chunk_kb ON mate_wiki_chunk (kb_id);
+
+CREATE INDEX IF NOT EXISTS idx_wiki_chunk_raw ON mate_wiki_chunk (raw_id);
+
+CREATE INDEX IF NOT EXISTS idx_wiki_chunk_hash ON mate_wiki_chunk (content_hash);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V130__agent_primary_kb.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V130__agent_primary_kb.sql
new file mode 100644
index 00000000..b2e53473
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V130__agent_primary_kb.sql
@@ -0,0 +1,26 @@
+-- V129: Store the per-agent primary wiki KB on mate_agent (KingbaseES).
+--
+-- Knowledge bases remain workspace-shared; this field only chooses the
+-- default KB for wiki tools when no kbName/kbId is specified.
+
+ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS primary_kb_id BIGINT DEFAULT NULL;
+CREATE INDEX IF NOT EXISTS idx_agent_primary_kb ON mate_agent (primary_kb_id);
+
+UPDATE mate_agent a
+SET primary_kb_id = (
+ SELECT kb.id
+ FROM mate_wiki_knowledge_base kb
+ WHERE kb.agent_id = a.id
+ AND (kb.workspace_id IS NULL OR kb.workspace_id = a.workspace_id)
+ AND kb.deleted = 0
+ ORDER BY kb.update_time DESC
+ LIMIT 1
+)
+WHERE a.primary_kb_id IS NULL
+ AND EXISTS (
+ SELECT 1
+ FROM mate_wiki_knowledge_base kb
+ WHERE kb.agent_id = a.id
+ AND (kb.workspace_id IS NULL OR kb.workspace_id = a.workspace_id)
+ AND kb.deleted = 0
+ );
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V131__claude_48_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V131__claude_48_models.sql
new file mode 100644
index 00000000..d8594aa7
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V131__claude_48_models.sql
@@ -0,0 +1,36 @@
+-- Add Claude Opus 4.8 (regular + -fast variant) model entries to
+-- mate_model_config for existing deployments. New installs pick these up via
+-- DatabaseBootstrapRunner from data-mysql-{en,zh}.sql; this migration covers
+-- operators who already have earlier Flyway versions applied.
+--
+-- Claude 4.8 inherits 4.7's strict API contract: temperature / top_p / top_k
+-- must be NULL (otherwise HTTP 400), and the "xhigh" thinking tier is
+-- available. Both are handled in AnthropicChatModelBuilder via the
+-- isClaude47OrLater() detector.
+--
+-- INSERT ... ON CONFLICT DO UPDATE is the PostgreSQL idempotent upsert.
+-- Same V number is used in h2/ for cross-dialect parity.
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+-- Direct Anthropic
+(1000000290, 'Claude Opus 4.8', 'anthropic', 'claude-opus-4-8', 'Anthropic Claude Opus 4.8 (xhigh adaptive thinking)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000291, 'Claude Opus 4.8 Fast', 'anthropic', 'claude-opus-4-8-fast', 'Claude Opus 4.8 fast variant (higher output speed, 2x pricing)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- OpenRouter passthrough
+(1000000292, 'Claude Opus 4.8', 'openrouter', 'anthropic/claude-opus-4-8', 'Claude Opus 4.8 via OpenRouter', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000293, 'Claude Opus 4.8 Fast', 'openrouter', 'anthropic/claude-opus-4-8-fast', 'Claude Opus 4.8 fast variant via OpenRouter', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- Claude Code OAuth (Pro/Max subscription)
+(1000000294, 'Claude Opus 4.8', 'anthropic-claude-code', 'claude-opus-4-8', 'Claude Opus 4.8 via Claude Code Pro/Max subscription', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ provider = EXCLUDED.provider,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ temperature = EXCLUDED.temperature,
+ max_tokens = EXCLUDED.max_tokens,
+ top_p = EXCLUDED.top_p,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ is_default = EXCLUDED.is_default,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V132__memory_consolidation_cron_tier_discipline.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V132__memory_consolidation_cron_tier_discipline.sql
new file mode 100644
index 00000000..dc961ad0
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V132__memory_consolidation_cron_tier_discipline.sql
@@ -0,0 +1,13 @@
+-- Update the daily "memory consolidation" cron prompt on existing databases so it
+-- keeps project-specific volatile facts (codenames, tech stacks, per-project
+-- decisions) out of the always-on MEMORY.md. Seed scripts only run on fresh
+-- installs, so existing rows need this data migration to pick up the new wording.
+-- Scoped to the original default text so user-edited prompts are left untouched.
+
+UPDATE mate_cron_job
+SET trigger_message = '请回顾你最近的 memory/ 日记文件,将反复出现的重要信息(用户偏好、稳定事实、经验教训、工作流)提炼整合到 MEMORY.md 中。注意:MEMORY.md 会被注入每一次对话,只整合跨项目长期稳定的信息;具体项目的代号、名称、技术栈、仓库、单项目的指标/预算/团队/上线日期或只对某个项目成立的决策等易变事实,不要写入 MEMORY.md(会随项目切换互相冲突、导致张冠李戴),应留在 daily note 或通过结构化 project 记忆维护。判定口诀:换一个项目后仍成立才进 MEMORY.md。保留日记原文不动,只更新 MEMORY.md。完成后简要说明做了哪些整合。'
+WHERE trigger_message = '请回顾你最近的 memory/ 日记文件,将反复出现的重要信息(用户偏好、稳定事实、经验教训、工作流)提炼整合到 MEMORY.md 中。保留日记原文不动,只更新 MEMORY.md。完成后简要说明做了哪些整合。';
+
+UPDATE mate_cron_job
+SET trigger_message = 'Review your recent memory/ daily note files and consolidate recurring important information (user preferences, stable facts, lessons learned, workflows) into MEMORY.md. Note: MEMORY.md is injected into every conversation, so only consolidate cross-project, long-term stable information; do NOT write project-specific volatile facts into MEMORY.md (project codenames, names, tech stacks, repos, a single project''s metrics/budget/team/launch date, or decisions that hold only for one project) — they conflict across projects and cause mix-ups. Keep those in the daily note or maintain them via structured project memory. Rule of thumb: only facts that still hold after switching projects belong in MEMORY.md. Keep the original daily notes intact, only update MEMORY.md. Briefly describe what consolidations were made.'
+WHERE trigger_message = 'Review your recent memory/ daily note files and consolidate recurring important information (user preferences, stable facts, lessons learned, workflows) into MEMORY.md. Keep the original daily notes intact, only update MEMORY.md. Briefly describe what consolidations were made.';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V133__wiki_agent_page_type_permission.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V133__wiki_agent_page_type_permission.sql
new file mode 100644
index 00000000..8a184454
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V133__wiki_agent_page_type_permission.sql
@@ -0,0 +1,23 @@
+-- V133: Per-agent, per-KB, per-pageType permission for wiki tools.
+-- Read permission filters retrieval/listing; write permission gates the
+-- create/compile/delete/archive/enrich/transformation tools. A row with
+-- page_type='*' is the agent's KB-wide default; an exact page_type row is
+-- more specific and wins over '*'. Unconfigured (no rows) falls back to the
+-- KB-level defaultReadPolicy stored in the KB config.
+
+CREATE TABLE IF NOT EXISTS mate_wiki_agent_page_type_permission (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ kb_id BIGINT NOT NULL,
+ page_type VARCHAR(64) NOT NULL,
+ can_read SMALLINT NOT NULL DEFAULT 1,
+ can_create SMALLINT NOT NULL DEFAULT 0,
+ can_update SMALLINT NOT NULL DEFAULT 0,
+ can_delete SMALLINT NOT NULL DEFAULT 0,
+ write_policy VARCHAR(32) NOT NULL DEFAULT 'approval_required',
+ create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_agent_ptperm ON mate_wiki_agent_page_type_permission (agent_id, kb_id, page_type, deleted);
+CREATE INDEX IF NOT EXISTS idx_wiki_ptperm_agent_kb ON mate_wiki_agent_page_type_permission (agent_id, kb_id, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V134__wiki_page_type_profile.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V134__wiki_page_type_profile.sql
new file mode 100644
index 00000000..00275df8
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V134__wiki_page_type_profile.sql
@@ -0,0 +1,78 @@
+-- V134: KB-scoped pageType profile + structured page metadata columns.
+-- See the H2 file for the design rationale. PostgreSQL/KingbaseES uses a STORED
+-- generated column for the "one enabled profile per KB" constraint, and an
+-- information_schema guard for each idempotent ADD COLUMN (PostgreSQL has no
+-- ADD COLUMN IF NOT EXISTS).
+
+CREATE TABLE IF NOT EXISTS mate_wiki_page_type_profile (
+ id BIGINT NOT NULL,
+ kb_id BIGINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ version INT NOT NULL DEFAULT 1,
+ config_json TEXT NOT NULL,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0,
+ -- Yields kb_id only for the live-enabled row; NULL otherwise. PostgreSQL
+ -- ignores NULL keys for uniqueness, giving "at most one enabled per KB".
+ enabled_kb BIGINT
+ GENERATED ALWAYS AS (
+ CASE WHEN enabled = 1 AND deleted = 0 THEN kb_id ELSE NULL END
+ ) STORED,
+ PRIMARY KEY (id)
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_ptprofile_name ON mate_wiki_page_type_profile (kb_id, name, deleted);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_ptprofile_enabled ON mate_wiki_page_type_profile (enabled_kb);
+CREATE INDEX IF NOT EXISTS idx_wiki_ptprofile_kb ON mate_wiki_page_type_profile (kb_id, enabled, deleted);
+
+-- Structured page metadata columns (idempotent adds).
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'metadata_json'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN metadata_json TEXT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'metadata_validation_status'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN metadata_validation_status VARCHAR(32);
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'metadata_validation_json'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN metadata_validation_json TEXT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'template_key'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN template_key VARCHAR(128);
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'profile_version'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN profile_version INT;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V135__wiki_layered_knowledge.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V135__wiki_layered_knowledge.sql
new file mode 100644
index 00000000..ec0ea987
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V135__wiki_layered_knowledge.sql
@@ -0,0 +1,56 @@
+-- V135: Layered knowledge (fact / experience) + page dependency graph.
+-- See the H2 file for rationale. MySQL uses INFORMATION_SCHEMA guards for the
+-- idempotent column adds.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'knowledge_layer'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN knowledge_layer VARCHAR(16);
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'depends_on_json'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN depends_on_json TEXT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'stale'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN stale SMALLINT NOT NULL DEFAULT 0;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'stale_reason_json'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN stale_reason_json TEXT;
+ END IF;
+END $$;
+
+CREATE TABLE IF NOT EXISTS mate_wiki_page_dependency (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ page_id BIGINT NOT NULL,
+ depends_on_page_id BIGINT NOT NULL,
+ dependency_type VARCHAR(32) NOT NULL DEFAULT 'fact',
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_page_dep ON mate_wiki_page_dependency (page_id, depends_on_page_id, dependency_type, deleted);
+CREATE INDEX IF NOT EXISTS idx_wiki_page_dep_reverse ON mate_wiki_page_dependency (kb_id, depends_on_page_id, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V136__wiki_pipeline_runtime.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V136__wiki_pipeline_runtime.sql
new file mode 100644
index 00000000..9965511a
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V136__wiki_pipeline_runtime.sql
@@ -0,0 +1,55 @@
+-- V136: Wiki pipeline runtime — definitions, runs, and per-step runs.
+-- See the H2 file for design rationale.
+
+CREATE TABLE IF NOT EXISTS mate_wiki_pipeline_definition (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ owner_agent_id BIGINT NOT NULL,
+ trigger_type VARCHAR(32) NOT NULL,
+ trigger_config_json TEXT,
+ steps_json TEXT NOT NULL,
+ dedup_window_seconds INT NOT NULL DEFAULT 0,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_pipeline_def_name ON mate_wiki_pipeline_definition (kb_id, name, deleted);
+CREATE INDEX IF NOT EXISTS idx_wiki_pipeline_def_trigger ON mate_wiki_pipeline_definition (kb_id, trigger_type, enabled, deleted);
+
+CREATE TABLE IF NOT EXISTS mate_wiki_pipeline_run (
+ id BIGINT NOT NULL PRIMARY KEY,
+ definition_id BIGINT NOT NULL,
+ kb_id BIGINT NOT NULL,
+ status VARCHAR(16) NOT NULL,
+ trigger_type VARCHAR(32) NOT NULL,
+ trigger_subject VARCHAR(128) NOT NULL,
+ trigger_bucket VARCHAR(64) NOT NULL,
+ trigger_payload_json TEXT,
+ input_json TEXT,
+ output_json TEXT,
+ error_message VARCHAR(2048),
+ started_at TIMESTAMP(3),
+ finished_at TIMESTAMP(3),
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_pipeline_run_dedup ON mate_wiki_pipeline_run (definition_id, trigger_type, trigger_subject, trigger_bucket, deleted);
+CREATE INDEX IF NOT EXISTS idx_wiki_pipeline_run_def ON mate_wiki_pipeline_run (definition_id, status);
+
+CREATE TABLE IF NOT EXISTS mate_wiki_pipeline_step_run (
+ id BIGINT NOT NULL PRIMARY KEY,
+ run_id BIGINT NOT NULL,
+ step_id VARCHAR(128) NOT NULL,
+ executor VARCHAR(32) NOT NULL,
+ status VARCHAR(16) NOT NULL,
+ input_json TEXT,
+ output_json TEXT,
+ error_message VARCHAR(2048),
+ started_at TIMESTAMP(3),
+ finished_at TIMESTAMP(3),
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wiki_pipeline_step_run ON mate_wiki_pipeline_step_run (run_id, status);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V137__memory_owner_scope.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V137__memory_owner_scope.sql
new file mode 100644
index 00000000..ed7a0033
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V137__memory_owner_scope.sql
@@ -0,0 +1,107 @@
+-- V137: Per-owner memory isolation with a three-state visibility scope (MySQL).
+--
+-- See the H2 counterpart for the full rationale. MySQL has no
+-- "ADD COLUMN IF NOT EXISTS", so each column/index is guarded with an
+-- INFORMATION_SCHEMA existence check + prepared statement for idempotency.
+--
+-- Existing rows are backfilled to scope='TEAM' by the NOT NULL DEFAULT so that
+-- upgrading does NOT hide previously-shared memory.
+
+-- ---------- mate_workspace_file ----------
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_workspace_file' AND column_name = 'owner_key'
+ ) THEN
+ ALTER TABLE mate_workspace_file ADD COLUMN owner_key VARCHAR(128) NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_workspace_file' AND column_name = 'scope'
+ ) THEN
+ ALTER TABLE mate_workspace_file ADD COLUMN scope VARCHAR(16) NOT NULL DEFAULT 'TEAM';
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_workspace_file_scope_owner ON mate_workspace_file (agent_id, scope, owner_key);
+
+-- Shared rows use the '' sentinel (not NULL) so the unique index below treats
+-- one shared row per filename as a single slot (NULLs are distinct in unique indexes).
+UPDATE mate_workspace_file SET owner_key = '' WHERE owner_key IS NULL;
+
+-- De-duplicate before adding the unique index: the table never had a unique
+-- constraint and the service layer was check-then-insert, so historical
+-- duplicates may exist. Keep the most recently inserted row per
+-- (agent_id, filename, owner_key); drop the rest. The extra derived-table wrap
+-- is required so MySQL doesn't reject selecting from the table being deleted.
+--
+-- IRREVERSIBLE: this keeps MAX(id) (newest row) and PERMANENTLY deletes the
+-- other rows in a duplicate group — their content / enabled / sort_order are
+-- not preserved or merged. Duplicates are NOT expected (every write path is
+-- check-then-insert), so this is a safety net to guarantee the index builds,
+-- not a routine merge. If a deployment knowingly relies on duplicate rows,
+-- reconcile them manually before upgrading.
+DELETE FROM mate_workspace_file
+WHERE id NOT IN (
+ SELECT keep_id FROM (
+ SELECT MAX(id) AS keep_id
+ FROM mate_workspace_file
+ GROUP BY agent_id, filename, owner_key
+ ) t
+);
+
+-- One row per (agent, filename, owner): one shared row + one row per PERSONAL
+-- owner. Hardens the check-then-insert in saveFile/saveMemoryFile against
+-- concurrent / multi-node duplicates.
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workspace_file_owner ON mate_workspace_file (agent_id, filename, owner_key);
+
+-- ---------- mate_memory_recall ----------
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_memory_recall' AND column_name = 'owner_key'
+ ) THEN
+ ALTER TABLE mate_memory_recall ADD COLUMN owner_key VARCHAR(128) NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_memory_recall' AND column_name = 'scope'
+ ) THEN
+ ALTER TABLE mate_memory_recall ADD COLUMN scope VARCHAR(16) NOT NULL DEFAULT 'TEAM';
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_memory_recall_scope_owner ON mate_memory_recall (agent_id, scope, owner_key);
+
+-- ---------- mate_fact ----------
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_fact' AND column_name = 'owner_key'
+ ) THEN
+ ALTER TABLE mate_fact ADD COLUMN owner_key VARCHAR(128) NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_fact' AND column_name = 'scope'
+ ) THEN
+ ALTER TABLE mate_fact ADD COLUMN scope VARCHAR(16) NOT NULL DEFAULT 'TEAM';
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_fact_scope_owner ON mate_fact (agent_id, scope, owner_key);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V138__rename_search_tool_to_web_search.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V138__rename_search_tool_to_web_search.sql
new file mode 100644
index 00000000..835cf030
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V138__rename_search_tool_to_web_search.sql
@@ -0,0 +1,7 @@
+-- Rename the built-in web-search tool from "search" to "web_search".
+-- DashScope's native protocol reserves the function name "search" and rejects any
+-- request that declares a tool with that name ("InvalidParameter: Tool names are not
+-- allowed to be [search]"), which broke tool use for every qwen/DashScope-native model
+-- that had this tool bound. Migrate existing agent bindings to the new name so they
+-- keep resolving after the tool was renamed in code. Idempotent.
+UPDATE mate_agent_tool SET tool_name = 'web_search' WHERE tool_name = 'search';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V139__mcp_default_read_timeout_60s.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V139__mcp_default_read_timeout_60s.sql
new file mode 100644
index 00000000..541bc6dc
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V139__mcp_default_read_timeout_60s.sql
@@ -0,0 +1,8 @@
+-- Raise the default per-request (read) timeout for MCP servers from 30s to 60s.
+-- A 30s ceiling cut off MCP tools whose single callTool round-trip legitimately
+-- runs longer (data-heavy or compute-heavy tools), surfacing as a request timeout
+-- with no retry. The application layer already falls back to 60s when the column
+-- is null; this aligns the schema default so the value is consistent everywhere.
+-- Only changes the column default for newly inserted rows — existing rows keep
+-- whatever value they were given. Idempotent.
+ALTER TABLE mate_mcp_server ALTER COLUMN read_timeout_seconds SET DEFAULT 60;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V13__wiki_chunk_embedding.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V13__wiki_chunk_embedding.sql
new file mode 100644
index 00000000..e5e16dac
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V13__wiki_chunk_embedding.sql
@@ -0,0 +1,21 @@
+-- V13: Add embedding column to mate_wiki_chunk (RFC-011 Phase 2)
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'embedding'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN embedding BYTEA DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'embedding_model'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN embedding_model VARCHAR(64) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V140__goal_criteria_checklist.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V140__goal_criteria_checklist.sql
new file mode 100644
index 00000000..0dc48a13
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V140__goal_criteria_checklist.sql
@@ -0,0 +1,19 @@
+-- V140: Structured, checkable criteria for goals (MySQL).
+--
+-- See the H2 counterpart for the full rationale. MySQL has no
+-- "ADD COLUMN IF NOT EXISTS", so the column is guarded with an
+-- INFORMATION_SCHEMA existence check + prepared statement for idempotency.
+--
+-- The column holds the goal's checklist as JSON:
+-- [{ "id": "C1", "text": "...", "passed": false, "evidence": "" }, ...]
+-- Additive and nullable, so existing goals load unchanged (a NULL list
+-- bootstraps on first evaluation).
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_agent_goal' AND column_name = 'criteria'
+ ) THEN
+ ALTER TABLE mate_agent_goal ADD COLUMN criteria JSON NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V141__agent_wiki_kb_scope.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V141__agent_wiki_kb_scope.sql
new file mode 100644
index 00000000..4a321bb8
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V141__agent_wiki_kb_scope.sql
@@ -0,0 +1,24 @@
+-- V141: Per-agent knowledge base access scope for wiki tools (KingbaseES / PostgreSQL 兼容).
+--
+-- Knowledge bases are workspace-shared, so by default every agent in a
+-- workspace can reach every KB in it. This table lets an operator pin an
+-- agent to a subset of KBs: once at least one enabled row exists for an
+-- agent, the wiki tools (list/search/read/write) can only see and target
+-- those KBs. No rows for an agent = unrestricted (workspace-wide), which
+-- keeps every pre-existing agent behaving exactly as before.
+--
+-- The default KB an agent's wiki tools fall back to when no kbId/kbName is
+-- given still lives on mate_agent.primary_kb_id; this table only narrows the
+-- visible set, and the primary is expected to be one of the scoped KBs.
+
+CREATE TABLE IF NOT EXISTS mate_agent_wiki_kb (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ kb_id BIGINT NOT NULL,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_agent_wiki_kb ON mate_agent_wiki_kb (agent_id, kb_id, deleted);
+CREATE INDEX IF NOT EXISTS idx_agent_wiki_kb_agent ON mate_agent_wiki_kb (agent_id, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V142__wiki_transformation_target_page_type.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V142__wiki_transformation_target_page_type.sql
new file mode 100644
index 00000000..dd693ee0
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V142__wiki_transformation_target_page_type.sql
@@ -0,0 +1,13 @@
+-- Optional target pageType for a transformation whose output_target='page'.
+-- See the h2 sibling migration for the prose explanation.
+-- MySQL INFORMATION_SCHEMA guard converted to plpgsql DO block.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_transformation' AND column_name = 'target_page_type'
+ ) THEN
+ ALTER TABLE mate_wiki_transformation ADD COLUMN target_page_type VARCHAR(64) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V143__register_code_execute_tool.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V143__register_code_execute_tool.sql
new file mode 100644
index 00000000..b8914fbf
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V143__register_code_execute_tool.sql
@@ -0,0 +1,6 @@
+-- V143: Register CodeExecuteTool as a built-in tool.
+-- ON DUPLICATE KEY UPDATE is the MySQL idempotent upsert.
+-- Converted to: ON CONFLICT DO UPDATE with EXCLUDED references (KingbaseES / PostgreSQL).
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000023, 'CodeExecuteTool', 'Code Execute', 'Execute a snippet of code (python, bash, or node) that the agent writes on the fly. Lets a documentation-only skill be acted on by running the code its instructions describe. Dangerous operations trigger approval.', 'builtin', 'codeExecuteTool', '🧑💻', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V144__ckjia_mcp_fix_production_endpoint.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V144__ckjia_mcp_fix_production_endpoint.sql
new file mode 100644
index 00000000..26576cc8
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V144__ckjia_mcp_fix_production_endpoint.sql
@@ -0,0 +1,28 @@
+-- Fix the ckjia-shopping MCP seed to its real production endpoint.
+--
+-- V85 seeded a dev/test placeholder (sse + http://localhost:8085/sse +
+-- "Bearer ${CKJIA_MCP_KEY}"), which can never connect out of the box, so the
+-- 参考价 / price-comparison skill stayed unusable until an admin hand-edited it.
+-- The official CKJIA SaaS endpoint is Streamable HTTP at
+-- https://m.ckjia.com/api/ai/mcp and needs no Authorization header.
+--
+-- Also raises both timeouts to 60s: the price-aggregation round-trip
+-- (multi-platform search) legitimately runs longer than the old 30s ceiling.
+--
+-- SAFETY: only rewrites rows that still carry the untouched dev placeholder
+-- URL, so an admin who already pointed ckjia-shopping at a private CKJIA
+-- deployment (or the SaaS URL) is left completely alone. Idempotent — after it
+-- runs the URL no longer matches the WHERE clause. `enabled` is deliberately
+-- not changed: the server stays opt-in.
+UPDATE mate_mcp_server
+SET transport = 'streamable_http',
+ url = 'https://m.ckjia.com/api/ai/mcp',
+ headers_json = NULL,
+ connect_timeout_seconds = 60,
+ read_timeout_seconds = 60,
+ last_status = 'disconnected',
+ last_error = NULL,
+ description = 'CKJIA price comparison MCP server (Streamable HTTP). Disabled by default — enable it in Settings > MCP Connections to use the 参考价 shopping skill.',
+ update_time = NOW()
+WHERE name = 'ckjia-shopping'
+ AND url = 'http://localhost:8085/sse';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V145__claude_fable_5_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V145__claude_fable_5_models.sql
new file mode 100644
index 00000000..cd0a7ead
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V145__claude_fable_5_models.sql
@@ -0,0 +1,37 @@
+-- Add Claude Fable 5 model entries to mate_model_config. Unlike earlier Claude
+-- families, the Fable rows live ONLY here, not in the data-kingbase-{en,zh}.sql
+-- seed: Flyway runs every version (V1..) on a fresh database, so the migration
+-- seeds new installs and upgrades existing deployments alike. The trade-off is
+-- that the description below is English-only (seed files carry localized copy).
+--
+-- Fable 5 is a reasoning-first model with a 1M-token context window and native
+-- vision input. It follows the same strict API contract as Claude 4.7+:
+-- temperature / top_p / top_k must be NULL (otherwise HTTP 400), and the
+-- "xhigh" adaptive thinking tier is available. Both are handled in
+-- AnthropicChatModelBuilder via the isClaudeFable() / isClaude47OrLater()
+-- detectors. Vision capability is resolved in ModelCapabilityService.
+--
+-- ON CONFLICT DO UPDATE is the PostgreSQL idempotent upsert.
+-- Same V number is used in h2/ for cross-dialect parity.
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+-- Direct Anthropic
+(1000000300, 'Claude Fable 5', 'anthropic', 'claude-fable-5', 'Anthropic Claude Fable 5 (1M context, vision, xhigh adaptive thinking)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- OpenRouter passthrough
+(1000000301, 'Claude Fable 5', 'openrouter', 'anthropic/claude-fable-5', 'Claude Fable 5 via OpenRouter', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+-- Claude Code OAuth (Pro/Max subscription)
+(1000000302, 'Claude Fable 5', 'anthropic-claude-code', 'claude-fable-5', 'Claude Fable 5 via Claude Code Pro/Max subscription', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ provider = EXCLUDED.provider,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ temperature = EXCLUDED.temperature,
+ max_tokens = EXCLUDED.max_tokens,
+ top_p = EXCLUDED.top_p,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ is_default = EXCLUDED.is_default,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V146__wiki_kb_watcher_enabled.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V146__wiki_kb_watcher_enabled.sql
new file mode 100644
index 00000000..d8c64b57
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V146__wiki_kb_watcher_enabled.sql
@@ -0,0 +1,13 @@
+-- Per-KB source-watcher toggle. See the h2 sibling for the prose explanation.
+-- MySQL INFORMATION_SCHEMA guard converted to plpgsql DO block.
+-- TINYINT(1) converted to SMALLINT.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_knowledge_base' AND column_name = 'watcher_enabled'
+ ) THEN
+ ALTER TABLE mate_wiki_knowledge_base ADD COLUMN watcher_enabled SMALLINT NOT NULL DEFAULT 0;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V14__embedding_model_config.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V14__embedding_model_config.sql
new file mode 100644
index 00000000..fa1390b0
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V14__embedding_model_config.sql
@@ -0,0 +1,40 @@
+-- V14: Embedding model UI config (对标 Dify)
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_config' AND column_name = 'model_type'
+ ) THEN
+ ALTER TABLE mate_model_config ADD COLUMN model_type VARCHAR(32) DEFAULT 'chat';
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_knowledge_base' AND column_name = 'embedding_model_id'
+ ) THEN
+ ALTER TABLE mate_wiki_knowledge_base ADD COLUMN embedding_model_id BIGINT DEFAULT NULL;
+ END IF;
+END $$;
+
+-- 播种 DashScope embedding(与 chat 模型共享 provider apiKey)
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES (1000001001, 'Text Embedding v3', 'dashscope', 'text-embedding-v3',
+ 'DashScope 通义千问 v3 通用文本向量模型(1024 维)', 0, 0, 0,
+ 1, 1, 1, 'embedding', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET model_type = 'embedding';
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES (1000001002, 'Text Embedding v2', 'dashscope', 'text-embedding-v2',
+ 'DashScope 通义千问 v2 文本向量模型(1536 维)', 0, 0, 0,
+ 1, 1, 0, 'embedding', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET model_type = 'embedding';
+
+-- 系统默认 embedding 模型(id 必须显式指定,与 chat 段 100000xxxx 错开)
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+VALUES (1000001100, 'embedding.default.model.id', '1000001001',
+ 'Default embedding model id for wiki semantic search', NOW(), NOW())
+ON CONFLICT (id) DO UPDATE SET setting_value = EXCLUDED.setting_value;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V15__purge_unavailable_dashscope_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V15__purge_unavailable_dashscope_models.sql
new file mode 100644
index 00000000..947007a7
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V15__purge_unavailable_dashscope_models.sql
@@ -0,0 +1,7 @@
+-- V15: Purge DashScope model seed rows that are unavailable on the native protocol
+DELETE FROM mate_model_config
+WHERE id IN (1000000170, 1000000171)
+ AND provider = 'dashscope'
+ AND builtin = 1;
+-- 1000000170 = qwen3.5-plus
+-- 1000000171 = qwen3.5-max
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V16__purge_dot_version_dashscope_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V16__purge_dot_version_dashscope_models.sql
new file mode 100644
index 00000000..d5898491
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V16__purge_dot_version_dashscope_models.sql
@@ -0,0 +1,8 @@
+-- V16: Broaden the DashScope native-protocol purge (see V15).
+DELETE FROM mate_model_config
+WHERE provider = 'dashscope'
+ AND (model_name LIKE 'qwen1.%'
+ OR model_name LIKE 'qwen2.%'
+ OR model_name LIKE 'qwen3.%'
+ OR model_name LIKE 'qwen4.%'
+ OR model_name LIKE 'qwen5.%');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V1__baseline_schema.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V1__baseline_schema.sql
new file mode 100644
index 00000000..56c6822c
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V1__baseline_schema.sql
@@ -0,0 +1,630 @@
+-- MateClaw 数据库初始化脚本(KingbaseES / PostgreSQL 兼容)
+
+-- 用户表
+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 SMALLINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 1,
+ icon VARCHAR(256),
+ tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 PRECISION,
+ max_tokens INT,
+ top_p DOUBLE PRECISION,
+ builtin SMALLINT NOT NULL DEFAULT 1,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ is_default SMALLINT NOT NULL DEFAULT 0,
+ max_input_tokens INT DEFAULT 0,
+ enable_search SMALLINT DEFAULT 0,
+ search_strategy VARCHAR(32) DEFAULT NULL,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_model_config_model_name ON mate_model_config (model_name);
+
+-- 模型 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(512),
+ base_url VARCHAR(512),
+ generate_kwargs TEXT,
+ is_custom SMALLINT NOT NULL DEFAULT 0,
+ is_local SMALLINT NOT NULL DEFAULT 0,
+ support_model_discovery SMALLINT NOT NULL DEFAULT 0,
+ support_connection_check SMALLINT NOT NULL DEFAULT 0,
+ freeze_url SMALLINT NOT NULL DEFAULT 0,
+ require_api_key SMALLINT 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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 1,
+ builtin SMALLINT NOT NULL DEFAULT 0,
+ tags VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 1,
+ builtin SMALLINT NOT NULL DEFAULT 0,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 0,
+ description VARCHAR(256),
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP,
+ stream_status VARCHAR(16) NOT NULL DEFAULT 'idle',
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_conversation_username ON mate_conversation (username);
+
+-- 消息表
+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 JSONB,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+COMMENT ON COLUMN mate_message.metadata IS '存储 toolCalls, plan, currentPhase, pendingApproval 等元数据';
+CREATE INDEX IF NOT EXISTS idx_message_conversation ON mate_message (conversation_id);
+CREATE INDEX IF NOT EXISTS idx_message_conv_time ON mate_message (conversation_id, create_time);
+
+-- 执行计划表
+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 TIMESTAMP,
+ end_time TIMESTAMP,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP,
+ end_time TIMESTAMP,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_sub_plan_plan_id ON mate_sub_plan (plan_id);
+
+-- 定时任务表
+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 SMALLINT NOT NULL DEFAULT 1,
+ next_run_time TIMESTAMP,
+ last_run_time TIMESTAMP,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 渠道会话存储表
+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 TIMESTAMP NOT NULL,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TEXT,
+ file_size BIGINT NOT NULL DEFAULT 0,
+ enabled SMALLINT NOT NULL DEFAULT 0,
+ sort_order INT NOT NULL DEFAULT 0,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_workspace_file_agent ON mate_workspace_file (agent_id);
+CREATE INDEX IF NOT EXISTS idx_workspace_file_agent_enabled ON mate_workspace_file (agent_id, enabled);
+
+-- ==================== 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 SMALLINT 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 TIMESTAMP,
+ tool_count INT NOT NULL DEFAULT 0,
+ builtin SMALLINT NOT NULL DEFAULT 0,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP NOT NULL,
+ resolved_at TIMESTAMP,
+ expire_at TIMESTAMP,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 0,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ priority INT NOT NULL DEFAULT 100,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- 安全全局配置表
+CREATE TABLE IF NOT EXISTS mate_tool_guard_config (
+ id BIGINT NOT NULL PRIMARY KEY,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ guard_scope VARCHAR(32) NOT NULL DEFAULT 'all',
+ guarded_tools_json TEXT,
+ denied_tools_json TEXT,
+ file_guard_enabled SMALLINT NOT NULL DEFAULT 1,
+ sensitive_paths_json TEXT,
+ audit_enabled SMALLINT NOT NULL DEFAULT 1,
+ audit_min_severity VARCHAR(16) NOT NULL DEFAULT 'INFO',
+ audit_retention_days INT NOT NULL DEFAULT 90,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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);
+
+-- ==================== 外部数据源 ====================
+
+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 SMALLINT NOT NULL DEFAULT 1,
+ last_test_time TIMESTAMP,
+ last_test_ok SMALLINT,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- ==================== 异步任务(视频/图片生成等长耗时操作) ====================
+
+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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_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);
+
+-- ==================== 记忆召回追踪 ====================
+
+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 PRECISION NOT NULL DEFAULT 0.0,
+ last_recalled_at TIMESTAMP,
+ promoted SMALLINT NOT NULL DEFAULT 0,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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);
+
+-- ==================== 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 TEXT,
+ 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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TEXT,
+ extracted_text TEXT,
+ content_hash VARCHAR(64),
+ file_size BIGINT NOT NULL DEFAULT 0,
+ processing_status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ last_processed_at TIMESTAMP,
+ error_message VARCHAR(512),
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TEXT,
+ summary VARCHAR(1024),
+ outgoing_links TEXT,
+ source_raw_ids TEXT,
+ version INT NOT NULL DEFAULT 1,
+ last_updated_by VARCHAR(32) NOT NULL DEFAULT 'ai',
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wiki_page_kb_slug ON mate_wiki_page (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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workspace_slug ON mate_workspace (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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 1,
+ config_json TEXT,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 SMALLINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP NOT NULL,
+ finished_at TIMESTAMP,
+ error_message TEXT,
+ token_usage INT DEFAULT 0,
+ create_time TIMESTAMP NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_cron_run_job ON mate_cron_job_run (cron_job_id, started_at);
+
+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 TIMESTAMP 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 TIMESTAMP 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);
+
+-- 清理 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/migration/kingbase/V20__purge_soft_deleted_rows.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V20__purge_soft_deleted_rows.sql
new file mode 100644
index 00000000..095d5774
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V20__purge_soft_deleted_rows.sql
@@ -0,0 +1,34 @@
+-- V20: Purge soft-deleted rows from all tables and retire soft-delete semantics.
+-- @TableLogic has been removed from all entities — the project no longer
+-- supports soft-delete. Clear residual deleted=1 rows so queries that still
+-- reference the deleted column (or raw SQL in service layer) continue to
+-- behave consistently. The deleted column itself is retained with its
+-- NOT NULL DEFAULT 0 constraint for schema compatibility.
+DELETE FROM mate_agent WHERE deleted = 1;
+DELETE FROM mate_agent_skill WHERE deleted = 1;
+DELETE FROM mate_agent_tool WHERE deleted = 1;
+DELETE FROM mate_channel WHERE deleted = 1;
+DELETE FROM mate_channel_session WHERE deleted = 1;
+DELETE FROM mate_conversation WHERE deleted = 1;
+DELETE FROM mate_cron_job WHERE deleted = 1;
+DELETE FROM mate_datasource WHERE deleted = 1;
+DELETE FROM mate_mcp_server WHERE deleted = 1;
+DELETE FROM mate_memory_recall WHERE deleted = 1;
+DELETE FROM mate_message WHERE deleted = 1;
+DELETE FROM mate_model_config WHERE deleted = 1;
+DELETE FROM mate_plan WHERE deleted = 1;
+DELETE FROM mate_plugin WHERE deleted = 1;
+DELETE FROM mate_skill WHERE deleted = 1;
+DELETE FROM mate_sub_plan WHERE deleted = 1;
+DELETE FROM mate_tool WHERE deleted = 1;
+DELETE FROM mate_tool_approval WHERE deleted = 1;
+DELETE FROM mate_tool_guard_audit_log WHERE deleted = 1;
+DELETE FROM mate_tool_guard_rule WHERE deleted = 1;
+DELETE FROM mate_user WHERE deleted = 1;
+DELETE FROM mate_wiki_chunk WHERE deleted = 1;
+DELETE FROM mate_wiki_knowledge_base WHERE deleted = 1;
+DELETE FROM mate_wiki_page WHERE deleted = 1;
+DELETE FROM mate_wiki_raw_material WHERE deleted = 1;
+DELETE FROM mate_workspace WHERE deleted = 1;
+DELETE FROM mate_workspace_file WHERE deleted = 1;
+DELETE FROM mate_workspace_member WHERE deleted = 1;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V21__provider_fallback_priority.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V21__provider_fallback_priority.sql
new file mode 100644
index 00000000..deffe62d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V21__provider_fallback_priority.sql
@@ -0,0 +1,26 @@
+-- RFC-009 Phase 1: ordered multi-provider fallback chain
+--
+-- fallback_priority defines the order in which a provider is tried after the
+-- primary model exhausts retries:
+-- 0 : not in the fallback chain (default — matches pre-RFC behavior)
+-- 1, 2, … : try in ascending order
+--
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use the INFORMATION_SCHEMA guard so
+-- this migration is idempotent across redeploys.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'fallback_priority'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN fallback_priority INT DEFAULT 0;
+ END IF;
+END $$;
+
+-- Seed: keep DashScope as priority 1 so existing deployments preserve the
+-- single-fallback-to-DashScope behavior the hardcoded path used to provide.
+UPDATE mate_model_provider
+ SET fallback_priority = 1
+ WHERE provider_id = 'dashscope'
+ AND (fallback_priority IS NULL OR fallback_priority = 0);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V23__wiki_relation_model.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V23__wiki_relation_model.sql
new file mode 100644
index 00000000..e3dc2e3b
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V23__wiki_relation_model.sql
@@ -0,0 +1,16 @@
+CREATE TABLE IF NOT EXISTS mate_wiki_page_citation (
+ id BIGINT PRIMARY KEY,
+ page_id BIGINT NOT NULL,
+ chunk_id BIGINT NOT NULL,
+ paragraph_idx INT NOT NULL DEFAULT 0,
+ anchor_text VARCHAR(512),
+ confidence DECIMAL(4,3) NOT NULL DEFAULT 1.000,
+ created_by VARCHAR(32) NOT NULL DEFAULT 'system',
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wpc_page ON mate_wiki_page_citation (page_id);
+CREATE INDEX IF NOT EXISTS idx_wpc_chunk ON mate_wiki_page_citation (chunk_id);
+
+ALTER TABLE mate_wiki_page ADD COLUMN IF NOT EXISTS page_type VARCHAR(32) NOT NULL DEFAULT 'concept';
+ALTER TABLE mate_wiki_page ADD COLUMN IF NOT EXISTS purpose_hint TEXT;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V24__wiki_processing_job.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V24__wiki_processing_job.sql
new file mode 100644
index 00000000..8c49747a
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V24__wiki_processing_job.sql
@@ -0,0 +1,25 @@
+CREATE TABLE IF NOT EXISTS mate_wiki_processing_job (
+ id BIGINT PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ raw_id BIGINT NOT NULL,
+ job_type VARCHAR(32) NOT NULL DEFAULT 'heavy_ingest',
+ stage VARCHAR(64) NOT NULL DEFAULT 'queued',
+ status VARCHAR(32) NOT NULL DEFAULT 'queued',
+ primary_model_id BIGINT,
+ current_model_id BIGINT,
+ fallback_chain_json TEXT,
+ retry_count INT NOT NULL DEFAULT 0,
+ max_retries INT NOT NULL DEFAULT 3,
+ error_code VARCHAR(64),
+ error_message TEXT,
+ resume_from_stage VARCHAR(64),
+ meta_json TEXT,
+ started_at TIMESTAMP(3),
+ finished_at TIMESTAMP(3),
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_wpj_raw ON mate_wiki_processing_job (raw_id);
+CREATE INDEX IF NOT EXISTS idx_wpj_status ON mate_wiki_processing_job (status);
+CREATE INDEX IF NOT EXISTS idx_wpj_kb ON mate_wiki_processing_job (kb_id, status);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V25__agent_provider_preference.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V25__agent_provider_preference.sql
new file mode 100644
index 00000000..d03578f4
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V25__agent_provider_preference.sql
@@ -0,0 +1,24 @@
+-- RFC-009 Phase 4 PR-3: per-agent provider preferences
+--
+-- Lets each agent declare an ordered list of preferred provider ids. Empty
+-- table for an agent (no rows) means "use the global fallback chain order"
+-- — fully backwards compatible with pre-PR-3 behavior. When rows exist,
+-- listed providers are tried in ascending sort_order before any non-listed
+-- provider is considered.
+--
+-- This is purely a routing hint. The runtime walker still gates each entry
+-- through AvailableProviderPool / ProviderHealthTracker — a preferred
+-- provider that is HARD-removed or in cooldown is still skipped.
+
+CREATE TABLE IF NOT EXISTS mate_agent_provider_preference (
+ id BIGINT NOT NULL PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ provider_id VARCHAR(128) NOT NULL,
+ sort_order INT NOT NULL DEFAULT 0,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_agent_provider ON mate_agent_provider_preference (agent_id, provider_id);
+CREATE INDEX IF NOT EXISTS idx_agent_provider_order ON mate_agent_provider_preference (agent_id, sort_order);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V26__dream_report.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V26__dream_report.sql
new file mode 100644
index 00000000..f9d9b003
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V26__dream_report.sql
@@ -0,0 +1,24 @@
+-- Dream v2: structured dream report (rfc-035 §4.4)
+CREATE TABLE IF NOT EXISTS mate_dream_report (
+ id BIGINT PRIMARY KEY,
+ agent_id BIGINT NOT NULL,
+ mode VARCHAR(32) NOT NULL,
+ topic VARCHAR(256),
+ trigger_source VARCHAR(32) NOT NULL,
+ triggered_by VARCHAR(64),
+ started_at TIMESTAMP NOT NULL,
+ finished_at TIMESTAMP NOT NULL,
+ candidate_count INT NOT NULL,
+ promoted_count INT NOT NULL,
+ rejected_count INT NOT NULL,
+ memory_diff TEXT,
+ llm_reason TEXT,
+ status VARCHAR(16) NOT NULL,
+ error_message TEXT,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted SMALLINT DEFAULT 0
+);
+
+CREATE INDEX idx_dream_agent_time ON mate_dream_report(agent_id, started_at DESC);
+CREATE INDEX idx_dream_agent_mode ON mate_dream_report(agent_id, mode, started_at DESC);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V27__memory_recall_review_fields.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V27__memory_recall_review_fields.sql
new file mode 100644
index 00000000..a561894a
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V27__memory_recall_review_fields.sql
@@ -0,0 +1,6 @@
+-- Dream v2: candidate state machine fields (rfc-035 4.1.4)
+-- Phase 1 writes values only; filtering enabled in Phase 2.
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_memory_recall ADD COLUMN IF NOT EXISTS review_count INT DEFAULT 0;
+ALTER TABLE mate_memory_recall ADD COLUMN IF NOT EXISTS last_reviewed_at TIMESTAMP;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V28__morning_card_seen.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V28__morning_card_seen.sql
new file mode 100644
index 00000000..7a709a7d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V28__morning_card_seen.sql
@@ -0,0 +1,12 @@
+-- Dream v2 Phase 2b: Morning Card seen state per (user, agent)
+-- Ref: rfc-034 F5 — DO NOT add to mate_user; use separate table
+CREATE TABLE IF NOT EXISTS mate_morning_card_seen (
+ id BIGINT PRIMARY KEY ,
+ user_id BIGINT NOT NULL,
+ agent_id BIGINT NOT NULL,
+ last_seen_at TIMESTAMP NOT NULL,
+ last_report_id BIGINT,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_user_agent ON mate_morning_card_seen (user_id, agent_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V29__memory_fact_projection.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V29__memory_fact_projection.sql
new file mode 100644
index 00000000..af833ad2
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V29__memory_fact_projection.sql
@@ -0,0 +1,49 @@
+-- Dream v2 Phase 3: Fact projection tables (read-only derived from canonical)
+-- Ref: rfc-038 §3.3
+
+CREATE TABLE IF NOT EXISTS mate_fact (
+ id BIGINT PRIMARY KEY ,
+ agent_id BIGINT NOT NULL,
+ source_ref VARCHAR(512) NOT NULL,
+ category VARCHAR(64),
+ subject VARCHAR(256),
+ predicate VARCHAR(256),
+ object_value TEXT,
+ confidence DOUBLE PRECISION DEFAULT 1.0,
+ trust DOUBLE PRECISION DEFAULT 0.5,
+ last_used_at TIMESTAMP,
+ use_count INT DEFAULT 0,
+ extracted_by VARCHAR(32) DEFAULT 'pattern',
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted SMALLINT DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_fact_agent_source ON mate_fact (agent_id, source_ref);
+CREATE INDEX IF NOT EXISTS idx_fact_agent_subject ON mate_fact (agent_id, subject);
+CREATE INDEX IF NOT EXISTS idx_fact_agent ON mate_fact (agent_id, deleted);
+
+CREATE TABLE IF NOT EXISTS mate_fact_entity_ref (
+ id BIGINT PRIMARY KEY ,
+ fact_id BIGINT NOT NULL,
+ entity_name VARCHAR(256) NOT NULL,
+ entity_type VARCHAR(64),
+ role VARCHAR(32) NOT NULL,
+ create_time TIMESTAMP NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_fact_ref_entity ON mate_fact_entity_ref (entity_name, entity_type);
+CREATE INDEX IF NOT EXISTS idx_fact_ref_fact ON mate_fact_entity_ref (fact_id);
+
+CREATE TABLE IF NOT EXISTS mate_fact_contradiction (
+ id BIGINT PRIMARY KEY ,
+ agent_id BIGINT NOT NULL,
+ fact_a_id BIGINT NOT NULL,
+ fact_b_id BIGINT NOT NULL,
+ description TEXT,
+ resolution VARCHAR(32),
+ resolved_at TIMESTAMP,
+ resolved_by VARCHAR(64),
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted SMALLINT DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_contradiction_agent ON mate_fact_contradiction (agent_id, resolution);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V2__workspace_base_path.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V2__workspace_base_path.sql
new file mode 100644
index 00000000..785c104e
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V2__workspace_base_path.sql
@@ -0,0 +1,227 @@
+-- V2: Upgrade schema for databases created before Flyway was introduced.
+-- MySQL does NOT support ALTER TABLE ... ADD COLUMN IF NOT EXISTS (MariaDB-only).
+-- We use INFORMATION_SCHEMA + dynamic SQL as an idempotent replacement so this migration
+-- is safe on BOTH: (a) fresh MySQL installs whose V1 baseline already contains the columns,
+-- and (b) legacy installs bootstrapped from the old schema.sql that predates those columns.
+
+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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workspace_slug ON mate_workspace (slug);
+
+-- mate_workspace.base_path (legacy upgrade path)
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_workspace' AND column_name = 'base_path'
+ ) THEN
+ ALTER TABLE mate_workspace ADD COLUMN base_path VARCHAR(512);
+ END IF;
+END $$;
+
+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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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);
+
+-- workspace_id on pre-existing domain tables
+DO $$
+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;
+END $$;
+
+DO $$
+BEGIN
+ 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;
+END $$;
+
+DO $$
+BEGIN
+ 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;
+END $$;
+
+DO $$
+BEGIN
+ 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;
+END $$;
+
+DO $$
+BEGIN
+ 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;
+END $$;
+
+DO $$
+BEGIN
+ 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 $$;
+
+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 TEXT,
+ file_size BIGINT NOT NULL DEFAULT 0,
+ enabled BOOLEAN NOT NULL DEFAULT FALSE,
+ sort_order INT NOT NULL DEFAULT 0,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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 TIMESTAMP NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_audit_ws_time ON mate_audit_event (workspace_id, create_time);
+
+-- model provider OAuth columns
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'auth_type'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN auth_type VARCHAR(16) NOT NULL DEFAULT 'api_key';
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'oauth_access_token'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN oauth_access_token TEXT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'oauth_refresh_token'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN oauth_refresh_token TEXT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'oauth_expires_at'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN oauth_expires_at BIGINT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'oauth_account_id'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN oauth_account_id VARCHAR(128);
+ END IF;
+END $$;
+
+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 TIMESTAMP 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 TIMESTAMP 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 TEXT,
+ tags VARCHAR(512),
+ score DOUBLE PRECISION NOT NULL DEFAULT 0.0,
+ last_recalled_at TIMESTAMP,
+ promoted BOOLEAN NOT NULL DEFAULT FALSE,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP 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/kingbase/V30__register_collab_skills.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V30__register_collab_skills.sql
new file mode 100644
index 00000000..4012674a
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V30__register_collab_skills.sql
@@ -0,0 +1,76 @@
+-- Register 4 collaboration skills introduced in RFC-044.
+-- These were previously only in seed data files; this migration ensures they exist
+-- in all environments (including existing installs that have already run seed data).
+-- Ref: rfc-044-skill-md-completion-2026-04-23
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000016, 'make_plan',
+ '当任务需要多步拆解或不确定执行路径时,向更强 Agent 请求一份分步可落地的执行计划,由当前 Agent 自己执行。',
+ 'builtin', '🗺️', '1.3.0', 'MateClaw',
+ '{"upstream":"mateclaw","entryFile":"SKILL.md"}',
+ 1, 1, 'plan,delegate,agent,collaboration', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET description = EXCLUDED.description,
+ skill_type = EXCLUDED.skill_type,
+ icon = EXCLUDED.icon,
+ version = EXCLUDED.version,
+ author = EXCLUDED.author,
+ config_json = EXCLUDED.config_json,
+ enabled = EXCLUDED.enabled,
+ builtin = EXCLUDED.builtin,
+ tags = EXCLUDED.tags,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000017, 'chat_with_agent',
+ '当需要咨询其他 Agent、寻求帮助或用户明确要求某个 Agent 参与时,使用本技能进行单次或并行委托。',
+ 'builtin', '💬', '1.2.0', 'MateClaw',
+ '{"upstream":"mateclaw","entryFile":"SKILL.md"}',
+ 1, 1, 'agent,chat,collaborate,delegate', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET description = EXCLUDED.description,
+ skill_type = EXCLUDED.skill_type,
+ icon = EXCLUDED.icon,
+ version = EXCLUDED.version,
+ author = EXCLUDED.author,
+ config_json = EXCLUDED.config_json,
+ enabled = EXCLUDED.enabled,
+ builtin = EXCLUDED.builtin,
+ tags = EXCLUDED.tags,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000018, 'channel_message',
+ '当需要主动向用户、会话或渠道单向推送消息时使用。任务完成通知、定时提醒、异步结果回推等场景。',
+ 'builtin', '📤', '1.3.0', 'MateClaw',
+ '{"upstream":"mateclaw","entryFile":"SKILL.md"}',
+ 1, 1, 'channel,message,push,notify,dingtalk,feishu', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET description = EXCLUDED.description,
+ skill_type = EXCLUDED.skill_type,
+ icon = EXCLUDED.icon,
+ version = EXCLUDED.version,
+ author = EXCLUDED.author,
+ config_json = EXCLUDED.config_json,
+ enabled = EXCLUDED.enabled,
+ builtin = EXCLUDED.builtin,
+ tags = EXCLUDED.tags,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
+
+INSERT INTO mate_skill (id, name, description, skill_type, icon, version, author, config_json, enabled, builtin, tags, create_time, update_time, deleted)
+VALUES (1000000019, 'multi_agent_collaboration',
+ '当任务需要多个 Agent 的专业能力协同完成时,编排多 Agent 并行或串行协作,整合各方结果。',
+ 'builtin', '🤝', '1.4.0', 'MateClaw',
+ '{"upstream":"mateclaw","entryFile":"SKILL.md"}',
+ 1, 1, 'multi-agent,collaboration,orchestration,parallel', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET description = EXCLUDED.description,
+ skill_type = EXCLUDED.skill_type,
+ icon = EXCLUDED.icon,
+ version = EXCLUDED.version,
+ author = EXCLUDED.author,
+ config_json = EXCLUDED.config_json,
+ enabled = EXCLUDED.enabled,
+ builtin = EXCLUDED.builtin,
+ tags = EXCLUDED.tags,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V31__register_docx_render_tool.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V31__register_docx_render_tool.sql
new file mode 100644
index 00000000..8c79b1f6
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V31__register_docx_render_tool.sql
@@ -0,0 +1,5 @@
+-- V31: Register DocxRenderTool as built-in tool (RFC-045)
+-- Idempotent: ON DUPLICATE KEY UPDATE keeps the row in sync if it already exists.
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000019, 'DocxRenderTool', 'DOCX Render', 'Render Markdown directly into a .docx and return a one-time download link. In-process Apache POI implementation, no Node.js subprocess; supports headings, bold, lists, tables. Preferred tool for creating new documents.', 'builtin', 'docxRenderTool', '📝', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V32__bailian_team_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V32__bailian_team_provider.sql
new file mode 100644
index 00000000..978caa66
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V32__bailian_team_provider.sql
@@ -0,0 +1,24 @@
+-- V32: Register Aliyun Bailian Token Plan provider and models
+-- OpenAI-compatible endpoint for team subscription users.
+-- freeze_url=1: the endpoint is plan-specific and must not be overridden.
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('bailian-team', '百炼 Token Plan', 'sk-', 'OpenAIChatModel', '', 'https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1', '{}', 0, 0, 0, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, generate_kwargs=EXCLUDED.generate_kwargs, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+-- Chat models
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000400, 'Qwen 3.6 Plus', 'bailian-team', 'qwen3.6-plus', '百炼团队套餐 — 千问旗舰推理模型,支持视觉理解与文本生成', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000401, 'DeepSeek V3.2', 'bailian-team', 'deepseek-v3.2', '百炼团队套餐 — DeepSeek 最新推理模型', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000402, 'GLM-5', 'bailian-team', 'glm-5', '百炼团队套餐 — 智谱 GLM-5 文本生成模型', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, model_type=EXCLUDED.model_type, update_time=EXCLUDED.update_time;
+
+-- Image generation models (temperature/max_tokens/top_p not applicable)
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000403, 'Qwen Image 2.0', 'bailian-team', 'qwen-image-2.0', '百炼团队套餐 — 千问图片生成模型', NULL, NULL, NULL, 1, 1, 0, 'image', NOW(), NOW(), 0),
+(1000000404, 'Qwen Image 2.0 Pro', 'bailian-team', 'qwen-image-2.0-pro', '百炼团队套餐 — 千问图片生成旗舰模型', NULL, NULL, NULL, 1, 1, 0, 'image', NOW(), NOW(), 0),
+(1000000405, 'Wan 2.7 Image', 'bailian-team', 'wan2.7-image', '百炼团队套餐 — 万相图片生成模型', NULL, NULL, NULL, 1, 1, 0, 'image', NOW(), NOW(), 0),
+(1000000406, 'Wan 2.7 Image Pro', 'bailian-team', 'wan2.7-image-pro', '百炼团队套餐 — 万相图片生成旗舰模型', NULL, NULL, NULL, 1, 1, 0, 'image', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, model_type=EXCLUDED.model_type, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V33__expand_api_key_column.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V33__expand_api_key_column.sql
new file mode 100644
index 00000000..13cca308
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V33__expand_api_key_column.sql
@@ -0,0 +1,3 @@
+-- V33: Expand mate_model_provider.api_key from VARCHAR(256) to VARCHAR(512)
+-- Bailian Token Plan keys exceed 256 chars (observed: 298 chars).
+ALTER TABLE mate_model_provider ALTER COLUMN api_key TYPE VARCHAR(512);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V34__siliconflow_opencode_providers.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V34__siliconflow_opencode_providers.sql
new file mode 100644
index 00000000..65c6f715
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V34__siliconflow_opencode_providers.sql
@@ -0,0 +1,47 @@
+-- V34: Add SiliconFlow (CN + INTL) and OpenCode providers with preset models
+-- SiliconFlow supports model discovery; preset models cover the most popular ones.
+-- OpenCode is a free-tier provider with two fixed models.
+
+-- ── Providers ──────────────────────────────────────────────────────────────
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('siliconflow-cn', '硅基流动 (China)', 'sk-', 'OpenAIChatModel', '', 'https://api.siliconflow.cn/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('siliconflow-intl', '硅基流动 (International)', 'sk-', 'OpenAIChatModel', '', 'https://api.siliconflow.com/v1', '{}', 0, 0, 1, 1, 1, 1, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, api_key_prefix=EXCLUDED.api_key_prefix, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, support_model_discovery=EXCLUDED.support_model_discovery, support_connection_check=EXCLUDED.support_connection_check, freeze_url=EXCLUDED.freeze_url, require_api_key=EXCLUDED.require_api_key, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES ('opencode', 'OpenCode', '', 'OpenAIChatModel', '', 'https://opencode.ai/zen/v1', '{}', 0, 0, 0, 1, 1, 0, NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name=EXCLUDED.name, chat_model=EXCLUDED.chat_model, base_url=EXCLUDED.base_url, update_time=EXCLUDED.update_time;
+
+-- ── SiliconFlow CN — preset popular models ─────────────────────────────────
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000500, 'DeepSeek V3', 'siliconflow-cn', 'deepseek-ai/DeepSeek-V3', '硅基流动 — DeepSeek V3,综合能力强,有免费额度', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000501, 'DeepSeek R1', 'siliconflow-cn', 'deepseek-ai/DeepSeek-R1', '硅基流动 — DeepSeek R1 推理模型', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000502, 'Qwen3 235B A22B', 'siliconflow-cn', 'Qwen/Qwen3-235B-A22B', '硅基流动 — 千问3旗舰 MoE 模型', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000503, 'Qwen3 30B A3B', 'siliconflow-cn', 'Qwen/Qwen3-30B-A3B', '硅基流动 — 千问3高性价比 MoE 模型', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000504, 'GLM-4 9B Chat', 'siliconflow-cn', 'THUDM/glm-4-9b-chat', '硅基流动 — 智谱 GLM-4 9B,免费可用', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000505, 'DeepSeek V3 Pro', 'siliconflow-cn', 'Pro/deepseek-ai/DeepSeek-V3', '硅基流动 Pro — DeepSeek V3 Pro 优先调度版', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000506, 'DeepSeek R1 Pro', 'siliconflow-cn', 'Pro/deepseek-ai/DeepSeek-R1', '硅基流动 Pro — DeepSeek R1 推理 Pro 优先调度版', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, model_type=EXCLUDED.model_type, update_time=EXCLUDED.update_time;
+
+-- ── SiliconFlow INTL — same preset models via international endpoint ────────
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000510, 'DeepSeek V3', 'siliconflow-intl', 'deepseek-ai/DeepSeek-V3', 'SiliconFlow INTL — DeepSeek V3, strong general capability', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000511, 'DeepSeek R1', 'siliconflow-intl', 'deepseek-ai/DeepSeek-R1', 'SiliconFlow INTL — DeepSeek R1 reasoning model', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000512, 'Qwen3 235B A22B', 'siliconflow-intl', 'Qwen/Qwen3-235B-A22B', 'SiliconFlow INTL — Qwen3 flagship MoE model', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000513, 'Qwen3 30B A3B', 'siliconflow-intl', 'Qwen/Qwen3-30B-A3B', 'SiliconFlow INTL — Qwen3 efficient MoE model', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000514, 'GLM-4 9B Chat', 'siliconflow-intl', 'THUDM/glm-4-9b-chat', 'SiliconFlow INTL — Zhipu GLM-4 9B, free tier', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000515, 'DeepSeek V3 Pro', 'siliconflow-intl', 'Pro/deepseek-ai/DeepSeek-V3', 'SiliconFlow INTL Pro — DeepSeek V3 priority tier', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000516, 'DeepSeek R1 Pro', 'siliconflow-intl', 'Pro/deepseek-ai/DeepSeek-R1', 'SiliconFlow INTL Pro — DeepSeek R1 priority tier', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, model_type=EXCLUDED.model_type, update_time=EXCLUDED.update_time;
+
+-- ── OpenCode — free public models ──────────────────────────────────────────
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000520, 'Big Pickle', 'opencode', 'big-pickle', 'OpenCode 免费模型 — Big Pickle', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000521, 'Nemotron 3 Super Free', 'opencode', 'nemotron-3-super-free', 'OpenCode 免费模型 — Nemotron 3 Super', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V35__skill_security_scan_result.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V35__skill_security_scan_result.sql
new file mode 100644
index 00000000..1d7a7939
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V35__skill_security_scan_result.sql
@@ -0,0 +1,28 @@
+-- V35: RFC-042 §2.3 — persist skill security scan result and timestamp.
+-- Until now findings lived only in SkillRuntimeStatus memory; after a restart
+-- the admin page couldn't explain why a skill was blocked. These two columns
+-- keep the last scan's findings (JSONB) and time so the UI can render them
+-- and offer a rescan control.
+--
+-- MySQL has no ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guards so
+-- the migration is idempotent across redeploys.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'security_scan_result'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN security_scan_result TEXT DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'security_scan_time'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN security_scan_time TIMESTAMP DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V36__skill_i18n_name.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V36__skill_i18n_name.sql
new file mode 100644
index 00000000..91b2e0b6
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V36__skill_i18n_name.sql
@@ -0,0 +1,51 @@
+-- V36: RFC-042 §2.2 — bilingual display names for skills.
+-- name stays the immutable slug / unique identifier; name_zh and
+-- name_en are optional locale-specific display labels. The UI falls
+-- back to name when the locale-matching column is null.
+--
+-- MySQL has no ADD COLUMN IF NOT EXISTS; INFORMATION_SCHEMA guards
+-- make the migration idempotent across redeploys.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'name_zh'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN name_zh VARCHAR(128) DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'name_en'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN name_en VARCHAR(128) DEFAULT NULL;
+ END IF;
+END $$;
+
+-- Backfill bilingual names for the 19 builtin skills that already exist on
+-- upgraded deployments. UPDATE is idempotent — running it again is a no-op
+-- since the values match. Fresh installs handle this in data-*.sql instead
+-- (those rows don't exist yet when this migration runs).
+UPDATE mate_skill SET name_zh = '定时任务', name_en = 'Cron Jobs' WHERE name = 'cron';
+UPDATE mate_skill SET name_zh = '文件阅读器', name_en = 'File Reader' WHERE name = 'file_reader';
+UPDATE mate_skill SET name_zh = '钉钉渠道接入', name_en = 'DingTalk Channel' WHERE name = 'dingtalk_channel_connect';
+UPDATE mate_skill SET name_zh = '邮件管理', name_en = 'Email (Himalaya)' WHERE name = 'himalaya';
+UPDATE mate_skill SET name_zh = '新闻查询', name_en = 'News' WHERE name = 'news';
+UPDATE mate_skill SET name_zh = 'PDF 处理', name_en = 'PDF' WHERE name = 'pdf';
+UPDATE mate_skill SET name_zh = 'Word 文档', name_en = 'Word Document' WHERE name = 'docx';
+UPDATE mate_skill SET name_zh = 'PPT 演示', name_en = 'PowerPoint' WHERE name = 'pptx';
+UPDATE mate_skill SET name_zh = 'Excel 表格', name_en = 'Excel' WHERE name = 'xlsx';
+UPDATE mate_skill SET name_zh = '可见浏览器', name_en = 'Visible Browser' WHERE name = 'browser_visible';
+UPDATE mate_skill SET name_zh = '浏览器 CDP', name_en = 'Browser CDP' WHERE name = 'browser_cdp';
+UPDATE mate_skill SET name_zh = '安装指引', name_en = 'Setup Guidance' WHERE name = 'guidance';
+UPDATE mate_skill SET name_zh = '源码索引', name_en = 'Source Index' WHERE name = 'mateclaw_source_index';
+UPDATE mate_skill SET name_zh = 'SQL 查询', name_en = 'SQL Query' WHERE name = 'sql_query';
+UPDATE mate_skill SET name_zh = '乔布斯视角', name_en = 'Steve Jobs Perspective' WHERE name = 'steve_jobs_perspective';
+UPDATE mate_skill SET name_zh = '制定计划', name_en = 'Make Plan' WHERE name = 'make_plan';
+UPDATE mate_skill SET name_zh = '咨询智能体', name_en = 'Chat with Agent' WHERE name = 'chat_with_agent';
+UPDATE mate_skill SET name_zh = '渠道推送', name_en = 'Channel Push' WHERE name = 'channel_message';
+UPDATE mate_skill SET name_zh = '多智能体协作', name_en = 'Multi-Agent Collaboration' WHERE name = 'multi_agent_collaboration';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V37__wiki_page_source_entries.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V37__wiki_page_source_entries.sql
new file mode 100644
index 00000000..a989949d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V37__wiki_page_source_entries.sql
@@ -0,0 +1,6 @@
+-- RFC-047 P2: Add source_entries column to mate_wiki_page for paired (rawId, rawTitle) lineage.
+-- Paired entries guarantee title-rawId alignment even when raw titles change.
+-- Dual-written alongside the existing source_raw_ids for backwards compatibility.
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_wiki_page ADD COLUMN IF NOT EXISTS source_entries JSONB NULL;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V38__wiki_chunk_content_mediumtext.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V38__wiki_chunk_content_mediumtext.sql
new file mode 100644
index 00000000..605abd6f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V38__wiki_chunk_content_mediumtext.sql
@@ -0,0 +1,3 @@
+-- V38: Expand mate_wiki_chunk.content from TEXT (64KB) to TEXT (16MB)
+-- In KingbaseES/PostgreSQL, TEXT is already unlimited (up to 1GB),
+-- so this migration is a no-op. Keep for Flyway version compatibility.
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V39__rfc051_chunk_metadata.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V39__rfc051_chunk_metadata.sql
new file mode 100644
index 00000000..a3519b74
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V39__rfc051_chunk_metadata.sql
@@ -0,0 +1,41 @@
+-- V39: RFC-051 PR-1a — chunk structural metadata.
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'page_number'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN page_number INT DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'token_count'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN token_count INT DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'header_breadcrumb'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN header_breadcrumb VARCHAR(1024) DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_chunk' AND column_name = 'source_section'
+ ) THEN
+ ALTER TABLE mate_wiki_chunk ADD COLUMN source_section VARCHAR(512) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V3__register_cron_job_tool.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V3__register_cron_job_tool.sql
new file mode 100644
index 00000000..721e7a44
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V3__register_cron_job_tool.sql
@@ -0,0 +1,4 @@
+-- V3: Register CronJobTool as built-in tool (RFC-003)
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000018, 'CronJobTool', 'Scheduled Tasks', 'Create, list, enable/disable, and delete scheduled tasks (cron jobs) through chat.', 'builtin', 'cronJobTool', '⏰', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V40__rfc051_page_locked.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V40__rfc051_page_locked.sql
new file mode 100644
index 00000000..23aa877d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V40__rfc051_page_locked.sql
@@ -0,0 +1,11 @@
+-- V40: RFC-051 PR-2 — page protection flag.
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'locked'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN locked SMALLINT NOT NULL DEFAULT 0;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V41__rfc051_page_archived.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V41__rfc051_page_archived.sql
new file mode 100644
index 00000000..ba7db5ec
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V41__rfc051_page_archived.sql
@@ -0,0 +1,10 @@
+-- V41: RFC-051 PR-7 — soft-archive flag.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_page' AND column_name = 'archived'
+ ) THEN
+ ALTER TABLE mate_wiki_page ADD COLUMN archived SMALLINT NOT NULL DEFAULT 0;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V42__claude_47_gpt_55_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V42__claude_47_gpt_55_models.sql
new file mode 100644
index 00000000..592091d8
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V42__claude_47_gpt_55_models.sql
@@ -0,0 +1,37 @@
+-- Add Claude 4.7 + GPT-5.5 model entries to mate_model_config for existing
+-- deployments. New installs pick these up via DatabaseBootstrapRunner from
+-- data-mysql-{en,zh}.sql; this migration covers operators who already have
+-- earlier Flyway versions applied.
+--
+-- INSERT ... ON CONFLICT DO UPDATE is the PostgreSQL idempotent upsert.
+-- Same V number is used in h2/ for cross-dialect parity.
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+-- GPT-5.5 series (OpenAI / Azure / OpenRouter)
+(1000000260, 'GPT-5.5', 'openai', 'gpt-5.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000261, 'GPT-5.5 Mini', 'openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000262, 'GPT-5.5 Nano', 'openai', 'gpt-5.5-nano', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000263, 'GPT-5.5', 'azure-openai', 'gpt-5.5', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000264, 'GPT-5.5 Mini', 'azure-openai', 'gpt-5.5-mini', '', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+(1000000265, 'GPT-5.5', 'openrouter', 'openai/gpt-5.5', 'GPT-5.5 via OpenRouter', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+-- Claude 4.7 series. NOTE: Claude 4.7 forbids temperature/top_p/top_k —
+-- handled in AgentAnthropicChatModelBuilder. NULL temperature/top_p in seed
+-- is the documented signal.
+(1000000270, 'Claude Opus 4.7', 'anthropic', 'claude-opus-4-7', 'Anthropic Claude Opus 4.7 (xhigh adaptive thinking)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000271, 'Claude Sonnet 4.7', 'anthropic', 'claude-sonnet-4-7', 'Anthropic Claude Sonnet 4.7', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000272, 'Claude Opus 4.7', 'openrouter', 'anthropic/claude-opus-4-7', 'Claude Opus 4.7 via OpenRouter', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000273, 'Claude Sonnet 4.7', 'openrouter', 'anthropic/claude-sonnet-4-7', 'Claude Sonnet 4.7 via OpenRouter', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ provider = EXCLUDED.provider,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ temperature = EXCLUDED.temperature,
+ max_tokens = EXCLUDED.max_tokens,
+ top_p = EXCLUDED.top_p,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ is_default = EXCLUDED.is_default,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V43__rfc062_claude_code_oauth_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V43__rfc062_claude_code_oauth_provider.sql
new file mode 100644
index 00000000..90fa5b67
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V43__rfc062_claude_code_oauth_provider.sql
@@ -0,0 +1,32 @@
+-- RFC-062: Seed the Anthropic Claude Code OAuth provider + its Claude 4.7
+-- model bindings on existing deployments. New installs already get these
+-- rows from data-mysql-{en,zh}.sql via DatabaseBootstrapRunner; this
+-- migration is for operators upgrading from <= V42.
+--
+-- INSERT ... ON CONFLICT DO UPDATE is the PostgreSQL idempotent upsert.
+-- Same V number is used in h2/ for cross-dialect parity.
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, auth_type, create_time, update_time)
+VALUES ('anthropic-claude-code', 'Anthropic Claude Code (OAuth)', '', 'ClaudeCodeChatModel', '', 'https://api.anthropic.com', '{}', 0, 0, 0, 0, 1, 0, 'oauth', NOW(), NOW())
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ auth_type = EXCLUDED.auth_type,
+ update_time = EXCLUDED.update_time;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+(1000000280, 'Claude Opus 4.7', 'anthropic-claude-code', 'claude-opus-4-7', 'Claude Opus 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000281, 'Claude Sonnet 4.7', 'anthropic-claude-code', 'claude-sonnet-4-7', 'Claude Sonnet 4.7 via Claude Code Pro/Max subscription', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ provider = EXCLUDED.provider,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ temperature = EXCLUDED.temperature,
+ max_tokens = EXCLUDED.max_tokens,
+ top_p = EXCLUDED.top_p,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ is_default = EXCLUDED.is_default,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V44__fix_claude_sonnet_47_does_not_exist.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V44__fix_claude_sonnet_47_does_not_exist.sql
new file mode 100644
index 00000000..f6eb6d39
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V44__fix_claude_sonnet_47_does_not_exist.sql
@@ -0,0 +1,31 @@
+-- Repair migration for V42/V43: Anthropic only released Opus 4.7 — there
+-- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body
+-- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}.
+--
+-- Anthropic's current model line includes claude-opus-4-7 but no
+-- claude-sonnet-4-7. The latest released Sonnet remains claude-sonnet-4-6
+-- (released alongside Opus 4.6).
+--
+-- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281
+-- so user-customised settings (default flag, enabled flag) survive.
+
+UPDATE mate_model_config
+SET name = 'Claude Sonnet 4.6',
+ model_name = 'claude-sonnet-4-6',
+ description = 'Anthropic Claude Sonnet 4.6 (latest Sonnet — 4.7 not yet released)',
+ update_time = NOW()
+WHERE id = 1000000271 AND model_name = 'claude-sonnet-4-7';
+
+UPDATE mate_model_config
+SET name = 'Claude Sonnet 4.6',
+ model_name = 'anthropic/claude-sonnet-4-6',
+ description = 'Claude Sonnet 4.6 via OpenRouter',
+ update_time = NOW()
+WHERE id = 1000000273 AND model_name = 'anthropic/claude-sonnet-4-7';
+
+UPDATE mate_model_config
+SET name = 'Claude Sonnet 4.6',
+ model_name = 'claude-sonnet-4-6',
+ description = 'Claude Sonnet 4.6 via Claude Code Pro/Max subscription',
+ update_time = NOW()
+WHERE id = 1000000281 AND model_name = 'claude-sonnet-4-7';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V45__deepseek_v4_models.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V45__deepseek_v4_models.sql
new file mode 100644
index 00000000..5dd303cb
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V45__deepseek_v4_models.sql
@@ -0,0 +1,19 @@
+-- Add DeepSeek V4 (flash + pro) model entries for MySQL deployments.
+-- Cross-dialect parity with h2/V45 — see that file's header for context.
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+(1000000282, 'DeepSeek V4 Flash', 'deepseek', 'deepseek-v4-flash', 'DeepSeek V4 Flash (1M context, reasoning via thinking-enabled mode)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0),
+(1000000283, 'DeepSeek V4 Pro', 'deepseek', 'deepseek-v4-pro', 'DeepSeek V4 Pro (1M context, reasoning via thinking-enabled mode)', NULL, 4096, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ provider = EXCLUDED.provider,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ temperature = EXCLUDED.temperature,
+ max_tokens = EXCLUDED.max_tokens,
+ top_p = EXCLUDED.top_p,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ is_default = EXCLUDED.is_default,
+ update_time = EXCLUDED.update_time,
+ deleted = EXCLUDED.deleted;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V46__stt_default_enabled.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V46__stt_default_enabled.sql
new file mode 100644
index 00000000..2ed0518f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V46__stt_default_enabled.sql
@@ -0,0 +1,21 @@
+-- Default-enable STT on existing deployments. See the h2/ counterpart for
+-- the "why enabled by default" rationale and the bug history that drove
+-- the skip-if-exists idiom. Same V number is used in h2/ for cross-dialect
+-- parity.
+--
+-- MySQL doesn't allow INSERT ... SELECT ... WHERE NOT EXISTS without a
+-- FROM clause, so we synthesise one with FROM DUAL. The end result is
+-- the same: insert when the setting_key is absent, no-op when it's already
+-- there.
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000020, 'sttEnabled', 'true', 'Enable speech-to-text (TalkMode mic input)', NOW(), NOW()
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttEnabled');
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000021, 'sttProvider', 'auto', 'STT provider: auto / openai / dashscope', NOW(), NOW()
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttProvider');
+
+INSERT INTO mate_system_setting (id, setting_key, setting_value, description, create_time, update_time)
+SELECT 1000000022, 'sttFallbackEnabled', 'true', 'Try alternate STT provider when the primary fails', NOW(), NOW()
+WHERE NOT EXISTS (SELECT 1 FROM mate_system_setting WHERE setting_key = 'sttFallbackEnabled');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V47__agent_max_iterations_100.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V47__agent_max_iterations_100.sql
new file mode 100644
index 00000000..7c3f8b4d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V47__agent_max_iterations_100.sql
@@ -0,0 +1,16 @@
+-- V47: Bump default agents' max_iterations to 100 (hard ceiling).
+--
+-- The previous defaults (25 for ReAct, 20 for plan-execute) ran the LimitExceededNode
+-- too eagerly on substantive multi-tool tasks (e.g. document generation with image
+-- conversion). New default is 100, the hard upper bound enforced at runtime.
+-- AgentGraphBuilder still clamps any per-agent override to MAX_ITERATIONS_HARD_CEILING
+-- at runtime, so a user-configured 200 will be silently capped to 100.
+--
+-- Idempotent: only updates rows that still hold the old defaults, so user-customized
+-- agents are not touched.
+
+UPDATE mate_agent SET max_iterations = 100
+WHERE id IN (1000000001, 1000000003) AND max_iterations = 25;
+
+UPDATE mate_agent SET max_iterations = 100
+WHERE id = 1000000002 AND max_iterations = 20;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V48__agent_max_iterations_and_agents_md_tools.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V48__agent_max_iterations_and_agents_md_tools.sql
new file mode 100644
index 00000000..221644c5
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V48__agent_max_iterations_and_agents_md_tools.sql
@@ -0,0 +1,20 @@
+-- V48 (Kingbase): see h2/V48 for full rationale.
+-- Use || instead of CONCAT() because Kingbase Oracle-compat CONCAT() only takes 2 args.
+
+UPDATE mate_agent SET max_iterations = 100
+WHERE id IN (1000000001, 1000000002, 1000000003)
+ AND (max_iterations IS NULL OR max_iterations < 100);
+
+UPDATE mate_workspace_file
+SET content = REPLACE(
+ content,
+ '需要执行文件操作或命令时,直接调用对应的工具(如 execute_shell_command、read_file 等),不要用文本描述你要做什么。',
+ '需要执行文件操作或命令时,直接调用对应的工具:' || CHR(10) ||
+ '- 读文件 → read_file' || CHR(10) ||
+ '- 写新文件或覆盖整个文件 → write_file(一次写完整内容,不要用 printf / heredoc / echo 拼)' || CHR(10) ||
+ '- 修改已有文件局部内容 → edit_file' || CHR(10) ||
+ '- 执行 shell 命令 → execute_shell_command' || CHR(10) ||
+ '不要用文本描述你要做什么。'
+ )
+WHERE filename = 'AGENTS.md'
+ AND content LIKE '%execute_shell_command、read_file%';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V49__reenable_write_edit_file_tools.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V49__reenable_write_edit_file_tools.sql
new file mode 100644
index 00000000..52031a5d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V49__reenable_write_edit_file_tools.sql
@@ -0,0 +1,5 @@
+-- V49 (MySQL): see h2/V49 for full rationale.
+
+UPDATE mate_tool SET enabled = 1
+WHERE bean_name IN ('writeFileTool', 'editFileTool')
+ AND enabled = 0;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V4__agent_thinking_level.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V4__agent_thinking_level.sql
new file mode 100644
index 00000000..0f5226ca
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V4__agent_thinking_level.sql
@@ -0,0 +1,12 @@
+-- V4: Add default_thinking_level to mate_agent
+-- Supports: off / low / medium / high / max (null = follow model default)
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_agent' AND column_name = 'default_thinking_level'
+ ) THEN
+ ALTER TABLE mate_agent ADD COLUMN default_thinking_level VARCHAR(32) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V50__fix_agents_md_tool_guidance_concat.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V50__fix_agents_md_tool_guidance_concat.sql
new file mode 100644
index 00000000..30cc456f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V50__fix_agents_md_tool_guidance_concat.sql
@@ -0,0 +1,50 @@
+-- V50 (Kingbase): mirror of h2/V50.
+-- Use || instead of CONCAT() because Kingbase Oracle-compat CONCAT() only takes 2 args.
+
+UPDATE mate_workspace_file
+SET content =
+ '## 记忆' || CHR(10) ||
+ CHR(10) ||
+ '你的记忆由数据库工作区文件提供连续性:' || CHR(10) ||
+ CHR(10) ||
+ '- PROFILE.md:稳定用户画像与协作偏好' || CHR(10) ||
+ '- MEMORY.md:长期事实、经验教训、工具设置、反复出现的模式' || CHR(10) ||
+ '- memory/YYYY-MM-DD.md:当日事件、观察、一次性上下文' || CHR(10) ||
+ CHR(10) ||
+ '### 记忆策略' || CHR(10) ||
+ CHR(10) ||
+ '- 稳定信息进入 PROFILE.md 或 MEMORY.md' || CHR(10) ||
+ '- 临时事件进入 memory/YYYY-MM-DD.md' || CHR(10) ||
+ '- 修改前先读取原文,优先做增量编辑而不是整篇重写' || CHR(10) ||
+ '- 避免记录敏感信息,除非用户明确要求' || CHR(10) ||
+ CHR(10) ||
+ '### 主动召回' || CHR(10) ||
+ CHR(10) ||
+ '- 遇到历史偏好、旧决策、持续任务、用户习惯时,优先查看工作区记忆' || CHR(10) ||
+ '- 不确定具体发生日期时,检查相关 memory/YYYY-MM-DD.md' || CHR(10) ||
+ CHR(10) ||
+ '## 安全' || CHR(10) ||
+ CHR(10) ||
+ '- 绝不泄露私密数据。' || CHR(10) ||
+ '- 拿不准的事情,先确认。' || CHR(10) ||
+ CHR(10) ||
+ '## 边界' || CHR(10) ||
+ CHR(10) ||
+ '- 私密的保持私密。' || CHR(10) ||
+ '- 需要执行文件操作或命令时,**必须**调用对应的工具:' || CHR(10) ||
+ ' - 读文件 → read_file' || CHR(10) ||
+ ' - 写新文件或覆盖整个文件 → write_file(一次写完整内容,不要用 printf / heredoc / echo / cat << EOF 拼字符串)' || CHR(10) ||
+ ' - 修改已有文件局部内容 → edit_file' || CHR(10) ||
+ ' - 执行 shell 命令 → execute_shell_command' || CHR(10) ||
+ ' 禁止用 shell 命令绕过 write_file 写文件。系统会自动对危险操作弹出审批确认。' || CHR(10) ||
+ '- 拿不准就先问。' || CHR(10) ||
+ CHR(10) ||
+ '## 风格' || CHR(10) ||
+ CHR(10) ||
+ '该简洁就简洁,重要时详细。' || CHR(10) ||
+ CHR(10) ||
+ '## 连续性' || CHR(10) ||
+ CHR(10) ||
+ '每次会话都全新醒来。工作区文件就是你的记忆。读它们。更新它们。' || CHR(10)
+WHERE filename = 'AGENTS.md'
+ AND agent_id IN (1000000001, 1000000002, 1000000003);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V51__remove_write_edit_file_from_toolguard.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V51__remove_write_edit_file_from_toolguard.sql
new file mode 100644
index 00000000..60f6b184
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V51__remove_write_edit_file_from_toolguard.sql
@@ -0,0 +1,5 @@
+-- V51 (MySQL): see h2/V51 for full rationale.
+
+UPDATE mate_tool_guard_config
+SET guarded_tools_json = '["execute_shell_command"]'
+WHERE id = 1000000001;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V52__feishu_default_connection_mode.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V52__feishu_default_connection_mode.sql
new file mode 100644
index 00000000..ee8a2b3f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V52__feishu_default_connection_mode.sql
@@ -0,0 +1,4 @@
+-- V52: Intentional no-op (deprecated). See h2/V52 for the full rationale.
+-- The actual migration was moved to V53.
+
+SELECT 1;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V53__feishu_connection_mode_recover.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V53__feishu_connection_mode_recover.sql
new file mode 100644
index 00000000..81c67580
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V53__feishu_connection_mode_recover.sql
@@ -0,0 +1,24 @@
+-- V53: Recover from V52's silent no-op.
+-- See h2/V53 for the full rationale. Same surgery, Kingbase-flavored.
+-- Idempotent: rows already at "websocket" are skipped by the WHERE clause.
+
+UPDATE mate_channel
+SET config_json = CASE
+ WHEN config_json IS NULL OR TRIM(config_json) = '' OR TRIM(config_json) = '{}' THEN
+ '{"connection_mode":"websocket"}'
+ WHEN POSITION('"connection_mode"' IN config_json) = 0 THEN
+ CONCAT('{"connection_mode":"websocket",', SUBSTRING(config_json, 2))
+ ELSE
+ REPLACE(
+ REPLACE(config_json,
+ '"connection_mode": "webhook"', '"connection_mode": "websocket"'),
+ '"connection_mode":"webhook"', '"connection_mode":"websocket"'
+ )
+ END
+WHERE channel_type = 'feishu'
+ AND deleted = 0
+ AND (
+ config_json IS NULL
+ OR (POSITION('"connection_mode": "websocket"' IN config_json) = 0
+ AND POSITION('"connection_mode":"websocket"' IN config_json) = 0)
+ );
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V54__channel_names_to_chinese_for_zh_locale.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V54__channel_names_to_chinese_for_zh_locale.sql
new file mode 100644
index 00000000..1241fa39
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V54__channel_names_to_chinese_for_zh_locale.sql
@@ -0,0 +1,45 @@
+-- V54: Localize seeded channel names to Chinese for zh-CN installs.
+-- See h2/V54 for the full rationale. Same logic, MySQL-flavored.
+-- Idempotent: re-running finds no matching rows after the first apply.
+
+UPDATE mate_channel SET name = 'Web 控制台'
+ WHERE id = 1000000001 AND name = 'Web Console'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+UPDATE mate_channel SET name = '钉钉机器人'
+ WHERE id = 1000000002 AND name = 'DingTalk Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+UPDATE mate_channel SET name = '飞书机器人'
+ WHERE id = 1000000003 AND name = 'Feishu Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+UPDATE mate_channel SET name = 'Telegram 机器人'
+ WHERE id = 1000000004 AND name = 'Telegram Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+UPDATE mate_channel SET name = 'Discord 机器人'
+ WHERE id = 1000000005 AND name = 'Discord Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+UPDATE mate_channel SET name = '企业微信机器人'
+ WHERE id = 1000000006 AND name = 'WeCom Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+UPDATE mate_channel SET name = 'QQ 机器人'
+ WHERE id = 1000000007 AND name = 'QQ Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
+
+-- id 1000000008 ("微信") already in Chinese; intentionally skipped.
+
+UPDATE mate_channel SET name = 'Slack 机器人'
+ WHERE id = 1000000009 AND name = 'Slack Bot'
+ AND EXISTS (SELECT 1 FROM mate_system_setting
+ WHERE setting_key = 'language' AND setting_value = 'zh-CN');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V55__provider_enabled.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V55__provider_enabled.sql
new file mode 100644
index 00000000..dfd330eb
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V55__provider_enabled.sql
@@ -0,0 +1,45 @@
+-- V55 (RFC-074): explicit user-enabled flag on providers. See H2 sibling for
+-- the full rationale; this file only differs in dialect-specific syntax.
+--
+-- MySQL lacks ADD COLUMN IF NOT EXISTS and CREATE INDEX IF NOT EXISTS —
+-- guard via INFORMATION_SCHEMA + dynamic SQL so re-runs are no-ops.
+
+-- ── Add enabled column ───────────────────────────────────────────────────
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_provider' AND column_name = 'enabled'
+ ) THEN
+ ALTER TABLE mate_model_provider ADD COLUMN enabled BOOLEAN DEFAULT FALSE;
+ END IF;
+END $$;
+
+-- ── Index supporting Rule 3's 30-day usage lookup ──────────────────────────
+CREATE INDEX IF NOT EXISTS idx_message_runtime_provider_time ON mate_message (runtime_provider, create_time);
+
+-- ── Rule 1: real (non-masked, non-empty) API key → user is using it ────────
+UPDATE mate_model_provider
+ SET enabled = TRUE
+ WHERE api_key IS NOT NULL AND api_key <> '' AND POSITION('*' IN api_key) = 0;
+
+-- ── Rule 2: OAuth provider with token → user is using it ───────────────────
+UPDATE mate_model_provider
+ SET enabled = TRUE
+ WHERE oauth_access_token IS NOT NULL AND oauth_access_token <> '';
+
+-- ── Rule 3: local provider with messages in last 30 days → user is using it ─
+UPDATE mate_model_provider
+ SET enabled = TRUE
+ WHERE is_local = 1
+ AND provider_id IN (
+ SELECT DISTINCT runtime_provider
+ FROM mate_message
+ WHERE runtime_provider IS NOT NULL
+ AND create_time >= CURRENT_TIMESTAMP - INTERVAL '30 days'
+ );
+
+-- ── Rule 4: provider whose model is the current default → user is using it ─
+UPDATE mate_model_provider
+ SET enabled = TRUE
+ WHERE provider_id IN (SELECT provider FROM mate_model_config WHERE is_default = 1);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V56__volcengine_plan_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V56__volcengine_plan_provider.sql
new file mode 100644
index 00000000..ad0e0411
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V56__volcengine_plan_provider.sql
@@ -0,0 +1,39 @@
+-- V56: register Volcano Ark Coding Plan as a separate provider with its own
+-- pre-seeded model catalog. See the H2 copy for full background.
+
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'volcengine-plan',
+ 'Volcano Engine Coding Plan (火山方舟代码计划)',
+ '',
+ 'OpenAIChatModel',
+ '',
+ 'https://ark.cn-beijing.volces.com/api/coding/v3',
+ '{}',
+ 0, 0, 1, 1, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+ (1000000320, 'Ark Coding Plan', 'volcengine-plan', 'ark-code-latest', '方舟代码计划旗舰模型,256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000321, 'Doubao Seed Code', 'volcengine-plan', 'doubao-seed-code', '豆包代码模型,256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000322, 'Doubao Seed Code Preview','volcengine-plan', 'doubao-seed-code-preview-251028', '豆包代码预览模型,256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000323, 'GLM 4.7 Coding', 'volcengine-plan', 'glm-4.7', 'GLM 4.7 编码版(火山方舟托管),200K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000324, 'Kimi K2 Thinking', 'volcengine-plan', 'kimi-k2-thinking', 'Kimi K2 推理版(火山方舟托管),256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000325, 'Kimi K2.5 Coding', 'volcengine-plan', 'kimi-k2.5', 'Kimi K2.5 编码版(火山方舟托管),256K 上下文', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ update_time = EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V57__cron_run_delivery_status.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V57__cron_run_delivery_status.sql
new file mode 100644
index 00000000..3a6acbdc
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V57__cron_run_delivery_status.sql
@@ -0,0 +1,38 @@
+-- RFC-063r §2.9: cron run delivery state machine (MySQL dialect).
+-- See V57 H2 file for state-machine + design reasoning.
+--
+-- MySQL has no ADD COLUMN IF NOT EXISTS / CREATE INDEX IF NOT EXISTS —
+-- guard each statement via INFORMATION_SCHEMA + dynamic SQL (project pattern
+-- previously used in V4/V19/V44).
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_cron_job_run' AND column_name = 'delivery_status'
+ ) THEN
+ ALTER TABLE mate_cron_job_run ADD COLUMN delivery_status VARCHAR(16) NOT NULL DEFAULT 'NONE';
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_cron_job_run' AND column_name = 'delivery_target'
+ ) THEN
+ ALTER TABLE mate_cron_job_run ADD COLUMN delivery_target VARCHAR(512);
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_cron_job_run' AND column_name = 'delivery_error'
+ ) THEN
+ ALTER TABLE mate_cron_job_run ADD COLUMN delivery_error VARCHAR(500);
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_cron_run_pending_started ON mate_cron_job_run (delivery_status, started_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V58__cron_channel_binding.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V58__cron_channel_binding.sql
new file mode 100644
index 00000000..5380c9b4
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V58__cron_channel_binding.sql
@@ -0,0 +1,23 @@
+-- RFC-063r §2.9: bind a cron job to its originating channel (MySQL dialect).
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_cron_job' AND column_name = 'channel_id'
+ ) THEN
+ ALTER TABLE mate_cron_job ADD COLUMN channel_id BIGINT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_cron_job' AND column_name = 'delivery_config'
+ ) THEN
+ ALTER TABLE mate_cron_job ADD COLUMN delivery_config TEXT;
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_cron_channel ON mate_cron_job (channel_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V59__approval_chat_origin_snapshot.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V59__approval_chat_origin_snapshot.sql
new file mode 100644
index 00000000..1da5a73f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V59__approval_chat_origin_snapshot.sql
@@ -0,0 +1,11 @@
+-- RFC-063r §2.12: persist ChatOrigin Memento snapshot on approval (MySQL dialect).
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_tool_approval' AND column_name = 'chat_origin'
+ ) THEN
+ ALTER TABLE mate_tool_approval ADD COLUMN chat_origin TEXT;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V5__conversation_parent.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V5__conversation_parent.sql
new file mode 100644
index 00000000..69fce697
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V5__conversation_parent.sql
@@ -0,0 +1,13 @@
+-- V5: Add parent_conversation_id to mate_conversation for multi-agent delegation tracking
+-- MySQL lacks ADD COLUMN IF NOT EXISTS / CREATE INDEX IF NOT EXISTS; use INFORMATION_SCHEMA guards.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_conversation' AND column_name = 'parent_conversation_id'
+ ) THEN
+ ALTER TABLE mate_conversation ADD COLUMN parent_conversation_id VARCHAR(64) DEFAULT NULL;
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_conversation_parent ON mate_conversation (parent_conversation_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V60__fix_invalid_workspace_member_roles.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V60__fix_invalid_workspace_member_roles.sql
new file mode 100644
index 00000000..c569cdb1
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V60__fix_invalid_workspace_member_roles.sql
@@ -0,0 +1,26 @@
+-- RFC-076: clean up invalid workspace_member rows produced by the legacy
+-- WorkspaceSchemaMigration.ensureDefaultWorkspaceMembership() insert
+-- (issue: https://github.com/matevip/mateclaw/issues/29).
+
+-- 1) For users who already have a valid membership in another workspace,
+-- drop their illegal default-workspace membership.
+DELETE FROM mate_workspace_member
+WHERE workspace_id = 1
+ AND deleted = 0
+ AND role NOT IN ('owner', 'admin', 'member', 'viewer')
+ AND user_id IN (
+ SELECT user_id FROM (
+ SELECT user_id FROM mate_workspace_member
+ WHERE workspace_id <> 1
+ AND deleted = 0
+ AND role IN ('owner', 'admin', 'member', 'viewer')
+ ) t
+ );
+
+-- 2) For orphans whose only membership is the illegal default one,
+-- normalize the role to 'member' so they don't get locked out entirely.
+UPDATE mate_workspace_member
+SET role = 'member', update_time = NOW()
+WHERE workspace_id = 1
+ AND deleted = 0
+ AND role NOT IN ('owner', 'admin', 'member', 'viewer');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V61__agent_creator_user_id.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V61__agent_creator_user_id.sql
new file mode 100644
index 00000000..6318259e
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V61__agent_creator_user_id.sql
@@ -0,0 +1,14 @@
+-- RFC-077 §4.1: track which user created an Agent, so members can delete
+-- their own Agents without needing workspace admin role (issue #26 Bug B).
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_agent' AND column_name = 'creator_user_id'
+ ) THEN
+ ALTER TABLE mate_agent ADD COLUMN creator_user_id BIGINT;
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_agent_creator_user ON mate_agent (creator_user_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V62__cron_job_workspace_id.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V62__cron_job_workspace_id.sql
new file mode 100644
index 00000000..d6d37530
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V62__cron_job_workspace_id.sql
@@ -0,0 +1,14 @@
+-- RFC-083: workspace-isolate cron jobs
+-- (issue: https://github.com/matevip/mateclaw/issues/37).
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_cron_job' AND column_name = 'workspace_id'
+ ) THEN
+ ALTER TABLE mate_cron_job ADD COLUMN workspace_id BIGINT NOT NULL DEFAULT 1;
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_cron_job_workspace ON mate_cron_job (workspace_id, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V63__channel_identity_json.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V63__channel_identity_json.sql
new file mode 100644
index 00000000..4e9b5274
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V63__channel_identity_json.sql
@@ -0,0 +1,14 @@
+-- RFC-084 follow-up: persist the identity returned by ChannelVerifier so
+-- the channel list can show "Connected as @MyBot" instead of generic
+-- type-level descriptions. Populated on wizard create from VerificationResult;
+-- refreshed by adapters on first successful connect.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_channel' AND column_name = 'identity_json'
+ ) THEN
+ ALTER TABLE mate_channel ADD COLUMN identity_json TEXT;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V64__cleanup_unused_channel_seeds.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V64__cleanup_unused_channel_seeds.sql
new file mode 100644
index 00000000..77ca9581
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V64__cleanup_unused_channel_seeds.sql
@@ -0,0 +1,10 @@
+-- RFC-084 follow-up: see h2/V64 for the rationale. MySQL accepts the
+-- same DELETE directly — DELETE on no matching rows is a no-op, not an
+-- error, so no INFORMATION_SCHEMA guard is needed.
+
+DELETE FROM mate_channel
+WHERE id IN (1000000002, 1000000003, 1000000004, 1000000005,
+ 1000000006, 1000000007, 1000000008, 1000000009)
+ AND channel_type IN ('dingtalk', 'feishu', 'telegram', 'discord',
+ 'wecom', 'qq', 'weixin', 'slack')
+ AND enabled = 0;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V65__seed_tasks_conversation_per_workspace.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V65__seed_tasks_conversation_per_workspace.sql
new file mode 100644
index 00000000..ce5b9c87
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V65__seed_tasks_conversation_per_workspace.sql
@@ -0,0 +1,28 @@
+-- See h2/V65 for rationale. MySQL syntax differs only in the string
+-- concatenation operator (CONCAT vs ||).
+
+INSERT INTO mate_conversation
+ (id, conversation_id, title, agent_id, username, message_count,
+ last_message, last_active_time, stream_status, workspace_id,
+ parent_conversation_id, create_time, update_time, deleted)
+SELECT
+ 1000200000 + ws.id,
+ CONCAT('tasks_', ws.id),
+ '📋 定时任务',
+ NULL,
+ 'system',
+ 0,
+ NULL,
+ NOW(),
+ 'idle',
+ ws.id,
+ NULL,
+ NOW(),
+ NOW(),
+ 0
+FROM mate_workspace ws
+WHERE ws.deleted = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM mate_conversation c
+ WHERE c.conversation_id = CONCAT('tasks_', ws.id) AND c.deleted = 0
+ );
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V66__add_model_capabilities.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V66__add_model_capabilities.sql
new file mode 100644
index 00000000..4a38a512
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V66__add_model_capabilities.sql
@@ -0,0 +1,11 @@
+-- V66: Per-model capability declaration (issue #44)
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_config' AND column_name = 'modalities'
+ ) THEN
+ ALTER TABLE mate_model_config ADD COLUMN modalities VARCHAR(512) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V67__add_skill_manifest.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V67__add_skill_manifest.sql
new file mode 100644
index 00000000..315bef7c
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V67__add_skill_manifest.sql
@@ -0,0 +1,14 @@
+-- V67: Skill manifest_json column (RFC-090 Phase 2)
+-- Stores the full parsed SKILL.md frontmatter as JSON. This becomes the
+-- source of truth (RFC-090 §14.6); existing columns (skill_type, icon,
+-- version, author) are kept as index projections, written by
+-- SkillPackageResolver.projectManifestToColumns after each resolve.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_skill' AND column_name = 'manifest_json'
+ ) THEN
+ ALTER TABLE mate_skill ADD COLUMN manifest_json TEXT;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V68__add_acp_endpoints.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V68__add_acp_endpoints.sql
new file mode 100644
index 00000000..9cd8863e
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V68__add_acp_endpoints.sql
@@ -0,0 +1,51 @@
+-- V68: ACP (Agent Communication Protocol) endpoint registry (RFC-090 Phase 7)
+-- See h2/V68 for column rationale; MySQL needs INSERT ... ON DUPLICATE KEY
+-- and a unique index on name for the seed merge to be idempotent.
+CREATE TABLE IF NOT EXISTS mate_acp_endpoint (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(64) NOT NULL,
+ display_name VARCHAR(128),
+ description TEXT,
+ command VARCHAR(256) NOT NULL,
+ args_json TEXT,
+ env_json TEXT,
+ tool_parse_mode VARCHAR(32) NOT NULL DEFAULT 'call_title',
+ builtin BOOLEAN NOT NULL DEFAULT 0,
+ trusted BOOLEAN NOT NULL DEFAULT 1,
+ enabled BOOLEAN NOT NULL DEFAULT 0,
+ stdio_buffer_limit_bytes BIGINT NOT NULL DEFAULT 52428800,
+ last_status VARCHAR(32),
+ last_tested_at TIMESTAMP,
+ last_error TEXT,
+ workspace_id BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_acp_endpoint_name ON mate_acp_endpoint (name);
+
+INSERT INTO mate_acp_endpoint
+ (id, name, display_name, description, command, args_json, env_json,
+ tool_parse_mode, builtin, trusted, enabled,
+ stdio_buffer_limit_bytes, workspace_id, create_time, update_time, deleted)
+VALUES
+ (9100001, 'codex', 'OpenAI Codex CLI', 'Delegate to the Codex ACP agent via npx',
+ 'npx', '["-y","@zed-industries/codex-acp"]', '{}',
+ 'call_detail', 1, 1, 0, 52428800, 1, NOW(), NOW(), 0),
+ (9100002, 'claude-code', 'Claude Code', 'Delegate to Anthropic''s Claude Code agent via npx',
+ 'npx', '["-y","@zed-industries/claude-agent-acp"]', '{}',
+ 'update_detail', 1, 1, 0, 52428800, 1, NOW(), NOW(), 0),
+ (9100003, 'opencode', 'OpenCode', 'Delegate to OpenCode ACP agent (binary on PATH)',
+ 'opencode', '["acp"]', '{}',
+ 'update_detail', 1, 1, 0, 52428800, 1, NOW(), NOW(), 0),
+ (9100004, 'qwen-code', 'Qwen Code', 'Delegate to Qwen Code ACP agent (binary on PATH)',
+ 'qwen', '["--acp"]', '{}',
+ 'call_detail', 1, 1, 0, 52428800, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET display_name = EXCLUDED.display_name,
+ description = EXCLUDED.description,
+ command = EXCLUDED.command,
+ args_json = EXCLUDED.args_json,
+ tool_parse_mode = EXCLUDED.tool_parse_mode,
+ builtin = EXCLUDED.builtin,
+ trusted = EXCLUDED.trusted,
+ update_time = NOW();
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V69__cron_job_dedup_unique.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V69__cron_job_dedup_unique.sql
new file mode 100644
index 00000000..38cf96fe
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V69__cron_job_dedup_unique.sql
@@ -0,0 +1,16 @@
+-- Issue #50: deduplicate accumulated cron jobs and prevent future duplicates
+-- at the DB level. PostgreSQL allows DELETE with subquery on the same table.
+--
+-- Step 1 — purge duplicate active rows, keeping the earliest id per
+-- (workspace_id, agent_id, name). Hard delete because this entity has no
+-- @TableLogic; deleteById() already performs physical deletes.
+DELETE FROM mate_cron_job
+WHERE id NOT IN (
+ SELECT MIN(id)
+ FROM mate_cron_job
+ GROUP BY workspace_id, agent_id, name
+);
+
+-- Step 2 — add the unique index, idempotent via INFORMATION_SCHEMA guard
+-- (MySQL < 8.0.29 has no CREATE INDEX IF NOT EXISTS).
+CREATE UNIQUE INDEX IF NOT EXISTS uk_cron_job_workspace_agent_name ON mate_cron_job (workspace_id, agent_id, name);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V6__plugin_table.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V6__plugin_table.sql
new file mode 100644
index 00000000..7e1d3068
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V6__plugin_table.sql
@@ -0,0 +1,20 @@
+-- Plugin SDK: mate_plugin table
+CREATE TABLE IF NOT EXISTS mate_plugin (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ version VARCHAR(32) NOT NULL,
+ plugin_type VARCHAR(32) NOT NULL,
+ display_name VARCHAR(128),
+ description TEXT,
+ author VARCHAR(128),
+ entrypoint VARCHAR(256) NOT NULL,
+ jar_path VARCHAR(512),
+ config_json TEXT NOT NULL DEFAULT ('{}'),
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ status VARCHAR(32) NOT NULL DEFAULT 'LOADED',
+ error_message TEXT,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_plugin_name ON mate_plugin (name);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V70__cron_job_dedup_safety.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V70__cron_job_dedup_safety.sql
new file mode 100644
index 00000000..ee3cc2b9
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V70__cron_job_dedup_safety.sql
@@ -0,0 +1,16 @@
+-- Issue #50 follow-up: see h2/V70 for rationale. MySQL doesn't allow
+-- DELETE with a subquery scanning the same table directly, so use
+-- the LEFT JOIN + IS NULL pattern.
+
+-- Step 1: physically purge any deleted=1 rows.
+DELETE FROM mate_cron_job WHERE deleted = 1;
+
+-- Step 2: idempotent re-dedup against active rows only.
+DELETE FROM mate_cron_job
+WHERE deleted = 0
+ AND id NOT IN (
+ SELECT MIN(id)
+ FROM mate_cron_job
+ WHERE deleted = 0
+ GROUP BY workspace_id, agent_id, name
+ );
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V71__hunyuan_3d_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V71__hunyuan_3d_provider.sql
new file mode 100644
index 00000000..54fcf134
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V71__hunyuan_3d_provider.sql
@@ -0,0 +1,29 @@
+-- V71: Register Tencent Hunyuan 3D provider for ai3d service.
+-- See h2/V71 for full rationale.
+
+INSERT INTO mate_model_provider (
+ provider_id, name, api_key_prefix, chat_model, api_key, base_url,
+ generate_kwargs, is_custom, is_local, support_model_discovery,
+ support_connection_check, freeze_url, require_api_key, auth_type,
+ create_time, update_time
+) VALUES (
+ 'hunyuan-3d', '腾讯混元 3D', 'AKID', 'NotApplicable', '',
+ 'https://ai3d.tencentcloudapi.com',
+ '{"service":"ai3d","version":"2025-05-13","region":"ap-guangzhou"}',
+ 0, 0, 0, 0, 1, 1, 'tc3_hmac_sha256',
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET
+ name = EXCLUDED.name,
+ api_key_prefix = EXCLUDED.api_key_prefix,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ is_custom = EXCLUDED.is_custom,
+ is_local = EXCLUDED.is_local,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ auth_type = EXCLUDED.auth_type,
+ update_time = NOW();
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V72__hunyuan_3d_model_config.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V72__hunyuan_3d_model_config.sql
new file mode 100644
index 00000000..3ea58087
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V72__hunyuan_3d_model_config.sql
@@ -0,0 +1,25 @@
+-- V72: Register Tencent Hunyuan 3D model variants. See h2/V72 for full rationale.
+
+INSERT INTO mate_model_config (
+ id, name, provider, model_name, description,
+ temperature, max_tokens, top_p, builtin, enabled, is_default,
+ model_type, create_time, update_time, deleted
+) VALUES
+ (1000000500, 'HY-3D-3.1', 'hunyuan-3d', 'HY-3D-3.1',
+ '腾讯混元 3D 3.1 — 最高精度,支持 PBR / 多视角 / Geometry 白模等专业参数',
+ NULL, NULL, NULL, 1, 1, 1, 'model3d', NOW(), NOW(), 0),
+ (1000000501, 'HY-3D-3.0', 'hunyuan-3d', 'HY-3D-3.0',
+ '腾讯混元 3D 3.0 — 老一代 Pro 模型,与 3.1 共享 SubmitHunyuanTo3DProJob 调用',
+ NULL, NULL, NULL, 1, 1, 0, 'model3d', NOW(), NOW(), 0),
+ (1000000502, 'HY-3D-Express', 'hunyuan-3d', 'HY-3D-Express',
+ '腾讯混元 3D 极速版 — 走 SubmitHunyuanTo3DRapidJob 接口,速度最快但仅支持 Prompt / ImageUrl',
+ NULL, NULL, NULL, 1, 1, 0, 'model3d', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ is_default = EXCLUDED.is_default,
+ model_type = EXCLUDED.model_type,
+ update_time = NOW();
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V73__add_skill_secret.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V73__add_skill_secret.sql
new file mode 100644
index 00000000..0340b3de
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V73__add_skill_secret.sql
@@ -0,0 +1,23 @@
+-- V73: Per-skill encrypted secret store (RFC-091 settings bridge).
+-- Holds AES-encrypted values for skill manifest fields with type=secret —
+-- e.g. AIRTABLE_API_KEY for the airtable-base wizard template. The
+-- runtime layer (SkillScriptExecutionService) decrypts and injects these
+-- as environment variables when spawning skill subprocesses, so SKILL.md
+-- bodies can reference them as plain $AIRTABLE_API_KEY without baking
+-- the secret into the manifest.
+--
+-- MySQL doesn't support ADD COLUMN IF NOT EXISTS; we get idempotency
+-- via CREATE TABLE IF NOT EXISTS plus an INFORMATION_SCHEMA guard for
+-- index creation.
+
+CREATE TABLE IF NOT EXISTS mate_skill_secret (
+ id BIGINT NOT NULL PRIMARY KEY,
+ skill_id BIGINT NOT NULL,
+ secret_key VARCHAR(128) NOT NULL,
+ encrypted_value TEXT NOT NULL,
+ create_time TIMESTAMP NOT NULL,
+ update_time TIMESTAMP NOT NULL,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_skill_secret_key ON mate_skill_secret (skill_id, secret_key);
+CREATE INDEX IF NOT EXISTS idx_skill_secret_skill ON mate_skill_secret (skill_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V74__shedlock_table.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V74__shedlock_table.sql
new file mode 100644
index 00000000..bc262e84
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V74__shedlock_table.sql
@@ -0,0 +1,16 @@
+-- V74: ShedLock distributed-lock table (RFC-03 Lane G2).
+-- Backs the LockProvider configured in vip.mate.cron.config.ShedLockConfig
+-- so a multi-instance deployment fires each cron job exactly once per tick.
+-- Single-node setups are unaffected (acquiring the lock from the only node
+-- always succeeds trivially).
+--
+-- Schema is the canonical ShedLock layout from
+-- https://github.com/lukas-krecan/ShedLock#configure-lockprovider.
+
+CREATE TABLE IF NOT EXISTS shedlock (
+ name VARCHAR(64) NOT NULL,
+ lock_until TIMESTAMP(3) NOT NULL,
+ locked_at TIMESTAMP(3) NOT NULL,
+ locked_by VARCHAR(255) NOT NULL,
+ PRIMARY KEY (name)
+);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V75__model_config_request_timeout.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V75__model_config_request_timeout.sql
new file mode 100644
index 00000000..6a460660
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V75__model_config_request_timeout.sql
@@ -0,0 +1,16 @@
+-- V75: Per-model HTTP read timeout (RFC-03 Lane B1).
+-- Lets thinking models (o1-pro, claude opus extended-thinking, qwen3-max
+-- with deep reasoning) override the default 180s read timeout when their
+-- p99 legitimately exceeds it. Null / zero keeps the existing global
+-- default — no behavior change for existing rows.
+--
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_model_config' AND column_name = 'request_timeout_seconds'
+ ) THEN
+ ALTER TABLE mate_model_config ADD COLUMN request_timeout_seconds INT DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V76__personal_access_token.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V76__personal_access_token.sql
new file mode 100644
index 00000000..f71a7adb
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V76__personal_access_token.sql
@@ -0,0 +1,24 @@
+-- V76: Personal Access Token (RFC-03 Lane I1).
+-- Lets headless / CI / SDK callers authenticate without going through
+-- the interactive JWT login flow. Tokens are stored as SHA-256 hashes —
+-- a DB compromise reveals which user owns which token but never the
+-- plaintext value the user sees once at creation time.
+--
+-- token_hash is the lookup key (UNIQUE) so the auth filter can do a
+-- single indexed query on every authenticated request.
+
+CREATE TABLE IF NOT EXISTS mate_personal_access_token (
+ id BIGINT NOT NULL PRIMARY KEY,
+ user_id BIGINT NOT NULL,
+ name VARCHAR(64),
+ token_hash CHAR(64) NOT NULL,
+ scopes VARCHAR(255),
+ last_used_at TIMESTAMP NULL,
+ expires_at TIMESTAMP NULL,
+ enabled BOOLEAN DEFAULT TRUE,
+ create_time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted INT DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_pat_token_hash ON mate_personal_access_token (token_hash);
+CREATE INDEX IF NOT EXISTS idx_pat_user_id ON mate_personal_access_token (user_id);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V77__wiki_relation_table.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V77__wiki_relation_table.sql
new file mode 100644
index 00000000..7589de61
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V77__wiki_relation_table.sql
@@ -0,0 +1,41 @@
+-- mate_wiki_relation: persistent cache of page-to-page multi-signal relations.
+--
+-- Distinct from mate_wiki_page_citation, which models page-to-chunk citations.
+-- A row here represents one directed (or undirected, see notes below) edge in
+-- the wiki page graph for a given knowledge base, materializing:
+-- * the aggregate relevance score across registered signal strategies
+-- (direct link, shared chunk, shared raw, semantic similarity, ...)
+-- * a per-signal breakdown for explainability
+-- * an optional taxonomy tag (mention / cite / supports / contradicts /
+-- extends) populated by the planning stage of the compile pipeline
+-- * confidence + evidence snippets sourced from the same compile output
+-- * cache invalidation metadata so readers can decide whether to recompute
+
+CREATE TABLE IF NOT EXISTS mate_wiki_relation (
+ id BIGINT PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ page_a_id BIGINT NOT NULL,
+ page_b_id BIGINT NOT NULL,
+
+ total_score DECIMAL(8, 4),
+ signals_json JSONB,
+
+ type VARCHAR(32),
+
+ confidence VARCHAR(16),
+ evidence TEXT,
+ evidence_raw_id BIGINT,
+
+ source VARCHAR(32),
+
+ computed_at TIMESTAMP(3),
+ computed_hash VARCHAR(64),
+
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wr_pair ON mate_wiki_relation (kb_id, page_a_id, page_b_id, deleted);
+CREATE INDEX IF NOT EXISTS idx_wr_page_a ON mate_wiki_relation (kb_id, page_a_id, total_score DESC);
+CREATE INDEX IF NOT EXISTS idx_wr_kb_score ON mate_wiki_relation (kb_id, total_score DESC);
+CREATE INDEX IF NOT EXISTS idx_wr_computed_at ON mate_wiki_relation (computed_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V78__feature_flag.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V78__feature_flag.sql
new file mode 100644
index 00000000..95f22605
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V78__feature_flag.sql
@@ -0,0 +1,36 @@
+-- mate_feature_flag: runtime-toggleable feature flag store.
+--
+-- Each row defines one named flag with optional KB / user whitelists and
+-- a percentage rollout. Reads go through an in-memory cache that refreshes
+-- on a 30-second timer (and immediately on admin write); the cache is
+-- per-instance so multi-instance deployments converge within one tick.
+
+CREATE TABLE IF NOT EXISTS mate_feature_flag (
+ id BIGSERIAL PRIMARY KEY,
+ flag_key VARCHAR(128) NOT NULL,
+ enabled BOOLEAN NOT NULL DEFAULT 0,
+ description VARCHAR(512),
+ whitelist_kb_ids TEXT,
+ whitelist_user_ids TEXT,
+ rollout_percent INT DEFAULT 0,
+
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_mff_key ON mate_feature_flag (flag_key);
+CREATE INDEX IF NOT EXISTS idx_mff_key_flag ON mate_feature_flag (flag_key, enabled, deleted);
+
+-- Seed wiki feature flags with safe defaults. ON DUPLICATE KEY UPDATE
+-- preserves existing operator overrides on re-run.
+INSERT INTO mate_feature_flag (flag_key, enabled, description) VALUES
+ ('wiki.ocr.enabled', 0, 'Image OCR / vision-in pipeline for wiki uploads'),
+ ('wiki.compile.4stage.enabled', 0, 'Four-stage knowledge base compilation pipeline'),
+ ('wiki.compile.cache.enabled', 0, 'Prompt cache layer for the wiki compile pipeline'),
+ ('wiki.confidence.enabled', 0, 'Confidence taxonomy on wiki relations and pages'),
+ ('wiki.hot_cache.enabled', 0, 'KB-level recent-activity snapshot injected into agent system prompt'),
+ ('wiki.graph.insights.enabled', 0, 'Wiki graph insights panel (surprising connections, gaps, bridges)'),
+ ('wiki.graph.adamic_adar.enabled', 0, 'Adamic-Adar graph signal (additive to existing four signals)'),
+ ('wiki.graph.boundary.enabled', 0, 'Boundary score for surfacing dangling pages'),
+ ('wiki.relation.cache.enabled', 1, 'Persistent cache for wiki page-to-page relation computation')
+ON CONFLICT (flag_key) DO UPDATE SET description = EXCLUDED.description;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V79__wiki_image_caption_cache.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V79__wiki_image_caption_cache.sql
new file mode 100644
index 00000000..a6ba1c9a
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V79__wiki_image_caption_cache.sql
@@ -0,0 +1,32 @@
+-- mate_wiki_image_caption_cache: SHA-256 keyed image caption store.
+--
+-- Cache is shared across all knowledge bases — the same image bytes
+-- uploaded twice (in different KBs, by different users, or to the same
+-- KB at different times) cost exactly one vision-LLM call total.
+--
+-- The cache is content-addressed by raw image bytes; perceptual variations
+-- (re-encoded JPEG, slightly cropped) are intentionally treated as misses.
+-- A second-tier perceptual hash can be added later if the miss rate
+-- becomes a cost concern.
+
+CREATE TABLE IF NOT EXISTS mate_wiki_image_caption_cache (
+ id BIGINT PRIMARY KEY,
+ image_sha256 CHAR(64) NOT NULL,
+
+ caption TEXT NOT NULL,
+ visible_text TEXT,
+ mime_type VARCHAR(64),
+
+ capture_model VARCHAR(128) NOT NULL,
+ provider_id VARCHAR(64) NOT NULL,
+
+ duration_ms BIGINT,
+ hit_count BIGINT NOT NULL DEFAULT 0,
+
+ captured_at TIMESTAMP(3) NOT NULL,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wicc_sha ON mate_wiki_image_caption_cache (image_sha256);
+CREATE INDEX IF NOT EXISTS idx_wicc_captured ON mate_wiki_image_caption_cache (captured_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V7__wiki_last_processed_hash.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V7__wiki_last_processed_hash.sql
new file mode 100644
index 00000000..ec7050bb
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V7__wiki_last_processed_hash.sql
@@ -0,0 +1,12 @@
+-- V7: Add last_processed_hash to mate_wiki_raw_material for skip-if-unchanged optimization
+-- RFC-012 Change 5: when reprocessing, skip LLM pipeline if content_hash == last_processed_hash
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_raw_material' AND column_name = 'last_processed_hash'
+ ) THEN
+ ALTER TABLE mate_wiki_raw_material ADD COLUMN last_processed_hash VARCHAR(64) DEFAULT NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V80__wiki_raw_material_mime_type.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V80__wiki_raw_material_mime_type.sql
new file mode 100644
index 00000000..14948ab2
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V80__wiki_raw_material_mime_type.sql
@@ -0,0 +1,8 @@
+-- Adds the MIME type column to mate_wiki_raw_material so the upload pipeline
+-- can route uploads to the right downstream extractor. Image source types
+-- in particular need the original Content-Type to pick a vision provider
+-- and to render previews correctly without re-sniffing the file.
+--
+-- KingbaseES (PostgreSQL) supports ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_wiki_raw_material ADD COLUMN IF NOT EXISTS mime_type VARCHAR(64);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V81__refresh_bailian_qwen_catalog.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V81__refresh_bailian_qwen_catalog.sql
new file mode 100644
index 00000000..4893b494
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V81__refresh_bailian_qwen_catalog.sql
@@ -0,0 +1,49 @@
+-- Refresh the Bailian (Aliyun Model Studio) Qwen catalog to match the 2026 Q2
+-- model lineup, and remove a non-existent model id that triggered 400
+-- InvalidParameter on the native text-generation endpoint.
+--
+-- Why now:
+-- * Reported user error: "Bad request, please check input (type=MODEL_NOT_FOUND)"
+-- when chatting with the seeded "Qwen3 Plus" entry.
+-- * Root cause: model id qwen3-plus does not exist on Bailian. The real
+-- balanced Qwen3 series uses dotted minor versions (qwen3.5-plus,
+-- qwen3.6-plus); plain qwen3-plus was never published. DashScope's
+-- native endpoint rejects it with [InvalidParameter], which our
+-- failover classifier maps to MODEL_NOT_FOUND and evicts the entire
+-- dashscope provider from the pool.
+--
+-- Two-part fix:
+-- 1. Soft-delete the bogus qwen3-plus row (id 1000000172).
+-- 2. Seed three latest-snapshot trackers on the dashscope native provider
+-- (qwen-plus-latest / qwen-max-latest / qwen-turbo-latest) and six
+-- newer Qwen3 series models on the bailian-team OpenAI-compat provider
+-- where they are documented to work (vision + flash + coder + 3.6 snapshot).
+
+-- 1. Soft-delete the bogus model id (idempotent).
+UPDATE mate_model_config
+ SET deleted = 1, enabled = 0, update_time = NOW()
+ WHERE id = 1000000172
+ AND model_name = 'qwen3-plus';
+
+-- 2a. Latest-snapshot trackers on dashscope native (text-generation endpoint).
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000174, 'Qwen Plus (latest)', 'dashscope', 'qwen-plus-latest', 'Latest stable snapshot of Qwen Plus — auto-updates as Bailian rolls new releases.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000175, 'Qwen Max (latest)', 'dashscope', 'qwen-max-latest', 'Latest stable snapshot of Qwen Max — strongest reasoning capability.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000176, 'Qwen Turbo (latest)', 'dashscope', 'qwen-turbo-latest', 'Latest stable snapshot of Qwen Turbo — low latency, high frequency.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, model_type=EXCLUDED.model_type, update_time=EXCLUDED.update_time;
+
+-- 2b. Newer Qwen3 series on the bailian-team OpenAI-compat token plan endpoint.
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, model_type, create_time, update_time, deleted)
+VALUES
+(1000000407, 'Qwen 3.5 Plus', 'bailian-team', 'qwen3.5-plus', 'Bailian Token Plan — Qwen3.5 balanced flagship, hybrid thinking, 128K context.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000408, 'Qwen 3.5 Flash', 'bailian-team', 'qwen3.5-flash', 'Bailian Token Plan — Qwen3.5 fast variant, lower latency for high-frequency calls.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000409, 'Qwen3 VL Plus', 'bailian-team', 'qwen3-vl-plus', 'Bailian Token Plan — Qwen3 vision-language flagship, image + video understanding.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000410, 'Qwen3 VL Flash', 'bailian-team', 'qwen3-vl-flash', 'Bailian Token Plan — Qwen3 vision-language fast variant for high-throughput vision.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000411, 'Qwen3 Coder Plus', 'bailian-team', 'qwen3-coder-plus', 'Bailian Token Plan — Qwen3 coding flagship, agentic code editing and tool use.', 0.2, 8192, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000412, 'Qwen 3.6 Plus 2026-04-02', 'bailian-team', 'qwen3.6-plus-2026-04-02','Bailian Token Plan — pinned snapshot of Qwen 3.6 Plus released 2026-04-02.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000413, 'Qwen 3.6 Max (preview)', 'bailian-team', 'qwen3.6-max-preview', 'Bailian Token Plan — Qwen3.6 Max preview, strongest reasoning in the 3.6 lineup.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000414, 'Qwen 3.6 Flash', 'bailian-team', 'qwen3.6-flash', 'Bailian Token Plan — Qwen3.6 fast variant, hybrid thinking mode default-on.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000415, 'Qwen 3.6 Flash 2026-04-16','bailian-team', 'qwen3.6-flash-2026-04-16','Bailian Token Plan — pinned snapshot of Qwen 3.6 Flash released 2026-04-16.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0),
+(1000000416, 'Qwen 3.5 Omni Plus', 'bailian-team', 'qwen3.5-omni-plus', 'Bailian Token Plan — Qwen3.5 omni-modal plus, text + vision + audio in/out.', 0.7, 4096, 0.8, 1, 1, 0, 'chat', NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description, model_type=EXCLUDED.model_type, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V82__wiki_hot_cache.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V82__wiki_hot_cache.sql
new file mode 100644
index 00000000..ed692871
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V82__wiki_hot_cache.sql
@@ -0,0 +1,30 @@
+-- mate_wiki_hot_cache: KB-level rolling snapshot of "what happened recently"
+-- in this knowledge base, injected into the agent system prompt so the model
+-- doesn't have to wiki_search the obvious every turn.
+--
+-- One row per KB (uk_whc_kb). Body is markdown rendered from a four-section
+-- structure (Last Updated / Key Recent Facts / Recent Changes / Active Threads),
+-- regenerated by an LLM call after compile/page/conversation events; this
+-- migration only provisions storage. Consumers and updater land in later PRs.
+
+CREATE TABLE IF NOT EXISTS mate_wiki_hot_cache (
+ id BIGINT PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+
+ content TEXT,
+ content_hash CHAR(64),
+
+ last_updated TIMESTAMP(3),
+ update_reason VARCHAR(32),
+
+ rebuild_count BIGINT NOT NULL DEFAULT 0,
+ last_rebuild_started_at TIMESTAMP(3),
+ last_rebuild_duration_ms BIGINT,
+ last_rebuild_error VARCHAR(512),
+
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_whc_kb ON mate_wiki_hot_cache (kb_id, deleted);
+CREATE INDEX IF NOT EXISTS idx_whc_kb ON mate_wiki_hot_cache (kb_id, deleted);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V84__widen_raw_material_mime_type.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V84__widen_raw_material_mime_type.sql
new file mode 100644
index 00000000..f2a5ae8c
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V84__widen_raw_material_mime_type.sql
@@ -0,0 +1,7 @@
+-- V80 created mate_wiki_raw_material.mime_type as VARCHAR(64). Office Open
+-- XML Content-Types blow that on the very first upload — docx is 71 chars,
+-- pptx is 73, xlsx is 65 — so any user uploading a Word / Excel / PowerPoint
+-- file hits "Data truncation: Data too long for column 'mime_type'".
+-- Widen to 255 (covers any registered RFC 6838 type with parameters).
+
+ALTER TABLE mate_wiki_raw_material ALTER COLUMN mime_type TYPE VARCHAR(255);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V85__ckjia_mcp_seed.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V85__ckjia_mcp_seed.sql
new file mode 100644
index 00000000..4da972b9
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V85__ckjia_mcp_seed.sql
@@ -0,0 +1,28 @@
+-- Seed ckjia-shopping MCP server config (disabled by default).
+-- The localhost URL below is a dev/test placeholder only. Production admins
+-- must replace it in Settings > MCP Connections with the official CKJIA SaaS
+-- domain or their private CKJIA deployment URL before enabling the server,
+-- then configure CKJIA_MCP_KEY for authorization.
+--
+-- Column name is url (not endpoint) per McpServerEntity.
+-- headers_json uses ${CKJIA_MCP_KEY} placeholder so the plaintext API key
+-- never lands in the database (parseHeaders expands env vars at request time).
+
+-- mate_mcp_server.id is BIGINT NOT NULL PRIMARY KEY without DB-side auto-increment;
+-- production uses MyBatis Plus Snowflake at insert time, but Flyway bypasses that.
+-- Following existing seed convention (1000000901=filesystem, 1000000902=github),
+-- ckjia-shopping takes 1000000903.
+INSERT INTO mate_mcp_server (
+ id, name, transport, url, headers_json, enabled, description,
+ connect_timeout_seconds, read_timeout_seconds, builtin, create_time, update_time, deleted
+)
+SELECT 1000000903,
+ 'ckjia-shopping',
+ 'sse',
+ 'http://localhost:8085/sse',
+ '{"Authorization": "Bearer ${CKJIA_MCP_KEY}"}',
+ 0,
+ 'CKJIA price comparison MCP server. Disabled by default; replace the dev/test localhost URL with the production CKJIA domain before enabling.',
+ 30, 30, 1,
+ NOW(), NOW(), 0
+WHERE NOT EXISTS (SELECT 1 FROM mate_mcp_server WHERE name = 'ckjia-shopping');
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V87__skill_usage_stat.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V87__skill_usage_stat.sql
new file mode 100644
index 00000000..c19941b3
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V87__skill_usage_stat.sql
@@ -0,0 +1,17 @@
+CREATE TABLE IF NOT EXISTS mate_skill_usage_stat (
+ id BIGINT PRIMARY KEY,
+ skill_name VARCHAR(128) NOT NULL,
+ skill_id BIGINT,
+ agent_id BIGINT NOT NULL DEFAULT 0,
+ conversation_id VARCHAR(128) NOT NULL DEFAULT '',
+ load_count BIGINT NOT NULL DEFAULT 0,
+ last_loaded_at TIMESTAMP(3),
+ last_file_path VARCHAR(512),
+ last_token_estimate INT,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted SMALLINT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_skill_usage_scope ON mate_skill_usage_stat (skill_name, agent_id, conversation_id);
+CREATE INDEX IF NOT EXISTS idx_skill_usage_agent_recent ON mate_skill_usage_stat (agent_id, last_loaded_at);
+CREATE INDEX IF NOT EXISTS idx_skill_usage_name_recent ON mate_skill_usage_stat (skill_name, last_loaded_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V88__convert_default_agent_icons_to_pixelart.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V88__convert_default_agent_icons_to_pixelart.sql
new file mode 100644
index 00000000..490a0d4f
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V88__convert_default_agent_icons_to_pixelart.sql
@@ -0,0 +1,8 @@
+-- Convert the three default seed agents from emoji to pixelarticons icons.
+-- The card UI now treats pi: as an inline pixel-art SVG, so the
+-- defaults need to match. Guarded with the original emoji so a user who
+-- already customised the icon keeps their choice; non-seed (user-created)
+-- agents are intentionally untouched.
+UPDATE mate_agent SET icon = 'pi:robot-face-happy' WHERE id = 1000000001 AND icon = '🤖';
+UPDATE mate_agent SET icon = 'pi:clipboard-note' WHERE id = 1000000002 AND icon = '📋';
+UPDATE mate_agent SET icon = 'pi:cpu' WHERE id = 1000000003 AND icon = '🔄';
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V89__chatgpt_oauth_model_discovery.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V89__chatgpt_oauth_model_discovery.sql
new file mode 100644
index 00000000..d454f1f8
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V89__chatgpt_oauth_model_discovery.sql
@@ -0,0 +1,16 @@
+-- Enable model discovery on the ChatGPT OAuth provider so the catalog can be
+-- pulled live from chatgpt.com/backend-api/codex/models, and seed the GPT-5.5
+-- flagship row alongside the existing GPT-5.4 / GPT-5.4 Mini entries. The
+-- ON DUPLICATE KEY UPDATE clause keeps the migration idempotent.
+
+UPDATE mate_model_provider
+ SET support_model_discovery = 1,
+ update_time = CURRENT_TIMESTAMP
+ WHERE provider_id = 'openai-chatgpt'
+ AND support_model_discovery <> 1;
+
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES (1000000252, 'GPT-5.5', 'openai-chatgpt', 'gpt-5.5', 'ChatGPT Plus/Pro flagship model', NULL, 128000, NULL, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ description = EXCLUDED.description,
+ update_time = NOW();
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V8__wiki_raw_progress.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V8__wiki_raw_progress.sql
new file mode 100644
index 00000000..5c68c824
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V8__wiki_raw_progress.sql
@@ -0,0 +1,33 @@
+-- V8: wiki raw material two-phase digest progress fields, for UI progress bar
+-- RFC-012 M2 v2 UI follow-up: expose per-raw progress (current phase + pages done / total planned)
+-- so the frontend can render a determinate progress bar instead of an opaque "处理中" badge.
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_raw_material' AND column_name = 'progress_phase'
+ ) THEN
+ ALTER TABLE mate_wiki_raw_material ADD COLUMN progress_phase VARCHAR(32) DEFAULT NULL;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_raw_material' AND column_name = 'progress_total'
+ ) THEN
+ ALTER TABLE mate_wiki_raw_material ADD COLUMN progress_total INT DEFAULT 0;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_raw_material' AND column_name = 'progress_done'
+ ) THEN
+ ALTER TABLE mate_wiki_raw_material ADD COLUMN progress_done INT DEFAULT 0;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V90__zhipu_coding_plan_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V90__zhipu_coding_plan_provider.sql
new file mode 100644
index 00000000..f69e8468
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V90__zhipu_coding_plan_provider.sql
@@ -0,0 +1,115 @@
+-- V90: register coding-plan subscription endpoints as separate providers
+-- with their own pre-seeded model catalogs. See the H2 copy for full
+-- background. Covers: zhipu-cn-codingplan, zhipu-intl-codingplan, and
+-- aliyun-codingplan-intl.
+
+-- -- Zhipu Coding Plan (China) ----------------------------------------------
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'zhipu-cn-codingplan',
+ 'Zhipu Coding Plan (BigModel)',
+ '',
+ 'OpenAIChatModel',
+ '',
+ 'https://open.bigmodel.cn/api/coding/paas/v4',
+ '{"completionsPath":"/chat/completions"}',
+ 0, 0, 0, 0, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+-- -- Zhipu Coding Plan (International) ---------------------------------------
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'zhipu-intl-codingplan',
+ 'Zhipu Coding Plan (Z.AI)',
+ '',
+ 'OpenAIChatModel',
+ '',
+ 'https://api.z.ai/api/coding/paas/v4',
+ '{"completionsPath":"/chat/completions"}',
+ 0, 0, 0, 0, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+-- -- Aliyun DashScope Coding Plan (International) ---------------------------
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'aliyun-codingplan-intl',
+ 'Aliyun Coding Plan (International)',
+ 'sk-sp',
+ 'OpenAIChatModel',
+ '',
+ 'https://coding-intl.dashscope.aliyuncs.com/v1',
+ '{}',
+ 0, 0, 0, 0, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ api_key_prefix = EXCLUDED.api_key_prefix,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+-- -- Zhipu Coding Plan model catalog ----------------------------------------
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+ (1000000230, 'GLM-5 Coding', 'zhipu-cn-codingplan', 'glm-5', '智谱编码套餐 — GLM-5 旗舰', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000231, 'GLM-5.1 Coding', 'zhipu-cn-codingplan', 'glm-5.1', '智谱编码套餐 — GLM-5.1 最新旗舰', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000232, 'GLM-5-Turbo Coding', 'zhipu-cn-codingplan', 'glm-5-turbo', '智谱编码套餐 — GLM-5 高速版', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000233, 'GLM-4.7 Coding', 'zhipu-cn-codingplan', 'glm-4.7', '智谱编码套餐 — GLM-4.7', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000234, 'GLM-5 Coding', 'zhipu-intl-codingplan', 'glm-5', 'Zhipu Coding Plan — GLM-5 flagship', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000235, 'GLM-5.1 Coding', 'zhipu-intl-codingplan', 'glm-5.1', 'Zhipu Coding Plan — GLM-5.1 latest flagship', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000236, 'GLM-5-Turbo Coding', 'zhipu-intl-codingplan', 'glm-5-turbo', 'Zhipu Coding Plan — GLM-5 fast variant', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000237, 'GLM-4.7 Coding', 'zhipu-intl-codingplan', 'glm-4.7', 'Zhipu Coding Plan — GLM-4.7', 0.2, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ update_time = EXCLUDED.update_time;
+
+-- -- Aliyun Coding Plan model catalog (cn backfill + intl mirror) ----------
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+ -- Backfill: qwen3.6-plus on aliyun-codingplan (cn).
+ (1000000162, 'Qwen3.6 Plus', 'aliyun-codingplan', 'qwen3.6-plus', '阿里云编码套餐 — Qwen3.6 Plus 旗舰', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ -- Aliyun Coding Plan (International) catalog.
+ (1000000241, 'Qwen3.6 Plus', 'aliyun-codingplan-intl', 'qwen3.6-plus', 'Aliyun Coding Plan (Intl) — Qwen3.6 Plus flagship', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000242, 'Qwen3.5 Plus', 'aliyun-codingplan-intl', 'qwen3.5-plus', 'Aliyun Coding Plan (Intl) — Qwen3.5 balanced', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000243, 'GLM-5', 'aliyun-codingplan-intl', 'glm-5', 'Aliyun Coding Plan (Intl) — GLM-5 hosted on DashScope', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000244, 'GLM-4.7', 'aliyun-codingplan-intl', 'glm-4.7', 'Aliyun Coding Plan (Intl) — GLM-4.7 hosted on DashScope', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000245, 'MiniMax M2.5', 'aliyun-codingplan-intl', 'MiniMax-M2.5', 'Aliyun Coding Plan (Intl) — MiniMax M2.5 hosted on DashScope', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000246, 'Kimi K2.5', 'aliyun-codingplan-intl', 'kimi-k2.5', 'Aliyun Coding Plan (Intl) — Kimi K2.5 hosted on DashScope', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000247, 'Qwen3 Max 2026-01-23', 'aliyun-codingplan-intl', 'qwen3-max-2026-01-23', 'Aliyun Coding Plan (Intl) — Qwen3 Max pinned snapshot', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000248, 'Qwen3 Coder Next', 'aliyun-codingplan-intl', 'qwen3-coder-next', 'Aliyun Coding Plan (Intl) — Qwen3 Coder Next, agentic coding', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000249, 'Qwen3 Coder Plus', 'aliyun-codingplan-intl', 'qwen3-coder-plus', 'Aliyun Coding Plan (Intl) — Qwen3 Coder Plus, agentic coding', 0.2, 8192, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET
+ name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ update_time = EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V91__widen_message_and_skill_content.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V91__widen_message_and_skill_content.sql
new file mode 100644
index 00000000..2a3bf7f9
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V91__widen_message_and_skill_content.sql
@@ -0,0 +1,5 @@
+-- V91: Widen mate_message.content / content_parts and mate_skill.skill_content
+-- from TEXT (64KB) to TEXT (16MB).
+--
+-- In KingbaseES/PostgreSQL, TEXT is already unlimited (up to 1GB),
+-- so this migration is a no-op. Keep for Flyway version compatibility.
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V92__mcp_server_tools_cache.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V92__mcp_server_tools_cache.sql
new file mode 100644
index 00000000..98867cc5
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V92__mcp_server_tools_cache.sql
@@ -0,0 +1,31 @@
+-- V92: Persist each MCP server's discovered tool list as a per-row JSON
+-- snapshot so the agent edit picker can render the tools even when the
+-- upstream server is briefly disconnected, and so the per-tool atomic
+-- binding flow has a stable place to resolve raw tool names from the
+-- prefixed callback name.
+--
+-- MySQL doesn't support ADD COLUMN IF NOT EXISTS natively (5.7 and most
+-- 8.0 deployments), so guard each ALTER with an INFORMATION_SCHEMA lookup
+-- + PREPARE/EXECUTE so re-runs become no-ops instead of failing the
+-- migration. Flyway's repair-on-startup compensates for any partial
+-- failure.
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_mcp_server' AND column_name = 'tools_cache_json'
+ ) THEN
+ ALTER TABLE mate_mcp_server ADD COLUMN tools_cache_json TEXT;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_mcp_server' AND column_name = 'tools_cache_updated_at'
+ ) THEN
+ ALTER TABLE mate_mcp_server ADD COLUMN tools_cache_updated_at TIMESTAMP NULL;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V93__xiaomi_mimo_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V93__xiaomi_mimo_provider.sql
new file mode 100644
index 00000000..506ec17b
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V93__xiaomi_mimo_provider.sql
@@ -0,0 +1,41 @@
+-- V93: register Xiaomi MiMo as an OpenAI-compatible provider with a
+-- pre-seeded model catalog. See the H2 copy for full background.
+
+-- -- Provider --------------------------------------------------------------
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'xiaomi-mimo',
+ 'Xiaomi MiMo',
+ '',
+ 'OpenAIChatModel',
+ '',
+ 'https://api.xiaomimimo.com/v1',
+ '{}',
+ 0, 0, 1, 1, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ api_key_prefix = EXCLUDED.api_key_prefix,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+-- -- Model catalog ---------------------------------------------------------
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+ (1000001200, 'MiMo V2.5 Pro', 'xiaomi-mimo', 'mimo-v2.5-pro', 'Xiaomi MiMo V2.5 Pro — latest flagship reasoning + coding model', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000001201, 'MiMo V2.5', 'xiaomi-mimo', 'mimo-v2.5', 'Xiaomi MiMo V2.5 — balanced model in the V2.5 family', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000001202, 'MiMo V2 Pro', 'xiaomi-mimo', 'mimo-v2-pro', 'Xiaomi MiMo V2 Pro — 1M token context window flagship', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000001203, 'MiMo V2 Omni', 'xiaomi-mimo', 'mimo-v2-omni', 'Xiaomi MiMo V2 Omni — multimodal variant supporting text, vision, audio', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000001204, 'MiMo V2 Flash', 'xiaomi-mimo', 'mimo-v2-flash', 'Xiaomi MiMo V2 Flash — fast, low-latency variant with 262K context', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ update_time = EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V94__register_office_render_tools.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V94__register_office_render_tools.sql
new file mode 100644
index 00000000..76d6143c
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V94__register_office_render_tools.sql
@@ -0,0 +1,16 @@
+-- V94: Register XlsxRenderTool / PptxRenderTool / PdfRenderTool as built-in tools.
+-- These mirror DocxRenderTool (V31) so agents can bind them through the tool picker
+-- and so the AvailableToolService surfaces them in the UI.
+-- Idempotent: ON DUPLICATE KEY UPDATE keeps rows in sync if they already exist.
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000020, 'XlsxRenderTool', 'XLSX Render', 'Render Markdown directly into a .xlsx workbook and return a one-time download link. In-process Apache POI; each # heading becomes a sheet, pipe tables become rows, numeric cells auto-detected.', 'builtin', 'xlsxRenderTool', '📊', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000021, 'PptxRenderTool', 'PPTX Render', 'Render Marp-style Markdown directly into a .pptx deck and return a one-time download link. In-process Apache POI; --- separates slides, # / ## titles, - bullets, .', 'builtin', 'pptxRenderTool', '🎞️', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, update_time=EXCLUDED.update_time;
+
+INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
+VALUES (1000000022, 'PdfRenderTool', 'PDF Render', 'Render Markdown into a final-form .pdf and return a one-time download link. Two backends (LibreOffice subprocess preferred, OpenPDF + Flying Saucer fallback); supports YAML frontmatter for cover / page header / page footer.', 'builtin', 'pdfRenderTool', '📄', 1, 1, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, display_name=EXCLUDED.display_name, description=EXCLUDED.description, bean_name=EXCLUDED.bean_name, icon=EXCLUDED.icon, update_time=EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V95__wiki_raw_material_cancel.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V95__wiki_raw_material_cancel.sql
new file mode 100644
index 00000000..4e2a1854
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V95__wiki_raw_material_cancel.sql
@@ -0,0 +1,15 @@
+-- V95: cancellation flag for in-progress wiki raw material processing.
+-- Lets the user request a stop on a long-running PDF analysis (e.g. when
+-- the embedding model has run out of credits) without having to delete
+-- the raw material. The processing pipeline checks the flag at its
+-- existing abort checkpoints and bails out with a 'cancelled' status.
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_wiki_raw_material' AND column_name = 'cancel_requested'
+ ) THEN
+ ALTER TABLE mate_wiki_raw_material ADD COLUMN cancel_requested BOOLEAN NOT NULL DEFAULT FALSE;
+ END IF;
+END $$;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V96__workflow_foundations.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V96__workflow_foundations.sql
new file mode 100644
index 00000000..ef4a4c14
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V96__workflow_foundations.sql
@@ -0,0 +1,154 @@
+-- V96: Foundational schema for the workflow runtime.
+-- Eight tables establish workflow identity (workflow + immutable revisions),
+-- run state (run + per-step rows + durable pause rows for await_approval),
+-- payload URI storage with inline / filesystem fallback, and trigger
+-- definitions paired with a dedup-window table for envelope-based event
+-- governance. CREATE TABLE IF NOT EXISTS is itself idempotent on MySQL.
+
+-- 1. Stable workflow identity + draft (1:1 with workflow row).
+CREATE TABLE IF NOT EXISTS mate_workflow (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ description VARCHAR(1024),
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ draft_json TEXT,
+ draft_schema_version VARCHAR(8),
+ draft_updated_by BIGINT,
+ draft_updated_at TIMESTAMP(3),
+ latest_revision_id BIGINT,
+ created_by BIGINT,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workflow_workspace_name ON mate_workflow (workspace_id, name, deleted);
+
+-- 2. Immutable published revisions; integer revision is monotonic per workflow.
+CREATE TABLE IF NOT EXISTS mate_workflow_revision (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workflow_id BIGINT NOT NULL,
+ revision INT NOT NULL,
+ graph_json TEXT NOT NULL,
+ schema_version VARCHAR(8) NOT NULL,
+ published_note VARCHAR(512),
+ published_by BIGINT,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workflow_revision ON mate_workflow_revision (workflow_id, revision);
+
+-- 3. Workflow run instance; payload bodies live behind URIs in mate_workflow_payload.
+CREATE TABLE IF NOT EXISTS mate_workflow_run (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workflow_id BIGINT NOT NULL,
+ revision_id BIGINT NOT NULL,
+ workspace_id BIGINT NOT NULL,
+ state VARCHAR(16) NOT NULL,
+ triggered_by VARCHAR(32),
+ triggered_meta TEXT,
+ initial_input_ref VARCHAR(256),
+ final_output_ref VARCHAR(256),
+ error_message VARCHAR(2048),
+ started_at TIMESTAMP(3),
+ completed_at TIMESTAMP(3),
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_workflow_run_started ON mate_workflow_run (workflow_id, started_at);
+
+-- 4. Per-step run row; iteration_index reserved for fan_out (and future loop).
+CREATE TABLE IF NOT EXISTS mate_workflow_run_step (
+ id BIGINT NOT NULL PRIMARY KEY,
+ run_id BIGINT NOT NULL,
+ step_index INT NOT NULL,
+ iteration_index INT,
+ step_name VARCHAR(128),
+ agent_id BIGINT,
+ state VARCHAR(16),
+ input_ref VARCHAR(256),
+ output_ref VARCHAR(256),
+ output_summary VARCHAR(512),
+ output_content_type VARCHAR(64),
+ error_message VARCHAR(2048),
+ duration_ms BIGINT,
+ token_input INT,
+ token_output INT,
+ started_at TIMESTAMP(3),
+ completed_at TIMESTAMP(3)
+);
+CREATE INDEX IF NOT EXISTS idx_workflow_run_step ON mate_workflow_run_step (run_id, step_index, iteration_index);
+
+-- 5. Durable pause rows so await_approval can resume across restarts.
+CREATE TABLE IF NOT EXISTS mate_workflow_run_pause (
+ id BIGINT NOT NULL PRIMARY KEY,
+ run_id BIGINT NOT NULL,
+ step_id BIGINT NOT NULL,
+ pause_kind VARCHAR(32) NOT NULL,
+ pause_token VARCHAR(128) NOT NULL,
+ external_approval_id BIGINT,
+ paused_at TIMESTAMP(3) NOT NULL,
+ resume_deadline TIMESTAMP(3),
+ resume_payload_ref VARCHAR(256),
+ resumed_at TIMESTAMP(3),
+ resume_outcome VARCHAR(32)
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workflow_pause_run_step ON mate_workflow_run_pause (run_id, step_id);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workflow_pause_token ON mate_workflow_run_pause (pause_token);
+CREATE INDEX IF NOT EXISTS idx_workflow_pause_external_approval ON mate_workflow_run_pause (external_approval_id);
+CREATE INDEX IF NOT EXISTS idx_workflow_pause_open_deadline ON mate_workflow_run_pause (resumed_at, resume_deadline);
+
+-- 6. Payload URI storage. Inline BYTEA for < 256KB; storage_kind=fs/s3/oss
+-- carries the external object key in storage_ref.
+CREATE TABLE IF NOT EXISTS mate_workflow_payload (
+ id BIGINT NOT NULL PRIMARY KEY,
+ payload_uri VARCHAR(256) NOT NULL,
+ workspace_id BIGINT NOT NULL,
+ content_bytes BYTEA,
+ storage_kind VARCHAR(16) NOT NULL,
+ storage_ref VARCHAR(512),
+ content_type VARCHAR(64),
+ sha256 CHAR(64),
+ size_bytes BIGINT,
+ created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_workflow_payload_uri ON mate_workflow_payload (payload_uri);
+CREATE INDEX IF NOT EXISTS idx_workflow_payload_workspace_created ON mate_workflow_payload (workspace_id, created_at);
+
+-- 7. Trigger definitions. pattern_version is a lamport counter that fire
+-- callbacks compare against on every fire to detect that another instance
+-- has updated the cron expression and self-cancel the local schedule.
+CREATE TABLE IF NOT EXISTS mate_trigger (
+ id BIGINT NOT NULL PRIMARY KEY,
+ workspace_id BIGINT NOT NULL,
+ name VARCHAR(128),
+ pattern_type VARCHAR(32) NOT NULL,
+ pattern_json TEXT NOT NULL,
+ target_type VARCHAR(16) NOT NULL,
+ target_id BIGINT NOT NULL,
+ payload_template TEXT,
+ rate_limit_per_min INT NOT NULL DEFAULT 60,
+ dedup_window_secs INT NOT NULL DEFAULT 60,
+ bot_self_filter SMALLINT NOT NULL DEFAULT 1,
+ enabled SMALLINT NOT NULL DEFAULT 1,
+ fire_count BIGINT NOT NULL DEFAULT 0,
+ max_fires BIGINT NOT NULL DEFAULT 0,
+ last_fired_at TIMESTAMP(3),
+ pattern_version BIGINT NOT NULL DEFAULT 1,
+ create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ deleted INT NOT NULL DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_trigger_workspace_enabled ON mate_trigger (workspace_id, enabled, deleted);
+CREATE INDEX IF NOT EXISTS idx_trigger_target ON mate_trigger (target_type, target_id);
+
+-- 8. Event dedup window. dedup_key is envelope.eventId, falling back to
+-- sourceHash when the upstream channel did not provide a stable id.
+CREATE TABLE IF NOT EXISTS mate_trigger_event (
+ id BIGINT NOT NULL PRIMARY KEY,
+ trigger_id BIGINT NOT NULL,
+ dedup_key VARCHAR(128) NOT NULL,
+ received_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ expires_at TIMESTAMP(3) NOT NULL
+);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_trigger_dedup ON mate_trigger_event (trigger_id, dedup_key);
+CREATE INDEX IF NOT EXISTS idx_trigger_event_expires ON mate_trigger_event (expires_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V97__workflow_purge_tombstones.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V97__workflow_purge_tombstones.sql
new file mode 100644
index 00000000..8b3a9125
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V97__workflow_purge_tombstones.sql
@@ -0,0 +1,9 @@
+-- See the matching H2 file for context. The workflow / trigger entities
+-- moved off @TableLogic to align with the project's hard-delete convention;
+-- this migration drops any tombstones the old soft-delete path persisted so
+-- list endpoints don't expose them after the annotation-driven filter is
+-- removed.
+
+DELETE FROM mate_workflow WHERE deleted <> 0;
+DELETE FROM mate_workflow_run WHERE deleted <> 0;
+DELETE FROM mate_trigger WHERE deleted <> 0;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V98__trigger_last_error.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V98__trigger_last_error.sql
new file mode 100644
index 00000000..dd98b00d
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V98__trigger_last_error.sql
@@ -0,0 +1,5 @@
+-- See the H2 file for context. KingbaseES (PostgreSQL) supports
+-- ADD COLUMN IF NOT EXISTS natively.
+
+ALTER TABLE mate_trigger ADD COLUMN IF NOT EXISTS last_error VARCHAR(2048);
+ALTER TABLE mate_trigger ADD COLUMN IF NOT EXISTS last_dispatched_at TIMESTAMP NULL;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V99__dashscope_compat_provider.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V99__dashscope_compat_provider.sql
new file mode 100644
index 00000000..47a80602
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V99__dashscope_compat_provider.sql
@@ -0,0 +1,49 @@
+-- V99: register a DashScope OpenAI-compatible provider entry alongside the
+-- existing native dashscope provider, plus the dot-versioned Qwen families
+-- (qwen3.5-*, qwen3.6-*) that only ship on compatible-mode/v1.
+--
+-- See the H2 copy for full background. The MySQL copy uses INSERT ... ON
+-- DUPLICATE KEY UPDATE; the api_key column is intentionally omitted from the
+-- update list so existing deployments that have already configured a key keep
+-- it (this only matters if a future migration re-applies a similar block;
+-- Flyway runs each version once today).
+
+-- -- Provider --------------------------------------------------------------
+INSERT INTO mate_model_provider (provider_id, name, api_key_prefix, chat_model, api_key, base_url, generate_kwargs, is_custom, is_local, support_model_discovery, support_connection_check, freeze_url, require_api_key, create_time, update_time)
+VALUES (
+ 'dashscope-compat',
+ 'DashScope (兼容模式)',
+ 'sk-',
+ 'OpenAIChatModel',
+ '',
+ 'https://dashscope.aliyuncs.com/compatible-mode/v1',
+ '{}',
+ 0, 0, 1, 1, 1, 1,
+ NOW(), NOW()
+)
+ON CONFLICT (provider_id) DO UPDATE SET name = EXCLUDED.name,
+ api_key_prefix = EXCLUDED.api_key_prefix,
+ chat_model = EXCLUDED.chat_model,
+ base_url = EXCLUDED.base_url,
+ generate_kwargs = EXCLUDED.generate_kwargs,
+ support_model_discovery = EXCLUDED.support_model_discovery,
+ support_connection_check = EXCLUDED.support_connection_check,
+ freeze_url = EXCLUDED.freeze_url,
+ require_api_key = EXCLUDED.require_api_key,
+ update_time = EXCLUDED.update_time;
+
+-- -- Model catalog ---------------------------------------------------------
+-- Only seed the variants that are publicly callable on compatible-mode. The
+-- -max / -vl-max variants exist in the marketplace but return 404 for general
+-- accounts; users with whitelist access can add them via Settings → Models.
+INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
+VALUES
+ (1000000601, 'Qwen3.6 Plus', 'dashscope-compat', 'qwen3.6-plus', '通义千问 3.6 Plus 旗舰,平衡推理与速度(兼容模式专属)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000603, 'Qwen3.5 Plus', 'dashscope-compat', 'qwen3.5-plus', '通义千问 3.5 Plus(兼容模式专属)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0),
+ (1000000605, 'Qwen3 VL Plus', 'dashscope-compat', 'qwen3-vl-plus', '通义千问 3 视觉理解 Plus,支持图像、视频输入(兼容模式专属)', 0.7, 4096, 0.8, 1, 1, 0, NOW(), NOW(), 0)
+ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name,
+ model_name = EXCLUDED.model_name,
+ description = EXCLUDED.description,
+ builtin = EXCLUDED.builtin,
+ enabled = EXCLUDED.enabled,
+ update_time = EXCLUDED.update_time;
diff --git a/mateclaw-server/src/main/resources/db/migration/kingbase/V9__usage_cache_tokens.sql b/mateclaw-server/src/main/resources/db/migration/kingbase/V9__usage_cache_tokens.sql
new file mode 100644
index 00000000..ae616828
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/kingbase/V9__usage_cache_tokens.sql
@@ -0,0 +1,24 @@
+-- V9: Track Anthropic prompt cache token usage
+-- RFC-014 Change 4: per-call cache_creation_input_tokens / cache_read_input_tokens
+-- accumulated daily so the dashboard can show cache hit rate and cost savings.
+-- (was originally numbered V8 but collided with V8__wiki_raw_progress.sql; renumbered to V9.)
+-- MySQL lacks ADD COLUMN IF NOT EXISTS; use INFORMATION_SCHEMA guard instead.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_usage_daily' AND column_name = 'cache_read_tokens'
+ ) THEN
+ ALTER TABLE mate_usage_daily ADD COLUMN cache_read_tokens BIGINT DEFAULT 0;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'mate_usage_daily' AND column_name = 'cache_write_tokens'
+ ) THEN
+ ALTER TABLE mate_usage_daily ADD COLUMN cache_write_tokens BIGINT DEFAULT 0;
+ END IF;
+END $$;