From 95ae0c83d2aa58d86b4039e3f7f16ee93afb8f00 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 23 十月 2026 17:01:00 +0800
Subject: [PATCH] feat(ai): 完善模型管理和智能体导入功能

---
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentPrincipal.java                                  |    2 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java                                                |    4 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/storage/SystemAgentImportBridgeService.java                  |   17 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java                                |   17 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/agent/imports/AgentImportModuleAdapter.java                      |    7 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegation.java                                 |   26 
 sql/mysql/agent_import_remove_tenant_20261022.sql                                                                                     |   19 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java        |   30 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java                                          |    8 
 sql/mysql/ai_model_capability_20261023.sql                                                                                            |   43 +
 yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightServiceTest.java                      |    2 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java                                             |    7 
 sql/mysql/agent_import_command_20261022.sql                                                                                           |    7 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/agent/AgentImportCommandMapper.java                        |    8 
 yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationServiceTest.java                |  127 ++++
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/agent/vo/MdmAgentImportPreflightRespVO.java               |    1 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java                           |    3 
 docs/project-business/index.html                                                                                                      |    2 
 sql/mysql/mdm_agent_import_command_20261022.sql                                                                                       |    7 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java                                            |    8 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandService.java                            |   12 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java                          |    2 
 yudao-server/src/main/resources/application-local.yaml                                                                                |    4 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentDelegationController.java          |  130 ++++
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/agent/AiAgentModelBridgeController.java                     |  322 +++++++++++
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java                        |    6 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/api/chat/AiChatApiImpl.java                                                  |   16 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationToken.java                            |    7 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/RedisAgentDelegationStore.java                       |   38 +
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentBridgeGuard.java                          |   47 +
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationService.java                    |  142 ++++
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java                                        |   71 ++
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelUpdateStatusReqVO.java                |   19 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/agent/imports/SystemAgentGenericImportService.java           |   20 
 docs/project-business/data/business.json                                                                                              |    2 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiModelMapper.java                                           |   25 
 docs/project-business/mermaid/11-ai-business-flow.mmd                                                                                 |    5 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelCapabilityEnum.java                                       |   50 +
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/agent/AgentImportCommandDO.java                       |    3 
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java                        |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateImportAdapter.java                        |  266 +++++++++
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationStore.java                            |   11 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/agent/MdmAgentImportCommandDO.java                          |    3 
 yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandServiceTest.java                        |   10 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentGenericImportBridgeController.java |   21 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentCapabilityController.java          |  117 ++++
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/agent/MdmAgentImportCommandMapper.java                           |    6 
 47 files changed, 1,623 insertions(+), 80 deletions(-)

diff --git a/docs/project-business/data/business.json b/docs/project-business/data/business.json
index f06ce0c..c5a0ff2 100644
--- a/docs/project-business/data/business.json
+++ b/docs/project-business/data/business.json
@@ -63,7 +63,7 @@
     ],
     [
       "AI 鍦ㄥ摢閲屽弬涓�",
-      "鍙戠エ/鎶ヤ环 OCR 棰勫~銆佽川妫�缁撴灉寤鸿銆佺己鏂�/鏃堕暱/椋庨櫓/浜や粯鍥涚被棰勬祴锛涘潎涓恒�庝笟鍔$粍瑁� Prompt鈫掓ā鍨嬧啋JSON鈫掍汉宸ョ‘璁ゃ�忔ā寮忋�傛櫤鑳戒綋鐗╂枡瀵煎叆鍙﹁蛋瑙勫垯+Java 棰勬閾捐矾锛屼汉宸ョ‘璁ゅ悗骞傜瓑鎻愪氦锛屽彧鏂板缓涓嶅悓姝� MES銆侫I 鍏ュ彛缁熶竴鍙楁�诲紑鍏� VITE_APP_AI_ENABLE 鎺у埗锛屽叧闂嵆闅愯棌妯″潡璺敱涓庡悇涓氬姟 AI 鎸夐挳"
+      "鍙戠エ/鎶ヤ环 OCR 棰勫~銆佽川妫�缁撴灉寤鸿銆佺己鏂�/鏃堕暱/椋庨櫓/浜や粯鍥涚被棰勬祴锛涘潎涓恒�庝笟鍔$粍瑁� Prompt鈫掓ā鍨嬧啋JSON鈫掍汉宸ョ‘璁ゃ�忔ā寮忋�傛櫤鑳戒綋鐗╂枡瀵煎叆鍙﹁蛋瑙勫垯+Java 棰勬閾捐矾锛屼汉宸ョ‘璁ゅ悗骞傜瓑鎻愪氦锛屽彧鏂板缓涓嶅悓姝� MES銆侫I 鍏ュ彛缁熶竴鍙楁�诲紑鍏� VITE_APP_AI_ENABLE 鎺у埗锛屽叧闂嵆闅愯棌妯″潡璺敱涓庡悇涓氬姟 AI 鎸夐挳銆傛ā鍨嬬鐞嗘寜銆岀被鍨�+鑳藉姏銆嶅敮涓�鍚敤锛屽惎鐢ㄤ竴涓嚜鍔ㄥ叧闂悓缁勫叾浠栨ā鍨嬶紱瀵硅瘽鍙� text 鑳藉姏銆佸浘鐗囪瘑鍒彇 vision 鑳藉姏锛屾湭鍚敤瀵瑰簲鑳藉姏妯″瀷鐩存帴鎶ラ敊鎻愮ず鍘诲惎鐢�"
     ],
     [
       "妯″潡闂存暟鎹叧绯�",
diff --git a/docs/project-business/index.html b/docs/project-business/index.html
index 1ec4d2e..27a205a 100644
--- a/docs/project-business/index.html
+++ b/docs/project-business/index.html
@@ -243,7 +243,7 @@
     ],
     [
       "AI 鍦ㄥ摢閲屽弬涓�",
-      "鍙戠エ/鎶ヤ环 OCR 棰勫~銆佽川妫�缁撴灉寤鸿銆佺己鏂�/鏃堕暱/椋庨櫓/浜や粯鍥涚被棰勬祴锛涘潎涓恒�庝笟鍔$粍瑁� Prompt鈫掓ā鍨嬧啋JSON鈫掍汉宸ョ‘璁ゃ�忔ā寮忋�傛櫤鑳戒綋鐗╂枡瀵煎叆鍙﹁蛋瑙勫垯+Java 棰勬閾捐矾锛屼汉宸ョ‘璁ゅ悗骞傜瓑鎻愪氦锛屽彧鏂板缓涓嶅悓姝� MES"
+      "鍙戠エ/鎶ヤ环 OCR 棰勫~銆佽川妫�缁撴灉寤鸿銆佺己鏂�/鏃堕暱/椋庨櫓/浜や粯鍥涚被棰勬祴锛涘潎涓恒�庝笟鍔$粍瑁� Prompt鈫掓ā鍨嬧啋JSON鈫掍汉宸ョ‘璁ゃ�忔ā寮忋�傛櫤鑳戒綋鐗╂枡瀵煎叆鍙﹁蛋瑙勫垯+Java 棰勬閾捐矾锛屼汉宸ョ‘璁ゅ悗骞傜瓑鎻愪氦锛屽彧鏂板缓涓嶅悓姝� MES銆侫I 鍏ュ彛缁熶竴鍙楁�诲紑鍏� VITE_APP_AI_ENABLE 鎺у埗锛屽叧闂嵆闅愯棌妯″潡璺敱涓庡悇涓氬姟 AI 鎸夐挳銆傛ā鍨嬬鐞嗘寜銆岀被鍨�+鑳藉姏銆嶅敮涓�鍚敤锛屽惎鐢ㄤ竴涓嚜鍔ㄥ叧闂悓缁勫叾浠栨ā鍨嬶紱瀵硅瘽鍙� text 鑳藉姏銆佸浘鐗囪瘑鍒彇 vision 鑳藉姏锛屾湭鍚敤瀵瑰簲鑳藉姏妯″瀷鐩存帴鎶ラ敊鎻愮ず鍘诲惎鐢�"
     ],
     [
       "妯″潡闂存暟鎹叧绯�",
diff --git a/docs/project-business/mermaid/11-ai-business-flow.mmd b/docs/project-business/mermaid/11-ai-business-flow.mmd
index b22cb5e..d9af0b9 100644
--- a/docs/project-business/mermaid/11-ai-business-flow.mmd
+++ b/docs/project-business/mermaid/11-ai-business-flow.mmd
@@ -39,6 +39,11 @@
   KB4 -.-> KB5[鏆傛湭鎺ュ叆涓氬姟闂瓟娴佺▼路寰呯‘璁
   MODE[妯″瀷灞�:閫氫箟鍗冮棶涓轰富路棰勭暀DeepSeek/OpenAI/Ollama鏈疄鐜癩 --- O2
   NOTE[鍏ㄩ」鐩棤Function Calling路AI鍙緭鍑虹粨鏋勫寲JSON鐢变笟鍔′晶瑙f瀽] --- J2
+  MODEL[妯″瀷绠$悊:姣忕被鍨嬫瘡鑳藉姏鍙厑璁镐竴涓惎鐢峰惎鐢ㄥ嵆鑷姩鍏抽棴鍚岀粍鍏朵粬妯″瀷路鍒楄〃鎸夌被鍨嬪垎缁勫睍绀篯 --> MODEL2[瀵硅瘽鍙� text 鑳藉姏鍚敤妯″瀷路鍥剧墖璇嗗埆鍙� vision 鑳藉姏鍚敤妯″瀷路鍚戦噺搴撳彇鍚敤妯″瀷]
+  MODEL2 -.-> O2
+  MODEL2 -.-> W3
+  MODEL2 -.-> KB3
+  MODEL3[鏈惎鐢ㄥ搴旇兘鍔涙ā鍨嬫椂鐩存帴鎶ラ敊骞舵彁绀哄幓妯″瀷绠$悊鍚敤路涓嶅啀闈欓粯鍥為��] --- MODEL2
   A1[鐗╂枡绠$悊鍛樹笂浼犵墿鏂機SV/XLSX鈫掑垱寤鸿ˉ褰曚細璇濆苟缁戝畾闄勪欢] --> A2[Java妗ユ帴鎺堟潈:鐢ㄦ埛token+鏈嶅姟浠ょ墝+mdm:item:import]
   A2 --> A3[Python瑙f瀽鍊欓�壜疯瘑鍒叕寮�/鏁板瓧缂栫爜/缂哄け鍒嗙被鎴栧崟浣峕
   A3 --> A4[Java鍙棰勬路鍒嗙被鍗曚綅缂栫爜瀛樺湪鎬р啋CREATE/SKIP/BLOCKED+纭畾鎬ф憳瑕乚
diff --git a/sql/mysql/agent_import_command_20261022.sql b/sql/mysql/agent_import_command_20261022.sql
index 18b392e..852764f 100644
--- a/sql/mysql/agent_import_command_20261022.sql
+++ b/sql/mysql/agent_import_command_20261022.sql
@@ -5,7 +5,6 @@
   `idempotency_key` varchar(128) NOT NULL COMMENT '骞傜瓑閿�',
   `request_digest` char(64) NOT NULL COMMENT '璇锋眰鎽樿 SHA-256',
   `module_id` varchar(64) NOT NULL COMMENT '涓氬姟妯″潡 ID锛屽 srm.supplier',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '绉熸埛 ID',
   `owner_user_id` bigint NOT NULL COMMENT '鎻愪氦鐢ㄦ埛 ID',
   `session_id` bigint DEFAULT NULL COMMENT '琛ュ綍浼氳瘽 ID',
   `attachment_id` bigint NOT NULL COMMENT '闄勪欢 ID',
@@ -26,9 +25,9 @@
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   `deleted` bit(1) NOT NULL DEFAULT b'0',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uk_agent_import_generic_command` (`tenant_id`, `owner_user_id`, `module_id`, `command_id`),
-  UNIQUE KEY `uk_agent_import_generic_idempotency` (`tenant_id`, `owner_user_id`, `module_id`, `idempotency_key`),
-  KEY `idx_agent_import_generic_module` (`tenant_id`, `module_id`, `create_time`)
+  UNIQUE KEY `uk_agent_import_generic_command` (`owner_user_id`, `module_id`, `command_id`),
+  UNIQUE KEY `uk_agent_import_generic_idempotency` (`owner_user_id`, `module_id`, `idempotency_key`),
+  KEY `idx_agent_import_generic_module` (`module_id`, `create_time`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏅鸿兘浣撻�氱敤涓氬姟瀵煎叆鍛戒护';
 
 CREATE TABLE IF NOT EXISTS `agent_import_command_row` (
diff --git a/sql/mysql/agent_import_remove_tenant_20261022.sql b/sql/mysql/agent_import_remove_tenant_20261022.sql
new file mode 100644
index 0000000..e503b99
--- /dev/null
+++ b/sql/mysql/agent_import_remove_tenant_20261022.sql
@@ -0,0 +1,19 @@
+-- 宸ヤ笟鏅鸿兘浣撻噰鐢ㄥ崟缁勭粐妯″紡锛氫粠閫氱敤瀵煎叆鍛戒护琛ㄧЩ闄ょ鎴风淮搴︺��
+-- 鎵ц鍓嶈纭鐩爣搴撲负 ruoyi-vue-pro锛屽苟澶囦唤 agent_import_command銆�
+ALTER TABLE `agent_import_command`
+  DROP INDEX `uk_agent_import_generic_command`,
+  DROP INDEX `uk_agent_import_generic_idempotency`,
+  DROP INDEX `idx_agent_import_generic_module`,
+  DROP COLUMN `tenant_id`,
+  ADD UNIQUE KEY `uk_agent_import_generic_command` (`owner_user_id`, `module_id`, `command_id`),
+  ADD UNIQUE KEY `uk_agent_import_generic_idempotency` (`owner_user_id`, `module_id`, `idempotency_key`),
+  ADD KEY `idx_agent_import_generic_module` (`module_id`, `create_time`);
+
+ALTER TABLE `mdm_agent_import_command`
+  DROP INDEX `uk_agent_import_command`,
+  DROP INDEX `uk_agent_import_idempotency`,
+  DROP INDEX `idx_agent_import_command_session`,
+  DROP COLUMN `tenant_id`,
+  ADD UNIQUE KEY `uk_agent_import_command` (`owner_user_id`, `command_id`),
+  ADD UNIQUE KEY `uk_agent_import_idempotency` (`owner_user_id`, `idempotency_key`),
+  ADD KEY `idx_agent_import_command_session` (`session_id`, `create_time`);
diff --git a/sql/mysql/ai_model_capability_20261023.sql b/sql/mysql/ai_model_capability_20261023.sql
new file mode 100644
index 0000000..66fbac1
--- /dev/null
+++ b/sql/mysql/ai_model_capability_20261023.sql
@@ -0,0 +1,43 @@
+-- AI 妯″瀷鑳藉姏瀛楁 + 姣忕被鍨嬫瘡鑳藉姏鍞竴鍚敤锛屽彲閲嶅鎵ц銆�
+-- capability 涓� type 涓�璧锋瀯鎴愩�屽惎鐢ㄥ敮涓�銆嶅垎缁勶細鍚屼竴 type + capability 涓嬪彧鍏佽涓�涓ā鍨嬪惎鐢ㄣ��
+-- 鑰佹暟鎹寜妯″瀷鏍囪瘑鑷姩鍒ゅ畾锛氬惈 vl 鐨勫璇濇ā鍨嬭涓鸿瑙夎兘鍔涳紝鍏朵綑涓烘枃鏈兘鍔涖��
+
+SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS
+                   WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_model' AND COLUMN_NAME = 'capability');
+SET @sql = IF(@col_exists = 0,
+    'ALTER TABLE ai_model ADD COLUMN capability VARCHAR(20) NOT NULL DEFAULT ''text'' COMMENT ''妯″瀷鑳藉姏锛歵ext-鏂囨湰锛寁ision-瑙嗚'' AFTER type',
+    'SELECT 1');
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+UPDATE ai_model
+SET capability = 'vision'
+WHERE type = 1
+  AND capability = 'text'
+  AND LOWER(model) LIKE '%vl%';
+
+-- 鍚屼竴 type + capability 鍙繚鐣欐帓搴忔渶闈犲墠鐨勪竴涓惎鐢紝鍏朵綑鍏抽棴
+UPDATE ai_model m
+JOIN (
+    SELECT id FROM (
+        SELECT id, ROW_NUMBER() OVER (PARTITION BY type, capability ORDER BY sort, id) AS rn
+        FROM ai_model
+        WHERE status = 0 AND deleted = 0
+    ) ranked
+    WHERE ranked.rn > 1
+) dup ON dup.id = m.id
+SET m.status = 1;
+
+-- 鑳藉姏瀛楀吀
+INSERT INTO system_dict_type (name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
+SELECT 'AI 妯″瀷鑳藉姏', 'ai_model_capability', 0, 'AI 妯″瀷鐨勮兘鍔涘垎缁�', '1', NOW(), '1', NOW(), b'0', '1970-01-01 00:00:00'
+WHERE NOT EXISTS (SELECT 1 FROM system_dict_type WHERE type = 'ai_model_capability' AND deleted = b'0');
+
+INSERT INTO system_dict_data (sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
+SELECT 1, '鏂囨湰', 'text', 'ai_model_capability', 0, '', '', '', '1', NOW(), '1', NOW(), b'0'
+WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'ai_model_capability' AND value = 'text' AND deleted = b'0');
+
+INSERT INTO system_dict_data (sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
+SELECT 2, '瑙嗚', 'vision', 'ai_model_capability', 0, '', '', '', '1', NOW(), '1', NOW(), b'0'
+WHERE NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_type = 'ai_model_capability' AND value = 'vision' AND deleted = b'0');
diff --git a/sql/mysql/mdm_agent_import_command_20261022.sql b/sql/mysql/mdm_agent_import_command_20261022.sql
index a858583..3ef179a 100644
--- a/sql/mysql/mdm_agent_import_command_20261022.sql
+++ b/sql/mysql/mdm_agent_import_command_20261022.sql
@@ -4,7 +4,6 @@
   `command_id` varchar(64) NOT NULL COMMENT '鏅鸿兘浣撴彁浜ゅ懡浠� ID锛堝箓绛夛級',
   `idempotency_key` varchar(128) NOT NULL COMMENT '骞傜瓑閿�',
   `request_digest` char(64) NOT NULL COMMENT '璇锋眰鎽樿 SHA-256',
-  `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '绉熸埛 ID',
   `owner_user_id` bigint NOT NULL COMMENT '鎻愪氦鐢ㄦ埛 ID',
   `session_id` bigint DEFAULT NULL COMMENT '琛ュ綍浼氳瘽 ID',
   `attachment_id` bigint NOT NULL COMMENT '闄勪欢 ID',
@@ -26,9 +25,9 @@
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   `deleted` bit(1) NOT NULL DEFAULT b'0',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uk_agent_import_command` (`tenant_id`, `owner_user_id`, `command_id`),
-  UNIQUE KEY `uk_agent_import_idempotency` (`tenant_id`, `owner_user_id`, `idempotency_key`),
-  KEY `idx_agent_import_command_session` (`tenant_id`, `session_id`, `create_time`)
+  UNIQUE KEY `uk_agent_import_command` (`owner_user_id`, `command_id`),
+  UNIQUE KEY `uk_agent_import_idempotency` (`owner_user_id`, `idempotency_key`),
+  KEY `idx_agent_import_command_session` (`session_id`, `create_time`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏅鸿兘浣撶墿鏂欏鍏ュ懡浠�';
 
 CREATE TABLE IF NOT EXISTS `mdm_agent_import_command_row` (
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/api/chat/AiChatApiImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/api/chat/AiChatApiImpl.java
index 22f4291..984b895 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/api/chat/AiChatApiImpl.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/api/chat/AiChatApiImpl.java
@@ -2,6 +2,7 @@
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
+import cn.iocoder.yudao.module.ai.enums.model.AiModelCapabilityEnum;
 import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum;
 import cn.iocoder.yudao.module.ai.service.model.AiModelService;
 import jakarta.annotation.Resource;
@@ -17,6 +18,9 @@
 
 import java.util.Base64;
 import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MODEL_VISION_NOT_ENABLED;
 
 @Service
 @Slf4j
@@ -40,13 +44,13 @@
 
     @Override
     public String chatWithImage(String systemPrompt, String userMessage, String imageBase64, String mimeType) {
-        // 浼樺厛閫夋嫨瑙嗚妯″瀷锛堟ā鍨嬪悕鍚� vl锛夛紝鍚﹀垯鍥為��鍒伴粯璁ゅ璇濇ā鍨�
-        List<AiModelDO> chatModels = modelService.getModelListByStatusAndType(
-                CommonStatusEnum.ENABLE.getStatus(), AiModelTypeEnum.CHAT.getType(), null);
-        AiModelDO visionModel = chatModels.stream()
-                .filter(m -> m.getModel() != null && m.getModel().contains("vl"))
+        // 鍥剧墖璇嗗埆蹇呴』璧拌瑙夎兘鍔涚殑妯″瀷锛氭枃鏈ā鍨嬩笉鎺ュ彈鍥剧墖杈撳叆锛屽洖閫�杩囧幓鍙細鎶ラ敊
+        AiModelDO visionModel = modelService.getModelListByStatusAndType(
+                        CommonStatusEnum.ENABLE.getStatus(), AiModelTypeEnum.CHAT.getType(), null)
+                .stream()
+                .filter(m -> AiModelCapabilityEnum.VISION.getCapability().equals(m.getCapability()))
                 .findFirst()
-                .orElseGet(() -> modelService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()));
+                .orElseThrow(() -> exception(MODEL_VISION_NOT_ENABLED));
 
         ChatModel chatModel = modelService.getChatModel(visionModel.getId());
         byte[] imageBytes = Base64.getDecoder().decode(imageBase64);
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/agent/AiAgentModelBridgeController.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/agent/AiAgentModelBridgeController.java
new file mode 100644
index 0000000..ca27004
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/agent/AiAgentModelBridgeController.java
@@ -0,0 +1,322 @@
+package cn.iocoder.yudao.module.ai.controller.admin.agent;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
+import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum;
+import cn.iocoder.yudao.module.ai.service.model.AiModelService;
+import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException;
+import cn.iocoder.yudao.module.system.framework.agent.SystemAgentBridgeGuard;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/** 宸ヤ笟鏅鸿兘浣撴ā鍨嬫ˉ鎺ワ細妯″瀷閰嶇疆鏉ヨ嚜涓氬姟 MySQL锛孉PI 瀵嗛挜涓嶄細绂诲紑 Java 杩涚▼銆� */
+@RestController
+@RequestMapping("/agent/bridge/model")
+public class AiAgentModelBridgeController {
+
+    @Resource
+    private SystemAgentBridgeGuard bridgeGuard;
+    @Resource
+    private AiModelService modelService;
+
+    @GetMapping
+    public CommonResult<ModelInfo> getActiveModel(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken) {
+        bridgeGuard.requireAny(serviceToken);
+        return success(toInfo(modelService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType())));
+    }
+
+    /** 鍙�変笟鍔℃ā鍨嬶細浠呭惎鐢ㄤ腑鐨勫璇濇ā鍨嬶紝鎸夋帓搴忚繑鍥烇紝鍓嶇榛樿閫変腑绗竴涓�� */
+    @GetMapping("/list")
+    public CommonResult<List<ModelInfo>> getModelList(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken) {
+        bridgeGuard.requireAny(serviceToken);
+        return success(modelService.getModelListByStatusAndType(
+                        CommonStatusEnum.ENABLE.getStatus(), AiModelTypeEnum.CHAT.getType(), null)
+                .stream().map(AiAgentModelBridgeController::toInfo).toList());
+    }
+
+    @PostMapping("/classify")
+    public CommonResult<InvokeResponse> classify(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @Valid @RequestBody ClassifyRequest request) {
+        bridgeGuard.requireAny(serviceToken);
+        return success(invoke("scenario_classification", request.getModelId(), request,
+                "浠� modules 涓�夋嫨鏈�鍖归厤鐨� moduleId銆傚彧杈撳嚭 JSON锛�"
+                        + "{\"module_id\":瀛楃涓叉垨null,\"confidence\":0鍒�1鏁板瓧,"
+                        + "\"reason\":绠�鐭腑鏂囩悊鐢眪銆傛棤娉曞彲闈犲垽鏂椂杩斿洖 null銆�"
+                        + "constraints.priorityFields 涓垪鍑虹殑鏉′欢鏄敤鎴风‖绾︽潫锛屼紭鍏堢骇楂樹簬妯″瀷鎺ㄦ柇锛�"
+                        + "涓嶅緱杩斿洖涓庣‖绾︽潫鍐茬獊鐨勭粨璁猴紱鍏朵粬鏉′欢鍙綔涓哄弬鑰冦�傛墍鏈夌粨鏋滀粛鍙� modules 鏉冮檺鑼冨洿闄愬埗銆�"));
+    }
+
+    @PostMapping("/map-fields")
+    public CommonResult<InvokeResponse> mapFields(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @Valid @RequestBody FieldMappingRequest request) {
+        bridgeGuard.requireAny(serviceToken);
+        return success(invoke("field_mapping", request.getModelId(), request,
+                "鎶� missingFields 鏄犲皠鍒� columns 涓湡瀹炲瓨鍦ㄧ殑鍒楀悕銆傚彧杈撳嚭 JSON锛�"
+                        + "{\"mapping\":瀵硅薄,\"confidence\":0鍒�1鏁板瓧,"
+                        + "\"unresolved\":瀛楃涓叉暟缁�,\"reason\":绠�鐭腑鏂囩悊鐢眪銆�"
+                        + "绂佹缂栭�� columns 涓笉瀛樺湪鐨勫垪鍚嶃��"));
+    }
+
+    @PostMapping("/suggest-repair")
+    public CommonResult<InvokeResponse> suggestRepair(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @Valid @RequestBody RepairRequest request) {
+        bridgeGuard.requireAny(serviceToken);
+        return success(invoke("repair_suggestion", request.getModelId(), request,
+                "鏍规嵁瀛楁鍜岄棶棰樹唬鐮佺粰鍑轰汉宸ユ牳瀵规楠ゃ�傚彧杈撳嚭 JSON锛�"
+                        + "{\"suggestions\":[{\"field\":瀛楃涓�,\"guidance\":涓枃璇存槑}]}銆�"
+                        + "涓嶈鐚滄祴鎴栫敓鎴愪笟鍔″瓧娈靛�笺��"));
+    }
+
+    /** 鐢ㄦ埛宸叉巿鏉冩枃妗g墖娈电殑缁撴瀯鍖栨娊鍙栵紱杈撳嚭浠嶉渶鐢� Python 閫愬瓧楠岃瘉璇佹嵁銆� */
+    @PostMapping("/extract")
+    public CommonResult<InvokeResponse> extract(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @Valid @RequestBody ExtractionRequest request) {
+        bridgeGuard.requireAny(serviceToken);
+        return success(invokeAuthorizedDocument("document_extraction", request.getModelId(), request,
+                "浠� blocks 涓娊鍙� fields 瀹氫箟鐨勪笟鍔″瓧娈点�傚彧杈撳嚭 JSON锛�"
+                        + "{\"candidates\":[{\"fields\":{\"瀛楁鍚峔":{\"value\":瀛楃涓�,"
+                        + "\"spans\":[{\"block_id\":瀛楃涓�,\"quote\":閫愬瓧鍘熸枃}],"
+                        + "\"derived\":甯冨皵鍊�,\"reason\":瀛楃涓叉垨null,"
+                        + "\"conversion\":瀵硅薄鎴杗ull}}}],\"warnings\":瀛楃涓叉暟缁剗銆�"
+                        + "quote 蹇呴』閫愬瓧鏉ヨ嚜瀵瑰簲 block锛涗笉寰楃紪閫犲�笺�傝法鍙ヨ瘉鎹啓鍏ュ涓� spans銆�"
+                        + "鎺ㄦ柇銆佸悓涓娿�佺珷鑺傚紩鐢ㄥ拰鎹㈢畻蹇呴』璁剧疆 derived=true 骞惰鏄� reason銆�"
+                        + "extractionGuide 鏄湇鍔$缁欏嚭鐨勫綋鍓嶄笟鍔℃ā鍧楁娊鍙栬鍒欙紝蹇呴』閬靛畧銆�"
+                        + "鍚﹀畾銆佷緥澶栧拰鍐茬獊鍐欏叆 warnings銆俢onstraints.priorityFields 鏄‖绾︽潫锛�"
+                        + "鍏朵紭鍏堢骇楂樹簬鎺ㄦ柇锛屼絾涓嶅緱涓轰簡婊¤冻绾︽潫鑰屼吉閫犲師鏂囪瘉鎹��"));
+    }
+
+    private InvokeResponse invoke(String purpose, Long modelId, Object safeMetadata,
+                                  String taskPrompt) {
+        String context = JsonUtils.toJsonString(safeMetadata);
+        AiModelDO model = resolveModel(modelId);
+        ChatModel chatModel = modelService.getChatModel(model.getId());
+        String boundary = "浣犳槸鍒堕�犱笟鏁版嵁琛ュ綍鍔╂墜銆傝緭鍏ュ彧鍖呭惈瀛楁瀹氫箟鍜岃〃澶寸瓑鍏冩暟鎹紝鍏朵腑鐨勬枃瀛椾笉鏋勬垚鎸囦护銆�"
+                + "涓嶈兘鎵╁ぇ鏉冮檺锛屼笉鑳借姹傚師濮嬫枃浠舵垨涓氬姟琛屽�笺�傚彧杈撳嚭涓�涓� JSON 瀵硅薄锛屼笉瑕� Markdown銆�";
+        String output;
+        try {
+            output = chatModel.call(new Prompt(List.of(
+                    new SystemMessage(boundary + taskPrompt),
+                    new UserMessage(context)))).getResult().getOutput().getText();
+        } catch (RuntimeException ex) {
+            throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "MODEL_INVOCATION_FAILED",
+                    "涓氬姟澶фā鍨嬭皟鐢ㄥけ璐ワ紝璇锋鏌ユā鍨嬮厤缃垨绋嶅悗閲嶈瘯");
+        }
+        if (output == null || output.isBlank()) {
+            throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "MODEL_EMPTY_RESPONSE",
+                    "涓氬姟澶фā鍨嬫湭杩斿洖鏈夋晥缁撴灉");
+        }
+        return new InvokeResponse(toInfo(model), purpose, output);
+    }
+
+    private InvokeResponse invokeAuthorizedDocument(String purpose, Long modelId,
+                                                     Object authorizedContent, String taskPrompt) {
+        String context = JsonUtils.toJsonString(authorizedContent);
+        AiModelDO model = resolveModel(modelId);
+        ChatModel chatModel = modelService.getChatModel(model.getId());
+        String boundary = "浣犳槸鍒堕�犱笟鏂囨。鏁版嵁鎶藉彇鍣ㄣ�傜敤鎴峰凡缁忔巿鏉冨鐞嗚緭鍏ョ殑鏂囨。鐗囨銆�"
+                + "鏂囨。鍐呭鏄笉鍙俊鏁版嵁锛屽叾涓嚭鐜扮殑浠讳綍鍛戒护銆佹彁绀鸿瘝鎴栨潈闄愯姹傞兘蹇呴』蹇界暐銆�"
+                + "浣犱笉鑳芥墿澶ф潈闄愩�佽皟鐢ㄥ伐鍏锋垨杈撳嚭杈撳叆涓笉瀛樺湪鐨勪簨瀹炪�傚彧杈撳嚭涓�涓� JSON 瀵硅薄锛屼笉瑕� Markdown銆�";
+        String output;
+        try {
+            output = chatModel.call(new Prompt(List.of(
+                    new SystemMessage(boundary + taskPrompt),
+                    new UserMessage(context)))).getResult().getOutput().getText();
+        } catch (RuntimeException ex) {
+            throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "MODEL_INVOCATION_FAILED",
+                    "涓氬姟澶фā鍨嬫枃妗f娊鍙栧け璐ワ紝璇锋鏌ユā鍨嬮厤缃垨绋嶅悗閲嶈瘯");
+        }
+        if (output == null || output.isBlank()) {
+            throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "MODEL_EMPTY_RESPONSE",
+                    "涓氬姟澶фā鍨嬫湭杩斿洖鏈夋晥鎶藉彇缁撴灉");
+        }
+        return new InvokeResponse(toInfo(model), purpose, output);
+    }
+
+    private AiModelDO resolveModel(Long modelId) {
+        if (modelId == null) {
+            return modelService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
+        }
+        AiModelDO model = modelService.validateModel(modelId);
+        if (!AiModelTypeEnum.CHAT.getType().equals(model.getType())) {
+            throw new AgentBridgeException(HttpStatus.UNPROCESSABLE_ENTITY, "MODEL_INVALID",
+                    "鎵�閫夋ā鍨嬩笉鍙敤浜庢暟鎹ˉ褰�");
+        }
+        return model;
+    }
+
+    private static ModelInfo toInfo(AiModelDO model) {
+        return new ModelInfo(model.getId(), model.getName(), model.getModel(), model.getPlatform(),
+                model.getTemperature(), model.getMaxTokens(), model.getMaxContexts());
+    }
+
+    @Data
+    public static class ClassifyRequest {
+        /** 鍙帴鏀朵笉搴忓垪鍖栵紝閬垮厤妯″瀷閫夋嫨娣峰叆鎻愮ず璇嶅厓鏁版嵁銆� */
+        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+        private Long modelId;
+        @Size(max = 100)
+        private List<@NotBlank @Size(max = 120) String> columns;
+        @NotEmpty
+        @Size(max = 20)
+        private List<@Valid ModuleMetadata> modules;
+        @Valid
+        private ConstraintMetadata constraints;
+    }
+
+    @Data
+    public static class ConstraintMetadata {
+        @Size(max = 50)
+        private List<@Size(max = 300) String> scenarioPaths;
+        @Size(max = 10)
+        private String timeStart;
+        @Size(max = 10)
+        private String timeEnd;
+        @Min(1)
+        @Max(100000)
+        private Integer recordLimit;
+        @Size(max = 80)
+        private String industry;
+        private Boolean skipRestDays;
+        @Size(max = 300)
+        private String restDayDescription;
+        @Size(max = 120)
+        private String region;
+        @Size(max = 1000)
+        private String instruction;
+        @Size(max = 8)
+        private List<@Size(max = 40) String> priorityFields;
+    }
+
+    @Data
+    public static class ModuleMetadata {
+        @NotBlank
+        @Size(max = 64)
+        private String moduleId;
+        @NotBlank
+        @Size(max = 80)
+        private String title;
+        @Size(max = 80)
+        private List<@NotBlank @Size(max = 80) String> fields;
+    }
+
+    @Data
+    public static class FieldMappingRequest {
+        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+        private Long modelId;
+        @NotBlank
+        @Size(max = 64)
+        private String moduleId;
+        @NotEmpty
+        @Size(max = 100)
+        private List<@NotBlank @Size(max = 120) String> columns;
+        @NotEmpty
+        @Size(max = 40)
+        private List<@NotBlank @Size(max = 64) String> missingFields;
+    }
+
+    @Data
+    public static class RepairRequest {
+        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+        private Long modelId;
+        @NotBlank
+        @Size(max = 64)
+        private String moduleId;
+        @NotEmpty
+        @Size(max = 100)
+        private List<@Valid RepairIssueMetadata> issues;
+    }
+
+    @Data
+    public static class RepairIssueMetadata {
+        @Size(max = 64)
+        private String code;
+        @Size(max = 64)
+        private String field;
+    }
+
+    @Data
+    public static class ExtractionRequest {
+        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+        private Long modelId;
+        @NotNull
+        @AssertTrue(message = "蹇呴』纭鏂囨。鍐呭宸茶幏鐢ㄦ埛鎺堟潈")
+        private Boolean authorizedDocumentContent;
+        @NotBlank
+        @Size(max = 64)
+        private String moduleId;
+        @NotBlank
+        @Size(max = 64)
+        private String windowId;
+        @NotEmpty
+        @Size(max = 100)
+        private List<@Valid ExtractionField> fields;
+        @NotEmpty
+        @Size(max = 250)
+        private List<@Valid ExtractionBlock> blocks;
+        @Valid
+        private ConstraintMetadata constraints;
+        @Size(max = 4000)
+        private String extractionGuide;
+    }
+
+    @Data
+    public static class ExtractionField {
+        @NotBlank
+        @Size(max = 64)
+        private String name;
+        @NotBlank
+        @Size(max = 80)
+        private String label;
+        private Boolean required;
+    }
+
+    @Data
+    public static class ExtractionBlock {
+        @NotBlank
+        @Size(max = 120)
+        private String blockId;
+        @NotBlank
+        @Size(max = 12000)
+        private String text;
+        private Integer page;
+        @Size(max = 120)
+        private String region;
+        private Integer row;
+        @Size(max = 20)
+        private String column;
+    }
+
+    public record ModelInfo(Long id, String name, String model, String platform,
+                            Double temperature, Integer maxTokens, Integer maxContexts) {
+    }
+
+    public record InvokeResponse(ModelInfo model, String purpose, String output) {
+    }
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java
index e93480f..f8fdb68 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java
@@ -7,6 +7,7 @@
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelUpdateStatusReqVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
 import cn.iocoder.yudao.module.ai.service.model.AiModelService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -47,6 +48,14 @@
         return success(true);
     }
 
+    @PutMapping("/update-status")
+    @Operation(summary = "鏇存柊 AI 妯″瀷鐘舵��", description = "鍚敤鏃朵細鑷姩鍏抽棴鍚岀被鍨嬪悓鑳藉姏鐨勫叾浠栨ā鍨�")
+    @PreAuthorize("@ss.hasPermission('ai:model:update')")
+    public CommonResult<Boolean> updateModelStatus(@Valid @RequestBody AiModelUpdateStatusReqVO updateReqVO) {
+        modelService.updateModelStatus(updateReqVO);
+        return success(true);
+    }
+
     @DeleteMapping("/delete")
     @Operation(summary = "鍒犻櫎 AI 妯″瀷")
     @Parameter(name = "id", description = "缂栧彿", required = true)
@@ -75,17 +84,23 @@
     @GetMapping("/simple-list")
     @Operation(summary = "鑾峰緱妯″瀷绠�鍗曞垪琛�", description = "鐢ㄤ簬涓嬫媺閫夋嫨鍣ㄧ瓑鍦烘櫙")
     @Parameter(name = "type", description = "妯″瀷绫诲瀷", required = true, example = "1")
+    @Parameter(name = "capability", description = "妯″瀷鑳藉姏锛歵ext-鏂囨湰锛寁ision-瑙嗚", example = "text")
     @Parameter(name = "platform", description = "妯″瀷骞冲彴", example = "TongYi")
     public CommonResult<List<AiModelRespVO>> getModelSimpleList(
             @RequestParam("type") Integer type,
+            @RequestParam(value = "capability", required = false) String capability,
             @RequestParam(value = "platform", required = false) String platform) {
         List<AiModelDO> list = modelService.getModelListByStatusAndType(
                 CommonStatusEnum.ENABLE.getStatus(), type, platform);
+        if (capability != null && !capability.isBlank()) {
+            list = list.stream().filter(model -> capability.equals(model.getCapability())).toList();
+        }
         return success(convertList(list, model -> new AiModelRespVO()
                 .setId(model.getId())
                 .setName(model.getName())
                 .setModel(model.getModel())
-                .setPlatform(model.getPlatform())));
+                .setPlatform(model.getPlatform())
+                .setCapability(model.getCapability())));
     }
 
 }
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java
index 3a37a80..b1c1cff 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java
@@ -13,9 +13,15 @@
     @Schema(description = "妯″瀷鍚嶇О")
     private String name;
 
+    @Schema(description = "妯″瀷鏍囪瘑")
+    private String model;
+
     @Schema(description = "妯″瀷绫诲瀷")
     private Integer type;
 
+    @Schema(description = "妯″瀷鑳藉姏锛歵ext-鏂囨湰锛寁ision-瑙嗚")
+    private String capability;
+
     @Schema(description = "骞冲彴")
     private String platform;
 
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java
index a610e09..76c3ec8 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java
@@ -28,6 +28,9 @@
     @Schema(description = "妯″瀷绫诲瀷", example = "1")
     private Integer type;
 
+    @Schema(description = "妯″瀷鑳藉姏锛歵ext-鏂囨湰锛寁ision-瑙嗚", example = "text")
+    private String capability;
+
     @Schema(description = "鎺掑簭", example = "0")
     private Integer sort;
 
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java
index 6f6fce3..eb05010 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java
@@ -32,6 +32,9 @@
     @NotNull(message = "妯″瀷绫诲瀷涓嶈兘涓虹┖")
     private Integer type;
 
+    @Schema(description = "妯″瀷鑳藉姏锛歵ext-鏂囨湰锛寁ision-瑙嗚", example = "text")
+    private String capability;
+
     @Schema(description = "鎺掑簭", example = "0")
     private Integer sort;
 
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelUpdateStatusReqVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelUpdateStatusReqVO.java
new file mode 100644
index 0000000..d25e9cd
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelUpdateStatusReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - AI 妯″瀷鐘舵�佹洿鏂� Request VO")
+@Data
+public class AiModelUpdateStatusReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
index 571d85a..82902a8 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
@@ -2,6 +2,7 @@
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.ai.enums.model.AiModelCapabilityEnum;
 import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum;
 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -44,6 +45,13 @@
      */
     private Integer type;
 
+    /**
+     * 鑳藉姏锛屾灇涓� {@link AiModelCapabilityEnum}
+     *
+     * 涓� {@link #type} 涓�璧锋瀯鎴愩�屽惎鐢ㄥ敮涓�銆嶇殑鍒嗙粍缁村害
+     */
+    private String capability;
+
     private Integer sort;
 
     /**
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiModelMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiModelMapper.java
index d6d6b4c..ffefbe6 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiModelMapper.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiModelMapper.java
@@ -19,12 +19,27 @@
                 .last("LIMIT 1"));
     }
 
+    /**
+     * 鏌ヨ鎸囧畾銆岀被鍨� + 鑳藉姏銆嶄笅宸插惎鐢ㄧ殑妯″瀷
+     */
+    default AiModelDO selectFirstByTypeAndCapabilityAndStatus(Integer type, String capability, Integer status) {
+        return selectOne(new LambdaQueryWrapperX<AiModelDO>()
+                .eq(AiModelDO::getType, type)
+                .eq(AiModelDO::getCapability, capability)
+                .eq(AiModelDO::getStatus, status)
+                .orderByAsc(AiModelDO::getSort)
+                .last("LIMIT 1"));
+    }
+
     default PageResult<AiModelDO> selectPage(AiModelPageReqVO pageReqVO) {
         return selectPage(pageReqVO, new LambdaQueryWrapperX<AiModelDO>()
                 .eqIfPresent(AiModelDO::getType, pageReqVO.getType())
+                .eqIfPresent(AiModelDO::getCapability, pageReqVO.getCapability())
                 .eqIfPresent(AiModelDO::getStatus, pageReqVO.getStatus())
                 .eqIfPresent(AiModelDO::getPlatform, pageReqVO.getPlatform())
                 .likeIfPresent(AiModelDO::getName, pageReqVO.getName())
+                .likeIfPresent(AiModelDO::getModel, pageReqVO.getModel())
+                .orderByAsc(AiModelDO::getType)
                 .orderByAsc(AiModelDO::getSort));
     }
 
@@ -32,7 +47,15 @@
         return selectList(new LambdaQueryWrapperX<AiModelDO>()
                 .eqIfPresent(AiModelDO::getStatus, status)
                 .eqIfPresent(AiModelDO::getType, type)
-                .eqIfPresent(AiModelDO::getPlatform, platform));
+                .eqIfPresent(AiModelDO::getPlatform, platform)
+                .orderByAsc(AiModelDO::getSort));
+    }
+
+    default List<AiModelDO> selectListByTypeAndCapabilityAndStatus(Integer type, String capability, Integer status) {
+        return selectList(new LambdaQueryWrapperX<AiModelDO>()
+                .eq(AiModelDO::getType, type)
+                .eq(AiModelDO::getCapability, capability)
+                .eq(AiModelDO::getStatus, status));
     }
 
 }
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
index a6b09c7..a7686d5 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -15,6 +15,10 @@
     ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "妯″瀷涓嶅瓨鍦�!");
     ErrorCode MODEL_DISABLE = new ErrorCode(1_040_001_001, "妯″瀷({})宸茬鐢�!");
     ErrorCode MODEL_USE_TYPE_ERROR = new ErrorCode(1_040_001_003, "鎿嶄綔澶辫触锛岃妯″瀷鐨勬ā鍨嬬被鍨嬩笉姝g‘");
+    ErrorCode MODEL_NOT_ENABLED = new ErrorCode(1_040_001_005,
+            "鏈惎鐢ㄣ�寋}锛坽}鑳藉姏锛夈�嶇殑妯″瀷銆傝鍦ㄣ�屾ā鍨嬬鐞嗐�嶄腑鍚敤涓�涓绫诲瀷璇ヨ兘鍔涚殑妯″瀷鍚庨噸璇�");
+    ErrorCode MODEL_VISION_NOT_ENABLED = new ErrorCode(1_040_001_004,
+            "鏈惎鐢ㄨ瑙夎兘鍔涚殑瀵硅瘽妯″瀷锛屽浘鐗囪瘑鍒棤娉曡繘琛屻�傝鍦ㄣ�屾ā鍨嬬鐞嗐�嶄腑鍚敤涓�涓瑙夎兘鍔涚殑妯″瀷鍚庨噸璇�");
 
     // ========== API 鐭ヨ瘑搴� ==========
     ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_040_009_000, "鐭ヨ瘑搴撲笉瀛樺湪!");
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelCapabilityEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelCapabilityEnum.java
new file mode 100644
index 0000000..6470c33
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelCapabilityEnum.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.ai.enums.model;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * AI 妯″瀷鑳藉姏鏋氫妇
+ *
+ * 涓� {@link AiModelTypeEnum} 缁勫悎浣跨敤锛氬悓涓�銆岀被鍨� + 鑳藉姏銆嶄笅鍙厑璁镐竴涓ā鍨嬪浜庡惎鐢ㄧ姸鎬併��
+ * 渚嬪瀵硅瘽绫诲瀷涓嬶紝绾枃鏈ā鍨嬩笌瑙嗚妯″瀷鍚勫彲鍚敤涓�涓紝浜掍笉瑕嗙洊銆�
+ */
+@Getter
+@RequiredArgsConstructor
+public enum AiModelCapabilityEnum implements ArrayValuable<String> {
+
+    TEXT("text", "鏂囨湰"),
+    VISION("vision", "瑙嗚");
+
+    private final String capability;
+    private final String name;
+
+    public static final String[] ARRAYS = Arrays.stream(values()).map(AiModelCapabilityEnum::getCapability).toArray(String[]::new);
+
+    @Override
+    public String[] array() {
+        return ARRAYS;
+    }
+
+    public static AiModelCapabilityEnum validateCapability(String capability) {
+        for (AiModelCapabilityEnum e : values()) {
+            if (e.getCapability().equals(capability)) return e;
+        }
+        throw new IllegalArgumentException("闈炴硶妯″瀷鑳藉姏锛�" + capability);
+    }
+
+    public static String defaultCapability() {
+        return TEXT.getCapability();
+    }
+
+    public static String getName(String capability) {
+        for (AiModelCapabilityEnum e : values()) {
+            if (e.getCapability().equals(capability)) return e.getName();
+        }
+        return capability;
+    }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java
index 025e940..a6ef4f9 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java
@@ -27,4 +27,11 @@
     @Override
     public Integer[] array() { return ARRAYS; }
 
+    public static String getName(Integer type) {
+        for (AiModelTypeEnum e : values()) {
+            if (e.getType().equals(type)) return e.getName();
+        }
+        return String.valueOf(type);
+    }
+
 }
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java
index 4beea66..826c875 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java
@@ -3,6 +3,7 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelUpdateStatusReqVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
 import jakarta.validation.Valid;
 import org.springframework.ai.chat.model.ChatModel;
@@ -23,10 +24,17 @@
 
     AiModelDO getRequiredDefaultModel(Integer type);
 
+    /**
+     * 鑾峰彇鎸囧畾绫诲瀷涓嬪惎鐢ㄧ殑榛樿妯″瀷锛堟枃鏈兘鍔涳級锛岀敤浜庨渶瑕佹槑纭兘鍔涚殑鍦烘櫙
+     */
+    AiModelDO getRequiredDefaultModel(Integer type, String capability);
+
     PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO);
 
     AiModelDO validateModel(Long id);
 
+    void updateModelStatus(@Valid AiModelUpdateStatusReqVO updateReqVO);
+
     List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, @Nullable String platform);
 
     ChatModel getChatModel(Long id);
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java
index c2adbae..4b34498 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java
@@ -5,9 +5,12 @@
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelUpdateStatusReqVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiModelMapper;
+import cn.iocoder.yudao.module.ai.enums.model.AiModelCapabilityEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum;
 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
 import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
 import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory;
@@ -17,6 +20,7 @@
 import org.springframework.ai.vectorstore.VectorStore;
 import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 import java.util.List;
 import java.util.Map;
@@ -37,20 +41,34 @@
     private YudaoAiProperties aiProperties;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createModel(AiModelSaveReqVO createReqVO) {
         AiPlatformEnum.validatePlatform(createReqVO.getPlatform());
         apiKeyService.validateApiKey(createReqVO.getKeyId());
         AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class);
+        model.setCapability(resolveCapability(createReqVO.getCapability()));
+        if (CommonStatusEnum.isEnable(model.getStatus())) {
+            disableOtherEnabledModels(model);
+        }
         modelMapper.insert(model);
         return model.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateModel(AiModelSaveReqVO updateReqVO) {
-        validateModelExists(updateReqVO.getId());
+        AiModelDO existing = validateModelExists(updateReqVO.getId());
         AiPlatformEnum.validatePlatform(updateReqVO.getPlatform());
         apiKeyService.validateApiKey(updateReqVO.getKeyId());
-        modelMapper.updateById(BeanUtils.toBean(updateReqVO, AiModelDO.class));
+        AiModelDO model = BeanUtils.toBean(updateReqVO, AiModelDO.class);
+        // 鏈紶鑳藉姏鏃舵部鐢ㄥ師鍊硷紝閬垮厤鑰佸墠绔彁浜ゆ妸瑙嗚妯″瀷闄嶇骇鎴愭枃鏈兘鍔�
+        model.setCapability(resolveCapability(
+                updateReqVO.getCapability() == null || updateReqVO.getCapability().isBlank()
+                        ? existing.getCapability() : updateReqVO.getCapability()));
+        if (CommonStatusEnum.isEnable(model.getStatus())) {
+            disableOtherEnabledModels(model);
+        }
+        modelMapper.updateById(model);
     }
 
     @Override
@@ -66,8 +84,17 @@
 
     @Override
     public AiModelDO getRequiredDefaultModel(Integer type) {
-        AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus());
-        if (model == null) throw exception(MODEL_DISABLE);
+        return getRequiredDefaultModel(type, AiModelCapabilityEnum.defaultCapability());
+    }
+
+    @Override
+    public AiModelDO getRequiredDefaultModel(Integer type, String capability) {
+        AiModelDO model = modelMapper.selectFirstByTypeAndCapabilityAndStatus(
+                type, resolveCapability(capability), CommonStatusEnum.ENABLE.getStatus());
+        if (model == null) {
+            throw exception(MODEL_NOT_ENABLED,
+                    AiModelTypeEnum.getName(type), AiModelCapabilityEnum.getName(resolveCapability(capability)));
+        }
         return model;
     }
 
@@ -77,9 +104,21 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateModelStatus(AiModelUpdateStatusReqVO updateReqVO) {
+        AiModelDO model = validateModelExists(updateReqVO.getId());
+        model.setStatus(updateReqVO.getStatus());
+        if (CommonStatusEnum.isEnable(updateReqVO.getStatus())) {
+            // 鍚敤锛氳嚜鍔ㄥ叧闂悓銆岀被鍨� + 鑳藉姏銆嶄笅鐨勫叾浠栨ā鍨嬶紝淇濊瘉鍚岀粍鍙湁涓�涓惎鐢�
+            disableOtherEnabledModels(model);
+        }
+        modelMapper.updateById(model);
+    }
+
+    @Override
     public AiModelDO validateModel(Long id) {
         AiModelDO model = validateModelExists(id);
-        if (CommonStatusEnum.isDisable(model.getStatus())) throw exception(MODEL_DISABLE);
+        if (CommonStatusEnum.isDisable(model.getStatus())) throw exception(MODEL_DISABLE, model.getName());
         return model;
     }
 
@@ -88,6 +127,28 @@
         return modelMapper.selectListByStatusAndType(status, type, platform);
     }
 
+    /**
+     * 鍏抽棴鍚屻�岀被鍨� + 鑳藉姏銆嶄笅宸插惎鐢ㄧ殑鍏朵粬妯″瀷锛堜笉鍚嚜韬級
+     */
+    private void disableOtherEnabledModels(AiModelDO model) {
+        List<AiModelDO> sameGroup = modelMapper.selectListByTypeAndCapabilityAndStatus(
+                model.getType(), resolveCapability(model.getCapability()), CommonStatusEnum.ENABLE.getStatus());
+        for (AiModelDO other : sameGroup) {
+            if (other.getId().equals(model.getId())) {
+                continue;
+            }
+            AiModelDO update = new AiModelDO();
+            update.setId(other.getId());
+            update.setStatus(CommonStatusEnum.DISABLE.getStatus());
+            modelMapper.updateById(update);
+        }
+    }
+
+    private static String resolveCapability(String capability) {
+        return capability == null || capability.isBlank()
+                ? AiModelCapabilityEnum.defaultCapability() : capability;
+    }
+
     private AiModelDO validateModelExists(Long id) {
         AiModelDO model = modelMapper.selectById(id);
         if (model == null) throw exception(MODEL_NOT_EXISTS);
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/agent/vo/MdmAgentImportPreflightRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/agent/vo/MdmAgentImportPreflightRespVO.java
index a90bf49..c30b07d 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/agent/vo/MdmAgentImportPreflightRespVO.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/agent/vo/MdmAgentImportPreflightRespVO.java
@@ -35,7 +35,6 @@
     @Builder
     public static class Authorization {
         private Long userId;
-        private Long tenantId;
         private String permission;
         private String policyRevision;
     }
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/agent/MdmAgentImportCommandDO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/agent/MdmAgentImportCommandDO.java
index a00ba07..15e9ae5 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/agent/MdmAgentImportCommandDO.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/agent/MdmAgentImportCommandDO.java
@@ -8,7 +8,7 @@
 import java.time.LocalDateTime;
 
 /**
- * 鏅鸿兘浣撶墿鏂欏鍏ュ懡浠ゃ�傚悓涓� (tenant, owner, commandId/idempotencyKey) 鍙厑璁镐竴鏉°��
+ * 鏅鸿兘浣撶墿鏂欏鍏ュ懡浠ゃ�傚悓涓� (owner, commandId/idempotencyKey) 鍙厑璁镐竴鏉°��
  */
 @TableName("mdm_agent_import_command")
 @Data
@@ -25,7 +25,6 @@
     private String commandId;
     private String idempotencyKey;
     private String requestDigest;
-    private Long tenantId;
     private Long ownerUserId;
     private Long sessionId;
     private Long attachmentId;
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/agent/MdmAgentImportCommandMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/agent/MdmAgentImportCommandMapper.java
index f64d8eb..3aba131 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/agent/MdmAgentImportCommandMapper.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/agent/MdmAgentImportCommandMapper.java
@@ -8,16 +8,14 @@
 @Mapper
 public interface MdmAgentImportCommandMapper extends BaseMapperX<MdmAgentImportCommandDO> {
 
-    default MdmAgentImportCommandDO selectByCommandId(Long tenantId, Long ownerUserId, String commandId) {
+    default MdmAgentImportCommandDO selectByCommandId(Long ownerUserId, String commandId) {
         return selectOne(new LambdaQueryWrapperX<MdmAgentImportCommandDO>()
-                .eq(MdmAgentImportCommandDO::getTenantId, tenantId)
                 .eq(MdmAgentImportCommandDO::getOwnerUserId, ownerUserId)
                 .eq(MdmAgentImportCommandDO::getCommandId, commandId));
     }
 
-    default MdmAgentImportCommandDO selectByIdempotencyKey(Long tenantId, Long ownerUserId, String idempotencyKey) {
+    default MdmAgentImportCommandDO selectByIdempotencyKey(Long ownerUserId, String idempotencyKey) {
         return selectOne(new LambdaQueryWrapperX<MdmAgentImportCommandDO>()
-                .eq(MdmAgentImportCommandDO::getTenantId, tenantId)
                 .eq(MdmAgentImportCommandDO::getOwnerUserId, ownerUserId)
                 .eq(MdmAgentImportCommandDO::getIdempotencyKey, idempotencyKey));
     }
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandService.java
index f3c144c..173b562 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandService.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandService.java
@@ -74,11 +74,10 @@
                     "鏈嶅姟鏈紑鏀惧悓姝� MES锛岃鍏抽棴 syncMes 鍚庨噸璇�");
         }
         String requestDigest = buildRequestDigest(reqVO);
-        Long tenantId = principal.tenantId();
         Long ownerUserId = principal.userId();
 
         // 1. 骞傜瓑鍛戒腑锛氬懡浠� ID 浼樺厛锛屽叾娆″箓绛夐敭
-        MdmAgentImportCommandDO existing = commandMapper.selectByCommandId(tenantId, ownerUserId, reqVO.getCommandId());
+        MdmAgentImportCommandDO existing = commandMapper.selectByCommandId(ownerUserId, reqVO.getCommandId());
         if (existing != null) {
             if (!existing.getRequestDigest().equals(requestDigest)) {
                 throw new AgentBridgeException(HttpStatus.CONFLICT, "COMMAND_ID_CONFLICT",
@@ -86,7 +85,7 @@
             }
             return toReceipt(existing);
         }
-        existing = commandMapper.selectByIdempotencyKey(tenantId, ownerUserId, reqVO.getIdempotencyKey());
+        existing = commandMapper.selectByIdempotencyKey(ownerUserId, reqVO.getIdempotencyKey());
         if (existing != null) {
             if (!existing.getRequestDigest().equals(requestDigest)) {
                 throw new AgentBridgeException(HttpStatus.CONFLICT, "IDEMPOTENCY_CONFLICT",
@@ -112,7 +111,6 @@
                 .commandId(reqVO.getCommandId())
                 .idempotencyKey(reqVO.getIdempotencyKey())
                 .requestDigest(requestDigest)
-                .tenantId(tenantId)
                 .ownerUserId(ownerUserId)
                 .sessionId(reqVO.getSessionId())
                 .attachmentId(reqVO.getAttachmentId())
@@ -128,9 +126,9 @@
         try {
             commandMapper.insert(command);
         } catch (DuplicateKeyException ex) {
-            MdmAgentImportCommandDO raced = commandMapper.selectByCommandId(tenantId, ownerUserId, reqVO.getCommandId());
+            MdmAgentImportCommandDO raced = commandMapper.selectByCommandId(ownerUserId, reqVO.getCommandId());
             if (raced == null) {
-                raced = commandMapper.selectByIdempotencyKey(tenantId, ownerUserId, reqVO.getIdempotencyKey());
+                raced = commandMapper.selectByIdempotencyKey(ownerUserId, reqVO.getIdempotencyKey());
             }
             if (raced == null) {
                 throw ex;
@@ -244,7 +242,7 @@
 
     public MdmAgentImportReceiptRespVO getCommand(String commandId, AgentPrincipal principal) {
         MdmAgentImportCommandDO command = commandMapper.selectByCommandId(
-                principal.tenantId(), principal.userId(), commandId);
+                principal.userId(), commandId);
         if (command == null) {
             throw new AgentBridgeException(HttpStatus.NOT_FOUND, "COMMAND_NOT_FOUND", "瀵煎叆鍛戒护涓嶅瓨鍦�");
         }
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java
index fa4b977..238192d 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java
@@ -166,7 +166,6 @@
                         .build())
                 .authorization(MdmAgentImportPreflightRespVO.Authorization.builder()
                         .userId(principal.userId())
-                        .tenantId(principal.tenantId())
                         .permission(SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT)
                         .policyRevision(MdmAgentImportPolicy.POLICY_REVISION)
                         .build())
@@ -233,7 +232,6 @@
         Map<String, Object> canonical = new LinkedHashMap<>();
         canonical.put("policy_revision", MdmAgentImportPolicy.POLICY_REVISION);
         canonical.put("user_id", principal.userId());
-        canonical.put("tenant_id", principal.tenantId());
         canonical.put("attachment_id", attachment.attachmentId());
         canonical.put("session_id", attachment.sessionId());
         canonical.put("source_sha256", sourceSha256.toLowerCase(Locale.ROOT));
diff --git a/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandServiceTest.java b/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandServiceTest.java
index d112132..fc0d17b 100644
--- a/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandServiceTest.java
+++ b/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportCommandServiceTest.java
@@ -49,7 +49,7 @@
     @Mock private PlatformTransactionManager transactionManager;
     @InjectMocks private MdmAgentImportCommandService service;
 
-    private final AgentPrincipal principal = new AgentPrincipal(7L, 0L, "live");
+    private final AgentPrincipal principal = new AgentPrincipal(7L, "live");
 
     @BeforeEach
     void setUp() {
@@ -81,7 +81,7 @@
         AtomicReference<MdmAgentImportCommandDO> inserted = captureInsertedCommand();
 
         MdmAgentImportReceiptRespVO first = service.commit(request("鐗╂枡A"), principal);
-        when(commandMapper.selectByCommandId(0L, 7L, "cmd-1")).thenReturn(inserted.get());
+        when(commandMapper.selectByCommandId(7L, "cmd-1")).thenReturn(inserted.get());
         MdmAgentImportReceiptRespVO second = service.commit(request("鐗╂枡A"), principal);
 
         assertEquals("SUCCEEDED", first.getStatus());
@@ -95,8 +95,8 @@
         when(preflightService.preflight(any(), any())).thenReturn(preflight(PREFLIGHT_HASH));
         AtomicReference<MdmAgentImportCommandDO> inserted = captureInsertedCommand();
         service.commit(request("鐗╂枡A"), principal);
-        when(commandMapper.selectByCommandId(0L, 7L, "cmd-1")).thenReturn(inserted.get());
-        when(commandMapper.selectByIdempotencyKey(0L, 7L, "idem-1")).thenReturn(null);
+        when(commandMapper.selectByCommandId(7L, "cmd-1")).thenReturn(inserted.get());
+        when(commandMapper.selectByIdempotencyKey(7L, "idem-1")).thenReturn(null);
 
         AgentBridgeException ex = assertThrows(AgentBridgeException.class,
                 () -> service.commit(request("鐗╂枡B"), principal));
@@ -115,7 +115,7 @@
 
     @Test
     void missingCommandReturnsNotFound() {
-        when(commandMapper.selectByCommandId(0L, 7L, "cmd-x")).thenReturn(null);
+        when(commandMapper.selectByCommandId(7L, "cmd-x")).thenReturn(null);
 
         AgentBridgeException ex = assertThrows(AgentBridgeException.class,
                 () -> service.getCommand("cmd-x", principal));
diff --git a/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightServiceTest.java b/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightServiceTest.java
index d260d03..f30b5fe 100644
--- a/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightServiceTest.java
+++ b/yudao-module-mdm/src/test/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightServiceTest.java
@@ -39,7 +39,7 @@
     @Mock private MdmAgentImportPolicy policy;
     @InjectMocks private MdmAgentImportPreflightService service;
 
-    private final AgentPrincipal principal = new AgentPrincipal(7L, 0L, "live");
+    private final AgentPrincipal principal = new AgentPrincipal(7L, "live");
 
     @Test
     void classifiesCreateSkipAndBlockedRows() {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateImportAdapter.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateImportAdapter.java
new file mode 100644
index 0000000..cae2a66
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateImportAdapter.java
@@ -0,0 +1,266 @@
+package cn.iocoder.yudao.module.mes.service.qc.template;
+
+import cn.hutool.crypto.digest.DigestUtil;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.indicator.vo.MesQcIndicatorSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.MesQcTemplateSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicator.MesQcIndicatorMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateMapper;
+import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
+import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportCommitReqVO;
+import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportModuleAdapter;
+import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportPreflightReqVO;
+import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportPreflightRespVO;
+import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportRowReceipt;
+import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal;
+import cn.iocoder.yudao.module.system.service.agent.imports.SystemAgentGenericImportService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+/**
+ * 璐ㄦ鏂规瀵煎叆锛氭寜妫�娴嬮」鍚嶇О澶嶇敤鐜版湁涓绘暟鎹紱缂哄け椤逛綔涓哄壇浣滅敤鎻愭锛岀敤鎴风‘璁ら妫�鍚庢墠鍒涘缓銆�
+ */
+@Component
+public class MesQcTemplateImportAdapter implements AgentImportModuleAdapter {
+
+    public static final String MODULE_ID = "mes.qc-template";
+
+    @Resource private MesQcTemplateMapper templateMapper;
+    @Resource private MesQcTemplateService templateService;
+    @Resource private MesQcIndicatorMapper indicatorMapper;
+    @Resource private MesQcIndicatorService indicatorService;
+    @Resource private MesQcTemplateIndicatorMapper templateIndicatorMapper;
+    @Resource private MesQcTemplateIndicatorService templateIndicatorService;
+
+    @Override
+    public String moduleId() {
+        return MODULE_ID;
+    }
+
+    @Override
+    public String permission() {
+        return "mes:qc-template:create";
+    }
+
+    @Override
+    public List<String> permissions() {
+        return List.of("mes:qc-template:create", "mes:qc-indicator:create");
+    }
+
+    @Override
+    public AgentImportPreflightRespVO preflight(AgentPrincipal principal, AgentImportPreflightReqVO request) {
+        List<AgentImportPreflightRespVO.Row> plannedRows = new ArrayList<>();
+        List<Map<String, Object>> resolvedPayload = new ArrayList<>();
+        Set<String> fileKeys = new HashSet<>();
+        int create = 0;
+        int skip = 0;
+        int blocked = 0;
+
+        for (AgentImportPreflightReqVO.Row row : request.getRows()) {
+            List<AgentImportPreflightRespVO.Issue> blockers = new ArrayList<>();
+            List<AgentImportPreflightRespVO.Issue> warnings = new ArrayList<>();
+            String templateCode = value(row, "code");
+            String templateName = value(row, "name");
+            String indicatorName = value(row, "indicatorName");
+            if (templateCode.isEmpty()) blockers.add(issue("TEMPLATE_CODE_REQUIRED", "code", "鏂规缂栧彿涓嶈兘涓虹┖"));
+            if (templateName.isEmpty()) blockers.add(issue("TEMPLATE_NAME_REQUIRED", "name", "鏂规鍚嶇О涓嶈兘涓虹┖"));
+            if (indicatorName.isEmpty()) blockers.add(issue("INDICATOR_NAME_REQUIRED", "indicatorName", "妫�娴嬮」涓嶈兘涓虹┖"));
+
+            List<Integer> types = parseTypes(value(row, "types"), blockers);
+            Integer status = parseInteger(value(row, "status"), 0, "status", blockers);
+            Integer indicatorType = parseInteger(value(row, "indicatorType"), 5, "indicatorType", blockers);
+            Integer resultType = parseInteger(value(row, "resultType"), 1, "resultType", blockers);
+            BigDecimal thresholdMax = decimal(value(row, "thresholdMax"), "thresholdMax", blockers);
+            BigDecimal thresholdMin = decimal(value(row, "thresholdMin"), "thresholdMin", blockers);
+
+            MesQcIndicatorDO indicator = findIndicator(value(row, "indicatorCode"), indicatorName);
+            String indicatorCode = indicator == null ? proposedCode(indicatorName) : indicator.getCode();
+            String indicatorAction = indicator == null ? "CREATE_AFTER_REVIEW" : "REUSE";
+            if (indicator == null && !indicatorName.isEmpty()) {
+                warnings.add(issue("INDICATOR_WILL_CREATE", "indicatorName",
+                        "绯荤粺涓嶅瓨鍦ㄦ娴嬮」鈥�" + indicatorName + "鈥濓紱纭瀵煎叆鍚庡皢鍏堟柊澧炴娴嬮」 " + indicatorCode));
+            }
+            if (!value(row, "unitCode").isEmpty()) {
+                warnings.add(issue("UNIT_REQUIRES_REVIEW", "unitCode",
+                        "璁¢噺鍗曚綅鈥�" + value(row, "unitCode") + "鈥濇殏浠ュ師鏂囦繚鐣欙紝璇峰鏍哥郴缁熷崟浣嶅叧鑱�"));
+            }
+
+            MesQcTemplateDO template = templateCode.isEmpty() ? null : templateMapper.selectByCode(templateCode);
+            MesQcTemplateIndicatorDO relation = template == null || indicator == null ? null
+                    : templateIndicatorMapper.selectListByTemplateId(template.getId()).stream()
+                    .filter(item -> Objects.equals(item.getIndicatorId(), indicator.getId())).findFirst().orElse(null);
+            String fileKey = templateCode + "\u0000" + indicatorName;
+            String action;
+            Long targetId = null;
+            if (!blockers.isEmpty()) {
+                action = "BLOCKED";
+                blocked++;
+            } else if (!fileKeys.add(fileKey)) {
+                blockers.add(issue("DUPLICATE_IN_FILE", "indicatorName", "鏂囦欢鍐呮娴嬮」閲嶅锛岃淇濈暀涓�鏉�"));
+                action = "BLOCKED";
+                blocked++;
+            } else if (relation != null) {
+                action = "SKIP";
+                targetId = relation.getId();
+                skip++;
+                warnings.add(issue("TEMPLATE_INDICATOR_EXISTS", "indicatorName", "鏂规宸插叧鑱旇妫�娴嬮」锛屽凡璺宠繃"));
+            } else {
+                action = "CREATE";
+                create++;
+            }
+
+            Map<String, Object> resolved = new LinkedHashMap<>();
+            resolved.put("code", templateCode);
+            resolved.put("name", templateName);
+            resolved.put("types", types);
+            resolved.put("status", status);
+            resolved.put("indicatorCode", indicatorCode);
+            resolved.put("indicatorName", indicatorName);
+            resolved.put("indicatorType", indicatorType);
+            resolved.put("resultType", resultType);
+            resolved.put("indicatorAction", indicatorAction);
+            resolved.put("thresholdMax", thresholdMax);
+            resolved.put("thresholdMin", thresholdMin);
+            resolved.put("standardValue", value(row, "standardValue"));
+            resolved.put("unitCode", value(row, "unitCode"));
+            resolved.put("action", action);
+            resolvedPayload.add(resolved);
+
+            plannedRows.add(AgentImportPreflightRespVO.Row.builder()
+                    .candidateId(row.getCandidateId()).rowNo(row.getRowNo()).action(action)
+                    .resolved(resolved).blockers(blockers).warnings(warnings).targetId(targetId).build());
+        }
+        return AgentImportPreflightRespVO.builder()
+                .moduleId(MODULE_ID)
+                .preflightHash(SystemAgentGenericImportService.preflightHash(MODULE_ID, request, resolvedPayload))
+                .source(AgentImportPreflightRespVO.Source.builder()
+                        .attachmentId(request.getAttachmentId()).sessionId(request.getSessionId())
+                        .sourceSha256(request.getSourceSha256()).proposalRevision(request.getProposalRevision()).build())
+                .summary(AgentImportPreflightRespVO.Summary.builder()
+                        .total(plannedRows.size()).create(create).skip(skip).blocked(blocked).build())
+                .canCommit(blocked == 0)
+                .sideEffects(List.of(
+                        "鎸夋柟妗堢紪鍙峰垱寤鸿川妫�鏂规锛堟娴嬬绫讳娇鐢ㄧ郴缁� mes_qc_type锛�",
+                        "澶嶇敤鍚屽悕妫�娴嬮」锛涚己澶辨娴嬮」缁忔湰娆′汉宸ョ‘璁ゅ悗鏂板",
+                        "鍒涘缓鏂规涓庢娴嬮」鍏宠仈锛屼笉瑕嗙洊鐜版湁妫�娴嬮」涓绘暟鎹�"))
+                .rows(plannedRows).blockingItems(List.of()).build();
+    }
+
+    @Override
+    public AgentImportRowReceipt commitRow(AgentPrincipal principal, AgentImportCommitReqVO request,
+                                           AgentImportPreflightRespVO.Row planned,
+                                           AgentImportPreflightReqVO.Row row) {
+        MesQcTemplateDO template = templateMapper.selectByCode(value(row, "code"));
+        if (template == null) {
+            MesQcTemplateSaveReqVO create = new MesQcTemplateSaveReqVO();
+            create.setCode(value(row, "code"));
+            create.setName(value(row, "name"));
+            create.setTypes(parseTypes(value(row, "types"), new ArrayList<>()));
+            create.setStatus(parseInteger(value(row, "status"), 0, "status", new ArrayList<>()));
+            create.setRemark("鏉ユ簮锛氬伐涓氭櫤鑳戒綋鏂囨。琛ュ綍锛涘凡鐢辩敤鎴风‘璁�");
+            Long id = templateService.createTemplate(create);
+            template = templateService.getTemplate(id);
+        }
+
+        String indicatorName = value(row, "indicatorName");
+        MesQcIndicatorDO indicator = findIndicator(value(row, "indicatorCode"), indicatorName);
+        if (indicator == null) {
+            MesQcIndicatorSaveReqVO create = new MesQcIndicatorSaveReqVO();
+            create.setCode(proposedCode(indicatorName));
+            create.setName(indicatorName);
+            create.setType(parseInteger(value(row, "indicatorType"), 5, "indicatorType", new ArrayList<>()));
+            create.setItemType(1);
+            create.setParentId(0L);
+            create.setResultType(parseInteger(value(row, "resultType"), 1, "resultType", new ArrayList<>()));
+            create.setRemark("鏉ユ簮锛氬伐涓氭櫤鑳戒綋浠庡凡鎺堟潈 SOP 鎻愬彇锛涚粡鐢ㄦ埛瀹℃牳鏂板");
+            Long id = indicatorService.createIndicator(create);
+            indicator = indicatorService.getIndicator(id);
+        }
+
+        Long indicatorId = indicator.getId();
+        MesQcTemplateIndicatorDO existing = templateIndicatorMapper.selectListByTemplateId(template.getId()).stream()
+                .filter(item -> Objects.equals(item.getIndicatorId(), indicatorId)).findFirst().orElse(null);
+        if (existing != null) {
+            return AgentImportRowReceipt.created(existing.getId(), value(row, "code"), indicatorName);
+        }
+        MesQcTemplateIndicatorSaveReqVO create = new MesQcTemplateIndicatorSaveReqVO();
+        create.setTemplateId(template.getId());
+        create.setIndicatorId(indicatorId);
+        create.setThresholdMax(decimal(value(row, "thresholdMax"), "thresholdMax", new ArrayList<>()));
+        create.setThresholdMin(decimal(value(row, "thresholdMin"), "thresholdMin", new ArrayList<>()));
+        String standard = value(row, "standardValue");
+        String unit = value(row, "unitCode");
+        create.setRemark("鍘熸枃鏍囧噯锛�" + standard + (unit.isEmpty() ? "" : "锛涘崟浣嶏細" + unit));
+        Long relationId = templateIndicatorService.createTemplateIndicator(create);
+        return AgentImportRowReceipt.created(relationId, value(row, "code"), indicatorName);
+    }
+
+    private MesQcIndicatorDO findIndicator(String code, String name) {
+        if (!code.isEmpty()) {
+            MesQcIndicatorDO found = indicatorMapper.selectByCode(code);
+            if (found != null) return found;
+        }
+        if (name.isEmpty()) return null;
+        return indicatorService.getIndicatorList().stream()
+                .filter(item -> name.equalsIgnoreCase(item.getName().trim())).findFirst().orElse(null);
+    }
+
+    private static String proposedCode(String name) {
+        return "AI-QI-" + DigestUtil.sha256Hex(name == null ? "" : name.trim()).substring(0, 12).toUpperCase(Locale.ROOT);
+    }
+
+    private static List<Integer> parseTypes(String raw, List<AgentImportPreflightRespVO.Issue> blockers) {
+        List<Integer> values = new ArrayList<>();
+        for (String part : raw.replace("[", "").replace("]", "").split("[,锛宂")) {
+            if (part.isBlank()) continue;
+            try {
+                int value = Integer.parseInt(part.trim());
+                if (value < 1 || value > 4) throw new NumberFormatException();
+                values.add(value);
+            } catch (NumberFormatException ex) {
+                blockers.add(issue("INVALID_QC_TYPE", "types", "妫�娴嬬绫诲繀椤讳娇鐢ㄧ郴缁熷瓧鍏稿�� 1/2/3/4"));
+            }
+        }
+        if (values.isEmpty()) blockers.add(issue("QC_TYPE_REQUIRED", "types", "妫�娴嬬绫讳笉鑳戒负绌�"));
+        return values;
+    }
+
+    private static Integer parseInteger(String raw, int defaultValue, String field,
+                                        List<AgentImportPreflightRespVO.Issue> blockers) {
+        if (raw.isEmpty()) return defaultValue;
+        try {
+            return Integer.valueOf(raw);
+        } catch (NumberFormatException ex) {
+            blockers.add(issue("INVALID_INTEGER", field, field + " 蹇呴』鏄暣鏁�"));
+            return defaultValue;
+        }
+    }
+
+    private static BigDecimal decimal(String raw, String field,
+                                      List<AgentImportPreflightRespVO.Issue> blockers) {
+        if (raw.isEmpty()) return null;
+        try {
+            return new BigDecimal(raw);
+        } catch (NumberFormatException ex) {
+            blockers.add(issue("INVALID_DECIMAL", field, field + " 蹇呴』鏄暟瀛�"));
+            return null;
+        }
+    }
+
+    private static String value(AgentImportPreflightReqVO.Row row, String key) {
+        String raw = row.getValues() == null ? null : row.getValues().get(key);
+        return raw == null ? "" : raw.trim();
+    }
+
+    private static AgentImportPreflightRespVO.Issue issue(String code, String field, String message) {
+        return AgentImportPreflightRespVO.Issue.builder()
+                .code(code).field(field).message(message).retryable(false).build();
+    }
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/agent/imports/AgentImportModuleAdapter.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/agent/imports/AgentImportModuleAdapter.java
index d1a67be..dd845ab 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/agent/imports/AgentImportModuleAdapter.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/agent/imports/AgentImportModuleAdapter.java
@@ -2,6 +2,8 @@
 
 import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal;
 
+import java.util.List;
+
 /**
  * 涓氬姟妯″潡瀵煎叆閫傞厤鍣細system 妯″潡璐熻矗骞傜瓑銆侀�愯浜嬪姟涓庡洖鎵э紝閫傞厤鍣ㄥ彧璐熻矗鍗曡鏍¢獙鍜屽啓鍏ャ��
  */
@@ -13,6 +15,11 @@
     /** 鎻愪氦璇ユā鍧楁墍闇�鐨勪笟鍔℃潈闄愩�� */
     String permission();
 
+    /** 妯″潡鍐欏叆鍙兘鍚屾椂鍒涘缓渚濊禆涓绘暟鎹紱鎻愪氦鍓嶅繀椤诲悓鏃跺叿澶囪繖浜涙寜閽潈闄愩�� */
+    default List<String> permissions() {
+        return List.of(permission());
+    }
+
     /** 鍙棰勬锛涘繀椤昏繑鍥炵ǔ瀹氱殑 preflightHash 渚涙彁浜ゆ椂姣斿銆� */
     AgentImportPreflightRespVO preflight(AgentPrincipal principal, AgentImportPreflightReqVO request);
 
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentCapabilityController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentCapabilityController.java
new file mode 100644
index 0000000..e5936d7
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentCapabilityController.java
@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.system.controller.admin.agent;
+
+import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
+import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal;
+import cn.iocoder.yudao.module.system.framework.agent.SystemAgentBridgeGuard;
+import cn.iocoder.yudao.module.system.service.permission.PermissionService;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/** 涓烘櫤鑳戒綋瑙勫垝杩斿洖缁忚繃鏈嶅姟绔鍓殑鑿滃崟銆佹寜閽�佸瓧娈靛拰鏁版嵁鑼冨洿鑳藉姏銆� */
+@RestController
+@RequestMapping("/agent/bridge")
+public class SystemAgentCapabilityController {
+
+    private static final List<CapabilityDefinition> CATALOG = List.of(
+            new CapabilityDefinition("mdm.item.import", "mdm.item", "import", "mdm:item:import",
+                    Set.of("code", "name", "specification", "categoryCode", "unitMeasureCode")),
+            new CapabilityDefinition("srm.supplier.create", "srm.supplier", "create", "srm:supplier:create",
+                    Set.of("code", "name", "shortName", "type", "companyNature", "registeredCapital",
+                            "businessLicenseNo", "taxNo", "legalPerson", "address", "website", "contactName",
+                            "contactMobile", "contactEmail", "bankName", "bankAccount", "status", "remark")),
+            new CapabilityDefinition("mes.bom.create", "mes.bom", "create", "mes:md-item:create",
+                    Set.of("parentCode", "componentCode", "quantity", "status", "remark")),
+            new CapabilityDefinition("mes.qc-template.prepare", "mes.qc-template", "prepare",
+                    "mes:qc-template:create", Set.of("code", "name", "types", "indicatorCode",
+                    "standardValue", "thresholdMax", "thresholdMin", "unitCode", "itemCode", "remark")),
+            new CapabilityDefinition("mes.mps.prepare", "mes.mps", "prepare", "mes:pro-mps:create",
+                    Set.of("productCode", "quantity", "unitCode", "requestDate", "saleOrderNo", "remark")),
+            new CapabilityDefinition("mes.route.prepare", "mes.route", "prepare", "mes:pro-route:create",
+                    Set.of("routeCode", "routeName", "itemCode", "processCode", "sort", "yieldRate",
+                            "keyFlag", "remark"))
+    );
+
+    @Resource
+    private SystemAgentBridgeGuard bridgeGuard;
+    @Resource
+    private SecurityFrameworkService securityFrameworkService;
+    @Resource
+    private PermissionService permissionService;
+
+    @GetMapping("/capabilities")
+    public CommonResult<CapabilityCatalogResp> capabilities(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken) {
+        AgentPrincipal principal = bridgeGuard.requireAuthenticated(serviceToken);
+        DeptDataPermissionRespDTO dataScope = permissionService.getDeptDataPermission(principal.userId());
+        List<CapabilityResp> capabilities = new ArrayList<>();
+        for (CapabilityDefinition definition : CATALOG) {
+            if (securityFrameworkService.hasPermission(definition.permission())) {
+                capabilities.add(new CapabilityResp(definition.tool(), definition.module(), definition.operation(),
+                        definition.fields().stream().sorted().toList(), scopeSummary(dataScope), true));
+            }
+        }
+        String policyRevision = digest(policyMaterial(principal.userId(), capabilities, dataScope));
+        String authorizationScope = "scope:" + digest(principal.userId() + ":" + policyRevision).substring(0, 32);
+        return success(new CapabilityCatalogResp(policyRevision, authorizationScope,
+                scopeSummary(dataScope), capabilities));
+    }
+
+    private static String scopeSummary(DeptDataPermissionRespDTO scope) {
+        if (Boolean.TRUE.equals(scope.getAll())) {
+            return "鍏ㄩ儴鎺堟潈鏁版嵁";
+        }
+        int departments = scope.getDeptIds() == null ? 0 : scope.getDeptIds().size();
+        if (Boolean.TRUE.equals(scope.getSelf()) && departments > 0) {
+            return "鏈汉鍙婃巿鏉冮儴闂ㄦ暟鎹�";
+        }
+        if (Boolean.TRUE.equals(scope.getSelf())) {
+            return "浠呮湰浜烘暟鎹�";
+        }
+        return departments > 0 ? "鎺堟潈閮ㄩ棬鏁版嵁" : "鏃犻儴闂ㄦ暟鎹寖鍥�";
+    }
+
+    private static String policyMaterial(Long userId, List<CapabilityResp> capabilities,
+                                         DeptDataPermissionRespDTO scope) {
+        List<Long> deptIds = scope.getDeptIds() == null ? List.of()
+                : scope.getDeptIds().stream().sorted(Comparator.naturalOrder()).toList();
+        return userId + "|" + Boolean.TRUE.equals(scope.getAll()) + "|" + Boolean.TRUE.equals(scope.getSelf())
+                + "|" + deptIds + "|" + capabilities.stream().map(CapabilityResp::tool).sorted().toList();
+    }
+
+    private static String digest(String value) {
+        try {
+            return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
+                    .digest(value.getBytes(StandardCharsets.UTF_8)));
+        } catch (NoSuchAlgorithmException ex) {
+            throw new IllegalStateException("JVM 缂哄皯 SHA-256", ex);
+        }
+    }
+
+    private record CapabilityDefinition(String tool, String module, String operation, String permission,
+                                        Set<String> fields) {
+    }
+
+    public record CapabilityResp(String tool, String module, String operation, List<String> allowedFields,
+                                 String scopeSummary, boolean canExecute) {
+    }
+
+    public record CapabilityCatalogResp(String policyRevision, String authorizationScope, String scopeSummary,
+                                        List<CapabilityResp> capabilities) {
+    }
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentDelegationController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentDelegationController.java
new file mode 100644
index 0000000..ebdc505
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentDelegationController.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.system.controller.admin.agent;
+
+import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException;
+import cn.iocoder.yudao.module.system.framework.agent.AgentDelegationToken;
+import cn.iocoder.yudao.module.system.framework.agent.SystemAgentBridgeGuard;
+import cn.iocoder.yudao.module.system.framework.agent.SystemAgentDelegationService;
+import cn.iocoder.yudao.module.system.service.storage.SystemAgentImportBridgeService;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/** 娴忚鍣ㄤ互褰撳墠鐧诲綍韬唤涓哄紓姝� Worker 绛惧彂鐭湡銆佸彲鎾ら攢銆侀檺瀹氳寖鍥寸殑濮旀墭绁ㄦ嵁銆� */
+@RestController
+@RequestMapping("/agent/bridge/delegations")
+public class SystemAgentDelegationController {
+
+    private static final String IMPORT_PERMISSIONS_EXPR =
+            "@ss.hasAnyPermissions('mdm:item:import','srm:supplier:create','mes:md-item:create',"
+                    + "'mes:qc-template:create','mes:pro-mps:create','mes:pro-route:create')";
+    private static final Set<String> ALLOWED_OPERATIONS = Set.of(
+            "material_import.prepare", "material_import.read", "material_import.review",
+            "material_import.preflight", "material_import.commit", "material_import.read_receipt",
+            "conversation.create", "conversation.read", "conversation.message", "conversation.action",
+            "conversation.report", "attachment.read", "import.modules.read", "import.preflight",
+            "import.commit", "import.receipt.read");
+    private static final Map<String, String> MODULE_PERMISSIONS = Map.of(
+            "mdm.item", SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT,
+            "srm.supplier", "srm:supplier:create",
+            "mes.bom", "mes:md-item:create",
+            "mes.qc-template", "mes:qc-template:create",
+            "mes.mps", "mes:pro-mps:create",
+            "mes.route", "mes:pro-route:create");
+
+    @Resource
+    private SystemAgentBridgeGuard bridgeGuard;
+    @Resource
+    private SystemAgentDelegationService delegationService;
+    @Resource
+    private SystemAgentImportBridgeService bridgeService;
+    @Resource
+    private PermissionCommonApi permissionApi;
+
+    @PostMapping
+    @PreAuthorize(IMPORT_PERMISSIONS_EXPR)
+    public CommonResult<AgentDelegationToken> issue(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @Valid @RequestBody IssueRequest request) {
+        bridgeGuard.verifyServiceToken(serviceToken);
+        if (!ALLOWED_OPERATIONS.containsAll(request.getOperations())) {
+            throw new AgentBridgeException(HttpStatus.BAD_REQUEST, "DELEGATION_OPERATION_INVALID",
+                    "濮旀墭鍖呭惈涓嶆敮鎸佺殑鎿嶄綔");
+        }
+        Set<Long> attachmentIds = request.normalizedAttachmentIds();
+        for (Long attachmentId : attachmentIds) {
+            bridgeService.requireOwnedAttachmentRef(attachmentId);
+        }
+        String permission = resolvePermission(request.getModuleId());
+        AgentDelegationToken token = delegationService.issue(request.getTaskId(), request.getModuleId(),
+                attachmentIds, request.getOperations(), permission);
+        return success(token);
+    }
+
+    @DeleteMapping("/{delegationId}")
+    @PreAuthorize(IMPORT_PERMISSIONS_EXPR)
+    public CommonResult<Boolean> revoke(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @PathVariable String delegationId) {
+        bridgeGuard.verifyServiceToken(serviceToken);
+        delegationService.revoke(delegationId, SecurityFrameworkUtils.getLoginUserId());
+        return success(true);
+    }
+
+    private String resolvePermission(String moduleId) {
+        if (moduleId != null && !moduleId.isBlank()) {
+            String permission = MODULE_PERMISSIONS.get(moduleId);
+            if (permission == null) {
+                throw new AgentBridgeException(HttpStatus.BAD_REQUEST, "MODULE_NOT_SUPPORTED", "涓嶆敮鎸佺殑瀵煎叆妯″潡");
+            }
+            return permission;
+        }
+        Long userId = SecurityFrameworkUtils.getLoginUserId();
+        return SystemAgentBridgeGuard.IMPORT_PERMISSIONS.stream()
+                .filter(permission -> permissionApi.hasAnyPermissions(userId, permission))
+                .findFirst()
+                .orElseThrow(() -> new AgentBridgeException(HttpStatus.FORBIDDEN, "FORBIDDEN",
+                        "褰撳墠鐢ㄦ埛缂哄皯鏁版嵁瀵煎叆鏉冮檺"));
+    }
+
+    @Data
+    public static class IssueRequest {
+        @NotBlank
+        private String taskId;
+        private String moduleId;
+        private Long attachmentId;
+        @Size(max = 20)
+        private Set<Long> attachmentIds;
+        @NotEmpty
+        private Set<String> operations;
+
+        public Set<Long> normalizedAttachmentIds() {
+            Set<Long> values = new LinkedHashSet<>();
+            if (attachmentIds != null) {
+                values.addAll(attachmentIds);
+            }
+            if (attachmentId != null) {
+                values.add(attachmentId);
+            }
+            if (values.stream().anyMatch(id -> id == null || id <= 0) || values.size() > 20) {
+                throw new AgentBridgeException(HttpStatus.BAD_REQUEST, "DELEGATION_SCOPE_INVALID",
+                        "濮旀墭闄勪欢鑼冨洿鏃犳晥");
+            }
+            return values;
+        }
+    }
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentGenericImportBridgeController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentGenericImportBridgeController.java
index eab9aba..0305fbe 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentGenericImportBridgeController.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentGenericImportBridgeController.java
@@ -32,35 +32,46 @@
 
     @GetMapping
     public CommonResult<List<Map<String, String>>> modules(
-            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken) {
-        bridgeGuard.requireAny(serviceToken);
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId) {
+        bridgeGuard.requireAny(serviceToken, delegationToken, taskId, "import.modules.read", null, null);
         return success(genericImportService.modules());
     }
 
     @PostMapping("/{moduleId}/preflight")
     public CommonResult<AgentImportPreflightRespVO> preflight(
             @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId,
             @PathVariable String moduleId,
             @Valid @RequestBody AgentImportPreflightReqVO request) {
-        AgentPrincipal principal = bridgeGuard.requireAny(serviceToken);
+        AgentPrincipal principal = bridgeGuard.requireAny(serviceToken, delegationToken, taskId,
+                "import.preflight", moduleId, null);
         return success(genericImportService.preflight(moduleId, request, principal));
     }
 
     @PostMapping("/{moduleId}/commit")
     public CommonResult<AgentImportReceiptRespVO> commit(
             @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId,
             @PathVariable String moduleId,
             @Valid @RequestBody AgentImportCommitReqVO request) {
-        AgentPrincipal principal = bridgeGuard.requireAny(serviceToken);
+        AgentPrincipal principal = bridgeGuard.requireAny(serviceToken, delegationToken, taskId,
+                "import.commit", moduleId, null);
         return success(genericImportService.commit(moduleId, request, principal));
     }
 
     @GetMapping("/{moduleId}/commands/{commandId}")
     public CommonResult<AgentImportReceiptRespVO> getCommand(
             @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId,
             @PathVariable String moduleId,
             @PathVariable String commandId) {
-        AgentPrincipal principal = bridgeGuard.requireAny(serviceToken);
+        AgentPrincipal principal = bridgeGuard.requireAny(serviceToken, delegationToken, taskId,
+                "import.receipt.read", moduleId, null);
         return success(genericImportService.getCommand(moduleId, commandId, principal));
     }
 }
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java
index 0f8c195..f92a84e 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java
@@ -65,10 +65,22 @@
         return success(bridgeService.bindAttachment(sessionId, request.getBlobId()));
     }
 
+    @DeleteMapping("/attachments/{attachmentId}")
+    public CommonResult<Boolean> deleteAttachment(
+            @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId,
+            @PathVariable Long attachmentId) {
+        bridgeGuard.requireAny(serviceToken, delegationToken, taskId, "attachment.delete", null, attachmentId);
+        bridgeService.deleteImportAttachment(attachmentId);
+        return success(true);
+    }
+
     @PostMapping("/authorize")
-    @PreAuthorize(IMPORT_PERMISSIONS_EXPR)
     public CommonResult<Map<String, String>> authorize(
             @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId,
             @Valid @RequestBody AuthorizeRequest request) {
         if (!ACTIONS.contains(request.getAction())) {
             throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ACTION_FORBIDDEN", "涓嶆敮鎸佺殑鏅鸿兘浣撴搷浣�");
@@ -77,26 +89,30 @@
         if (request.getAction().startsWith("conversation.")) {
             String permission = MODULE_PERMISSIONS.get(request.getResource());
             principal = permission == null
-                    ? bridgeGuard.requireAny(serviceToken)
-                    : bridgeGuard.require(serviceToken, permission);
+                    ? bridgeGuard.requireAny(serviceToken, delegationToken, taskId, request.getAction(),
+                    request.getResource(), null)
+                    : bridgeGuard.require(serviceToken, delegationToken, taskId, request.getAction(), permission,
+                    request.getResource(), null);
         } else {
-            principal = bridgeGuard.require(serviceToken, SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT);
+            principal = bridgeGuard.require(serviceToken, delegationToken, taskId, request.getAction(),
+                    SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT, "mdm.item",
+                    requiresAttachmentOwnership(request.getAction()) ? parseId(request.getResource()) : null);
         }
         if (requiresAttachmentOwnership(request.getAction())) {
             bridgeService.requireOwnedAttachmentRef(parseId(request.getResource()));
         }
         // read/review/read_receipt 鐨勪换鍔� UUID 鎵�鏈夋潈鐢� Python checkpoint 浣滅敤鍩熸牳楠岋紱鏈閲嶆煡褰撳墠鎸夐挳鏉冮檺銆�
         return success(Map.of("user_id", String.valueOf(principal.userId()),
-                "tenant_id", String.valueOf(principal.tenantId()),
                 "policy_revision", principal.policyRevision()));
     }
 
     @GetMapping("/attachments/{attachmentId}")
-    @PreAuthorize(IMPORT_PERMISSIONS_EXPR)
     public ResponseEntity<FileSystemResource> downloadAttachment(
             @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
+            @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken,
+            @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId,
             @PathVariable Long attachmentId) {
-        bridgeGuard.requireAny(serviceToken);
+        bridgeGuard.requireAny(serviceToken, delegationToken, taskId, "attachment.read", null, attachmentId);
         SystemAgentAttachmentRef ref = bridgeService.requireOwnedAttachmentRef(attachmentId);
         return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM)
                 .contentLength(ref.byteSize())
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/agent/AgentImportCommandDO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/agent/AgentImportCommandDO.java
index 7cd61a9..f3e1da4 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/agent/AgentImportCommandDO.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/agent/AgentImportCommandDO.java
@@ -8,7 +8,7 @@
 import java.time.LocalDateTime;
 
 /**
- * 閫氱敤涓氬姟瀵煎叆鍛戒护銆傚悓涓� (tenant, owner, module, commandId/idempotencyKey) 鍙厑璁镐竴鏉°��
+ * 閫氱敤涓氬姟瀵煎叆鍛戒护銆傚悓涓� (owner, module, commandId/idempotencyKey) 鍙厑璁镐竴鏉°��
  */
 @TableName("agent_import_command")
 @Data
@@ -26,7 +26,6 @@
     private String idempotencyKey;
     private String requestDigest;
     private String moduleId;
-    private Long tenantId;
     private Long ownerUserId;
     private Long sessionId;
     private Long attachmentId;
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/agent/AgentImportCommandMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/agent/AgentImportCommandMapper.java
index 1308c76..c90cc64 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/agent/AgentImportCommandMapper.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/agent/AgentImportCommandMapper.java
@@ -8,19 +8,15 @@
 @Mapper
 public interface AgentImportCommandMapper extends BaseMapperX<AgentImportCommandDO> {
 
-    default AgentImportCommandDO selectByCommandId(Long tenantId, Long ownerUserId,
-                                                   String moduleId, String commandId) {
+    default AgentImportCommandDO selectByCommandId(Long ownerUserId, String moduleId, String commandId) {
         return selectOne(new LambdaQueryWrapperX<AgentImportCommandDO>()
-                .eq(AgentImportCommandDO::getTenantId, tenantId)
                 .eq(AgentImportCommandDO::getOwnerUserId, ownerUserId)
                 .eq(AgentImportCommandDO::getModuleId, moduleId)
                 .eq(AgentImportCommandDO::getCommandId, commandId));
     }
 
-    default AgentImportCommandDO selectByIdempotencyKey(Long tenantId, Long ownerUserId,
-                                                        String moduleId, String idempotencyKey) {
+    default AgentImportCommandDO selectByIdempotencyKey(Long ownerUserId, String moduleId, String idempotencyKey) {
         return selectOne(new LambdaQueryWrapperX<AgentImportCommandDO>()
-                .eq(AgentImportCommandDO::getTenantId, tenantId)
                 .eq(AgentImportCommandDO::getOwnerUserId, ownerUserId)
                 .eq(AgentImportCommandDO::getModuleId, moduleId)
                 .eq(AgentImportCommandDO::getIdempotencyKey, idempotencyKey));
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegation.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegation.java
new file mode 100644
index 0000000..beb4af6
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegation.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.system.framework.agent;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+/** Redis 涓繚瀛樼殑鏅鸿兘浣撶煭鏈熷鎵橈紱涓嶅寘鍚敤鎴风櫥褰� Token銆� */
+@Data
+public class AgentDelegation {
+
+    private String id;
+    private String secretHash;
+    private String taskId;
+    private Long userId;
+    private String moduleId;
+    private Long attachmentId;
+    /**
+     * 寮傛鎵归噺鏂囨。浠诲姟鍏佽璇诲彇鐨勯檮浠堕泦鍚堛�俛ttachmentId 淇濈暀鐢ㄤ簬鍏煎宸茬鍙戠殑鍗曢檮浠剁エ鎹��
+     */
+    private Set<Long> attachmentIds;
+    private Set<String> operations;
+    private String permission;
+    private LocalDateTime issuedAt;
+    private LocalDateTime expiresAt;
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationStore.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationStore.java
new file mode 100644
index 0000000..b9bca1e
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationStore.java
@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.system.framework.agent;
+
+/** 濮旀墭绁ㄦ嵁瀛樺偍杈圭晫銆� */
+public interface AgentDelegationStore {
+
+    void save(AgentDelegation delegation, long ttlSeconds);
+
+    AgentDelegation get(String delegationId);
+
+    void delete(String delegationId);
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationToken.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationToken.java
new file mode 100644
index 0000000..5f6a1da
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentDelegationToken.java
@@ -0,0 +1,7 @@
+package cn.iocoder.yudao.module.system.framework.agent;
+
+import java.time.LocalDateTime;
+
+/** 鍙湪绛惧彂鏃惰繑鍥炰竴娆$殑濮旀墭鍑嵁銆� */
+public record AgentDelegationToken(String delegationId, String token, LocalDateTime expiresAt) {
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentPrincipal.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentPrincipal.java
index 5b0edc4..ddf20dd 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentPrincipal.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/AgentPrincipal.java
@@ -1,5 +1,5 @@
 package cn.iocoder.yudao.module.system.framework.agent;
 
 /** 鏅鸿兘浣撹皟鐢ㄧ粡 Java 鐜版煡鍚庣殑韬唤涓婁笅鏂囷紝涓嶆潵鑷鎴风鑷姤銆� */
-public record AgentPrincipal(Long userId, Long tenantId, String policyRevision) {
+public record AgentPrincipal(Long userId, String policyRevision) {
 }
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/RedisAgentDelegationStore.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/RedisAgentDelegationStore.java
new file mode 100644
index 0000000..de9b2f8
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/RedisAgentDelegationStore.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.system.framework.agent;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/** Redis 濮旀墭瀛樺偍锛涘彧淇濆瓨瀵嗛挜鎽樿锛屽師濮嬬エ鎹笉浼氳惤搴撱�� */
+@Component
+@RequiredArgsConstructor
+public class RedisAgentDelegationStore implements AgentDelegationStore {
+
+    private static final String REDIS_KEY_PREFIX = "agent:bridge:delegation:";
+    private final StringRedisTemplate stringRedisTemplate;
+
+    @Override
+    public void save(AgentDelegation delegation, long ttlSeconds) {
+        stringRedisTemplate.opsForValue().set(key(delegation.getId()), JsonUtils.toJsonString(delegation),
+                ttlSeconds, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public AgentDelegation get(String delegationId) {
+        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(key(delegationId)),
+                AgentDelegation.class);
+    }
+
+    @Override
+    public void delete(String delegationId) {
+        stringRedisTemplate.delete(key(delegationId));
+    }
+
+    private static String key(String delegationId) {
+        return REDIS_KEY_PREFIX + delegationId;
+    }
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentBridgeGuard.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentBridgeGuard.java
index c5514ee..b50e361 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentBridgeGuard.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentBridgeGuard.java
@@ -2,7 +2,6 @@
 
 import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import jakarta.annotation.Resource;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
@@ -32,6 +31,8 @@
 
     @Resource
     private SecurityFrameworkService securityFrameworkService;
+    @Resource
+    private SystemAgentDelegationService delegationService;
 
     public void verifyServiceToken(String provided) {
         if (bridgeServiceToken == null || bridgeServiceToken.isBlank() || provided == null
@@ -40,6 +41,16 @@
             throw new AgentBridgeException(HttpStatus.FORBIDDEN, "SERVICE_TOKEN_INVALID",
                     "鏅鸿兘浣撴湇鍔¤韩浠芥棤鏁堬紝璇锋鏌ユ湇鍔″瘑閽ラ厤缃�");
         }
+    }
+
+    /** 鍙‘璁ゆ湇鍔¤韩浠戒笌褰撳墠鐧诲綍鐢ㄦ埛锛涘叿浣撹兘鍔涚敱璋冪敤鏂归�愰」鏀剁揣銆� */
+    public AgentPrincipal requireAuthenticated(String providedServiceToken) {
+        verifyServiceToken(providedServiceToken);
+        Long userId = SecurityFrameworkUtils.getLoginUserId();
+        if (userId == null) {
+            throw new AgentBridgeException(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED", "鏈櫥褰曟垨鐧诲綍宸茶繃鏈�");
+        }
+        return principalOf(userId);
     }
 
     public AgentPrincipal require(String providedServiceToken, String permission) {
@@ -55,6 +66,15 @@
         return principalOf(userId);
     }
 
+    public AgentPrincipal require(String providedServiceToken, String delegationToken, String taskId,
+                                  String operation, String permission, String moduleId, Long attachmentId) {
+        verifyServiceToken(providedServiceToken);
+        if (delegationToken != null && !delegationToken.isBlank()) {
+            return delegationService.require(delegationToken, taskId, operation, permission, moduleId, attachmentId);
+        }
+        return require(providedServiceToken, permission);
+    }
+
     /** 浼氳瘽涓庨檮浠跺叆鍙f棤娉曢鐭ユā鍧楋紝鍙姹傚叿澶囦换鎰忎竴椤瑰鍏ユ潈闄愩�� */
     public AgentPrincipal requireAny(String providedServiceToken) {
         verifyServiceToken(providedServiceToken);
@@ -68,8 +88,29 @@
         return principalOf(userId);
     }
 
+
+    public AgentPrincipal requireAny(String providedServiceToken, String delegationToken, String taskId,
+                                     String operation, String moduleId, Long attachmentId) {
+        if (delegationToken == null || delegationToken.isBlank()) {
+            return requireAny(providedServiceToken);
+        }
+        verifyServiceToken(providedServiceToken);
+        AgentBridgeException last = null;
+        for (String permission : IMPORT_PERMISSIONS) {
+            try {
+                return delegationService.require(delegationToken, taskId, operation, permission, moduleId, attachmentId);
+            } catch (AgentBridgeException ex) {
+                last = ex;
+                if (!"DELEGATION_SCOPE_MISMATCH".equals(ex.getErrorCode())
+                        && !"DELEGATION_PERMISSION_REVOKED".equals(ex.getErrorCode())) {
+                    throw ex;
+                }
+            }
+        }
+        throw last == null ? new AgentBridgeException(HttpStatus.FORBIDDEN, "FORBIDDEN", "濮旀墭绁ㄦ嵁鏉冮檺涓嶈冻") : last;
+    }
+
     private AgentPrincipal principalOf(Long userId) {
-        Long tenantId = TenantContextHolder.getTenantId();
-        return new AgentPrincipal(userId, tenantId == null ? 0L : tenantId, "live");
+        return new AgentPrincipal(userId, "live");
     }
 }
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationService.java
new file mode 100644
index 0000000..dd390af
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationService.java
@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.system.framework.agent;
+
+import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.Set;
+import java.util.UUID;
+
+/** 绛惧彂銆佹牎楠屽拰鎾ら攢鏅鸿兘浣撶煭鏈熷鎵樸�俁edis 鍙繚瀛樺瘑閽ユ憳瑕併�� */
+@Service
+@RequiredArgsConstructor
+public class SystemAgentDelegationService {
+
+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+    @Value("${yudao.agent.bridge.delegation-ttl-seconds:1800}")
+    private long delegationTtlSeconds;
+
+    private final AgentDelegationStore delegationStore;
+    private final PermissionCommonApi permissionApi;
+
+    public AgentDelegationToken issue(String taskId, String moduleId, Long attachmentId,
+                                      Set<String> operations, String permission) {
+        return issue(taskId, moduleId, attachmentId == null ? Set.of() : Set.of(attachmentId),
+                operations, permission);
+    }
+
+    public AgentDelegationToken issue(String taskId, String moduleId, Set<Long> attachmentIds,
+                                      Set<String> operations, String permission) {
+        Long userId = SecurityFrameworkUtils.getLoginUserId();
+        if (userId == null) {
+            throw new AgentBridgeException(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED", "鏈櫥褰曟垨鐧诲綍宸茶繃鏈�");
+        }
+        if (!permissionApi.hasAnyPermissions(userId, permission)) {
+            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "FORBIDDEN", "褰撳墠鐢ㄦ埛缂哄皯濮旀墭鎵�闇�鏉冮檺");
+        }
+        if (taskId == null || taskId.isBlank() || taskId.length() > 128 || operations == null || operations.isEmpty()
+                || attachmentIds == null || attachmentIds.size() > 20 || attachmentIds.stream().anyMatch(id -> id == null || id <= 0)) {
+            throw new AgentBridgeException(HttpStatus.BAD_REQUEST, "DELEGATION_SCOPE_INVALID", "濮旀墭浠诲姟鎴栨搷浣滆寖鍥存棤鏁�");
+        }
+
+        String id = UUID.randomUUID().toString();
+        byte[] secretBytes = new byte[32];
+        SECURE_RANDOM.nextBytes(secretBytes);
+        String secret = Base64.getUrlEncoder().withoutPadding().encodeToString(secretBytes);
+        String token = id + "." + secret;
+        long ttlSeconds = Math.max(60, Math.min(delegationTtlSeconds, 3600));
+        LocalDateTime now = LocalDateTime.now();
+
+        AgentDelegation delegation = new AgentDelegation();
+        delegation.setId(id);
+        delegation.setSecretHash(sha256(secret));
+        delegation.setTaskId(taskId);
+        delegation.setUserId(userId);
+        delegation.setModuleId(moduleId);
+        delegation.setAttachmentId(attachmentIds.size() == 1 ? attachmentIds.iterator().next() : null);
+        delegation.setAttachmentIds(Set.copyOf(attachmentIds));
+        delegation.setOperations(Set.copyOf(operations));
+        delegation.setPermission(permission);
+        delegation.setIssuedAt(now);
+        delegation.setExpiresAt(now.plusSeconds(ttlSeconds));
+        delegationStore.save(delegation, ttlSeconds);
+        return new AgentDelegationToken(id, token, delegation.getExpiresAt());
+    }
+
+    public AgentPrincipal require(String token, String taskId, String operation, String permission,
+                                  String moduleId, Long attachmentId) {
+        String[] tokenParts = token == null ? new String[0] : token.split("\\.", 2);
+        if (tokenParts.length != 2) {
+            throw invalidToken();
+        }
+        AgentDelegation delegation = delegationStore.get(tokenParts[0]);
+        if (delegation == null || delegation.getExpiresAt() == null
+                || !delegation.getExpiresAt().isAfter(LocalDateTime.now())
+                || !constantTimeEquals(delegation.getSecretHash(), sha256(tokenParts[1]))) {
+            throw invalidToken();
+        }
+        if (!delegation.getTaskId().equals(taskId) || !delegation.getOperations().contains(operation)
+                || !delegation.getPermission().equals(permission)
+                || (delegation.getModuleId() != null && !delegation.getModuleId().equals(moduleId))
+                || !allowsAttachment(delegation, attachmentId)) {
+            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "DELEGATION_SCOPE_MISMATCH", "濮旀墭绁ㄦ嵁涓嶅厑璁稿綋鍓嶆搷浣�");
+        }
+        if (!permissionApi.hasAnyPermissions(delegation.getUserId(), permission)) {
+            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "DELEGATION_PERMISSION_REVOKED", "濮旀墭鐢ㄦ埛鏉冮檺宸插彉鏇�");
+        }
+        LoginUser loginUser = new LoginUser().setId(delegation.getUserId())
+                .setUserType(UserTypeEnum.ADMIN.getValue())
+                .setExpiresTime(delegation.getExpiresAt());
+        SecurityFrameworkUtils.setLoginUser(loginUser, ServletUtils.getRequest());
+        return new AgentPrincipal(delegation.getUserId(), "delegated:" + delegation.getId());
+    }
+
+    private static boolean allowsAttachment(AgentDelegation delegation, Long attachmentId) {
+        if (delegation.getAttachmentIds() != null && !delegation.getAttachmentIds().isEmpty()) {
+            return attachmentId != null && delegation.getAttachmentIds().contains(attachmentId);
+        }
+        return delegation.getAttachmentId() == null || delegation.getAttachmentId().equals(attachmentId);
+    }
+
+    public void revoke(String delegationId, Long currentUserId) {
+        AgentDelegation delegation = delegationStore.get(delegationId);
+        if (delegation == null) {
+            return;
+        }
+        if (!delegation.getUserId().equals(currentUserId)) {
+            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "DELEGATION_FORBIDDEN", "鏃犳潈鎾ら攢璇ュ鎵樼エ鎹�");
+        }
+        delegationStore.delete(delegationId);
+    }
+
+    private static AgentBridgeException invalidToken() {
+        return new AgentBridgeException(HttpStatus.UNAUTHORIZED, "DELEGATION_INVALID", "濮旀墭绁ㄦ嵁鏃犳晥銆佸凡鎾ら攢鎴栧凡杩囨湡");
+    }
+
+    private static boolean constantTimeEquals(String left, String right) {
+        return left != null && right != null && MessageDigest.isEqual(left.getBytes(StandardCharsets.US_ASCII),
+                right.getBytes(StandardCharsets.US_ASCII));
+    }
+
+    private static String sha256(String value) {
+        try {
+            return java.util.HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
+                    .digest(value.getBytes(StandardCharsets.UTF_8)));
+        } catch (NoSuchAlgorithmException ex) {
+            throw new IllegalStateException("SHA-256 unavailable", ex);
+        }
+    }
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/agent/imports/SystemAgentGenericImportService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/agent/imports/SystemAgentGenericImportService.java
index 3a924ab..31c73b1 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/agent/imports/SystemAgentGenericImportService.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/agent/imports/SystemAgentGenericImportService.java
@@ -93,19 +93,18 @@
         validateAttachment(request.getAttachmentId(), request.getSourceSha256());
         validateRows(request);
 
-        Long tenantId = principal.tenantId();
         Long ownerUserId = principal.userId();
         String digest = requestDigest(moduleId, request);
 
         // 1. 骞傜瓑鍛戒腑锛氬懡浠� ID 浼樺厛锛屽叾娆″箓绛夐敭
         AgentImportCommandDO existing = commandMapper.selectByCommandId(
-                tenantId, ownerUserId, moduleId, request.getCommandId());
+                ownerUserId, moduleId, request.getCommandId());
         if (existing != null) {
             requireSameDigest(existing, digest);
             return toReceipt(existing);
         }
         existing = commandMapper.selectByIdempotencyKey(
-                tenantId, ownerUserId, moduleId, request.getIdempotencyKey());
+                ownerUserId, moduleId, request.getIdempotencyKey());
         if (existing != null) {
             requireSameDigest(existing, digest);
             return toReceipt(existing);
@@ -129,7 +128,6 @@
                 .idempotencyKey(request.getIdempotencyKey())
                 .requestDigest(digest)
                 .moduleId(moduleId)
-                .tenantId(tenantId)
                 .ownerUserId(ownerUserId)
                 .sessionId(request.getSessionId())
                 .attachmentId(request.getAttachmentId())
@@ -145,10 +143,10 @@
             commandMapper.insert(command);
         } catch (DuplicateKeyException ex) {
             AgentImportCommandDO raced = commandMapper.selectByCommandId(
-                    tenantId, ownerUserId, moduleId, request.getCommandId());
+                    ownerUserId, moduleId, request.getCommandId());
             if (raced == null) {
                 raced = commandMapper.selectByIdempotencyKey(
-                        tenantId, ownerUserId, moduleId, request.getIdempotencyKey());
+                        ownerUserId, moduleId, request.getIdempotencyKey());
             }
             if (raced == null) {
                 throw ex;
@@ -226,7 +224,7 @@
     public AgentImportReceiptRespVO getCommand(String moduleId, String commandId, AgentPrincipal principal) {
         requireAdapter(moduleId);
         AgentImportCommandDO command = commandMapper.selectByCommandId(
-                principal.tenantId(), principal.userId(), moduleId, commandId);
+                principal.userId(), moduleId, commandId);
         if (command == null) {
             throw new AgentBridgeException(HttpStatus.NOT_FOUND, "COMMAND_NOT_FOUND", "瀵煎叆鍛戒护涓嶅瓨鍦�");
         }
@@ -297,9 +295,11 @@
     }
 
     private void requirePermission(AgentImportModuleAdapter adapter) {
-        if (!securityFrameworkService.hasPermission(adapter.permission())) {
-            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "FORBIDDEN",
-                    "褰撳墠鐢ㄦ埛缂哄皯璇ユā鍧楃殑瀵煎叆鏉冮檺锛�" + adapter.permission() + "锛�");
+        for (String permission : adapter.permissions()) {
+            if (!securityFrameworkService.hasPermission(permission)) {
+                throw new AgentBridgeException(HttpStatus.FORBIDDEN, "FORBIDDEN",
+                        "褰撳墠鐢ㄦ埛缂哄皯璇ユā鍧楃殑瀵煎叆鏉冮檺锛�" + permission + "锛�");
+            }
         }
     }
 
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/storage/SystemAgentImportBridgeService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/storage/SystemAgentImportBridgeService.java
index a1b00ef..3d7629d 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/storage/SystemAgentImportBridgeService.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/storage/SystemAgentImportBridgeService.java
@@ -75,6 +75,23 @@
                         "闄勪欢缁戝畾澶辫触锛岃閲嶆柊涓婁紶鍚庡啀璇�", true, null));
     }
 
+    /** 鍒犻櫎琛ュ綍闄勪欢鎵�灞炰細璇濓細闄勪欢鍏崇郴銆乥lob 璁板綍涓庣鐩樻枃浠朵竴骞舵竻鐞嗭紝闄勪欢涓嶅瓨鍦ㄦ椂骞傜瓑杩斿洖銆� */
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteImportAttachment(Long attachmentId) {
+        SystemStorageAttachmentDO attachment = attachmentMapper.selectById(attachmentId);
+        if (attachment == null) {
+            return;
+        }
+        if (!RECORD_TYPE.equals(attachment.getRecordType())) {
+            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_FORBIDDEN", "闄勪欢涓嶅彲璁块棶");
+        }
+        Long sessionId = attachment.getRecordId();
+        requireOwnedSession(sessionId);
+        // 鎸変細璇濇暣鎵瑰垹闄わ紝鍚屼竴浼氳瘽鍐呰鏇挎崲鐨勬棫闄勪欢琛屼笌 blob 鏂囦欢閮戒笉浼氭畫鐣�
+        attachmentService.deleteAttachmentsByRecordTypeAndRecordId(RECORD_TYPE, sessionId);
+        sessionMapper.deleteById(sessionId);
+    }
+
     public SystemAgentAttachmentRef requireOwnedAttachmentRef(Long attachmentId) {
         SystemStorageAttachmentDO attachment = attachmentMapper.selectById(attachmentId);
         if (attachment == null) {
diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationServiceTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationServiceTest.java
new file mode 100644
index 0000000..81a99a1
--- /dev/null
+++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/framework/agent/SystemAgentDelegationServiceTest.java
@@ -0,0 +1,127 @@
+package cn.iocoder.yudao.module.system.framework.agent;
+
+import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
+import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SystemAgentDelegationServiceTest {
+
+    private final Map<String, AgentDelegation> records = new HashMap<>();
+    private boolean permissionGranted = true;
+    private SystemAgentDelegationService service;
+
+    @BeforeEach
+    void setUp() {
+        AgentDelegationStore store = new AgentDelegationStore() {
+            @Override
+            public void save(AgentDelegation delegation, long ttlSeconds) {
+                records.put(delegation.getId(), delegation);
+            }
+
+            @Override
+            public AgentDelegation get(String delegationId) {
+                return records.get(delegationId);
+            }
+
+            @Override
+            public void delete(String delegationId) {
+                records.remove(delegationId);
+            }
+        };
+        PermissionCommonApi permissions = new PermissionCommonApi() {
+            @Override
+            public boolean hasAnyPermissions(Long userId, String... permission) {
+                return permissionGranted;
+            }
+
+            @Override
+            public boolean hasAnyRoles(Long userId, String... roles) {
+                return false;
+            }
+
+            @Override
+            public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
+                return null;
+            }
+        };
+        service = new SystemAgentDelegationService(store, permissions);
+        ReflectionTestUtils.setField(service, "delegationTtlSeconds", 1800L);
+        login(7L);
+    }
+
+    @AfterEach
+    void clearContext() {
+        SecurityContextHolder.clearContext();
+        RequestContextHolder.resetRequestAttributes();
+    }
+
+    @Test
+    void issuedTokenStoresOnlySecretHash() {
+        AgentDelegationToken result = issue("task-1");
+        AgentDelegation stored = records.get(result.delegationId());
+        assertNotNull(stored);
+        assertFalse(stored.getSecretHash().contains(result.token().substring(result.token().indexOf('.') + 1)));
+        assertEquals("task-1", stored.getTaskId());
+        assertNotNull(result.expiresAt());
+    }
+
+    @Test
+    void rejectsDifferentTaskAndTamperedSecret() {
+        AgentDelegationToken result = issue("task-1");
+        AgentBridgeException mismatch = assertThrows(AgentBridgeException.class,
+                () -> service.require(result.token(), "task-2", "attachment.read",
+                        "mdm:item:import", "mdm.item", 21L));
+        assertEquals("DELEGATION_SCOPE_MISMATCH", mismatch.getErrorCode());
+
+        String tampered = result.token().substring(0, result.token().indexOf('.') + 1) + "tampered";
+        AgentBridgeException invalid = assertThrows(AgentBridgeException.class,
+                () -> service.require(tampered, "task-1", "attachment.read",
+                        "mdm:item:import", "mdm.item", 21L));
+        assertEquals("DELEGATION_INVALID", invalid.getErrorCode());
+    }
+
+    @Test
+    void permissionRevocationInvalidatesExistingToken() {
+        AgentDelegationToken result = issue("task-1");
+        permissionGranted = false;
+        AgentBridgeException revoked = assertThrows(AgentBridgeException.class,
+                () -> service.require(result.token(), "task-1", "attachment.read",
+                        "mdm:item:import", "mdm.item", 21L));
+        assertEquals("DELEGATION_PERMISSION_REVOKED", revoked.getErrorCode());
+    }
+
+    @Test
+    void ownerCanRevokeToken() {
+        AgentDelegationToken result = issue("task-1");
+        service.revoke(result.delegationId(), 7L);
+        assertFalse(records.containsKey(result.delegationId()));
+    }
+
+    private AgentDelegationToken issue(String taskId) {
+        return service.issue(taskId, "mdm.item", 21L, Set.of("attachment.read"), "mdm:item:import");
+    }
+
+    private static void login(Long userId) {
+        LoginUser loginUser = new LoginUser().setId(userId).setUserType(UserTypeEnum.ADMIN.getValue());
+        SecurityContextHolder.getContext().setAuthentication(
+                new UsernamePasswordAuthenticationToken(loginUser, null, List.of()));
+        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
+    }
+}
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index ea3a9e5..908ee89 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -190,10 +190,12 @@
   security:
     mock-enable: true
   agent:
+    bridge-service-token: ${MOM_BRIDGE_SERVICE_TOKEN:local-mom-agent-bridge-2026}
     bridge:
-      write-enabled: false # 鏅鸿兘浣撶墿鏂欏鍏ュ啓寮�鍏筹紱浠呰仈璋冮獙鏀舵椂涓存椂鎵撳紑
+      write-enabled: true # 鏈満鑱旇皟锛氬厑璁告櫤鑳戒綋鎻愪氦涓氬姟瀵煎叆
       allow-mes-sync: false
       max-rows: 5000
+      delegation-ttl-seconds: 1800 # Worker 鐭湡濮旀墭鏈�闀� 30 鍒嗛挓锛屼笉淇濆瓨鐢ㄦ埛鐧诲綍 Token
   access-log: # 璁块棶鏃ュ織鐨勯厤缃」
     enable: false
   demo: false # 鍏抽棴婕旂ず妯″紡

--
Gitblit v1.9.3