@cloudbase/cloudbase-mcp 2.25.5 → 2.25.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -205,13 +205,13 @@ function buildEnvDomainManagementResult(params) {
205
205
  operation: action,
206
206
  targetDomains: domains,
207
207
  asyncState: "PENDING",
208
- message: '安全域名已提交添加请求。该变更通常需要约 10 分钟传播,请继续轮询 queryEnv(action="domains"),直到目标域名状态为 ENABLE。',
208
+ message: '安全域名已提交添加请求。该变更通常需要数分钟传播(平台侧);请每 10 秒轮询 queryEnv(action="domains") 直到 Status 为 ENABLE,勿一次 sleep 满 10 分钟。',
209
209
  propagation: {
210
210
  requiresPolling: true,
211
211
  pollTool: "queryEnv",
212
212
  pollAction: "domains",
213
213
  pollIntervalSuggestionSeconds: 10,
214
- timeoutSuggestionSeconds: 600,
214
+ timeoutSuggestionSeconds: 300,
215
215
  successCondition: '目标域名出现在 queryEnv(action="domains") 返回中,且 Status 为 ENABLE。',
216
216
  },
217
217
  next_step: {
@@ -230,13 +230,13 @@ function buildEnvDomainManagementResult(params) {
230
230
  operation: action,
231
231
  targetDomains: domains,
232
232
  asyncState: "PENDING",
233
- message: '安全域名已提交删除请求。该变更可能需要数分钟传播,请继续轮询 queryEnv(action="domains"),直到目标域名不再出现。',
233
+ message: '安全域名已提交删除请求。该变更通常需要数分钟传播;请每 10 秒轮询 queryEnv(action="domains") 直到目标域名不再出现,勿一次 sleep 满数分钟。',
234
234
  propagation: {
235
235
  requiresPolling: true,
236
236
  pollTool: "queryEnv",
237
237
  pollAction: "domains",
238
238
  pollIntervalSuggestionSeconds: 10,
239
- timeoutSuggestionSeconds: 600,
239
+ timeoutSuggestionSeconds: 300,
240
240
  successCondition: '目标域名不再出现在 queryEnv(action="domains") 返回中。',
241
241
  },
242
242
  next_step: {
@@ -1944,7 +1944,7 @@ function registerEnvTools(server) {
1944
1944
  if (server.ide !== 'wxide') {
1945
1945
  server.registerTool?.("envDomainManagement", {
1946
1946
  title: "CloudBase 环境域名管理(安全域名 / CORS 白名单)",
1947
- description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后通常需要继续轮询 queryEnv(action=domains) 确认状态收敛;安全域名一般约 10 分钟生效。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。自定义域名公网 HTTPS:先 queryGateway(listCustomDomains);已有域名则 manageGateway(createRoute) 显式传 domain(无需证书);仅首次绑定新域名才用 bindCustomDomain(需 certificateId)。",
1947
+ description: "管理 CloudBase 环境的安全域名(安全域名 / CORS 白名单),支持添加和删除操作。(原工具名:createEnvDomain/deleteEnvDomain,为兼容旧AI规则可继续使用这些名称)当浏览器 Web 应用需要从本地 Vite / dev server 直接访问 CloudBase 资源时,应先用 queryEnv(action=domains) 检查当前实际浏览器 origin 对应的 host:port 是否已在白名单中,再按该实际值添加。新增或删除后请每约 10 秒轮询 queryEnv(action=domains) 确认状态收敛,勿一次 sleep 满 10 分钟;多数环境数分钟内可收敛。⚠️ 重要:此工具仅用于 CORS/请求来源验证,不涉及 SSL 证书。自定义域名公网 HTTPS:先 queryGateway(listCustomDomains);已有域名则 manageGateway(createRoute) 显式传 domain(无需证书);仅首次绑定新域名才用 bindCustomDomain(需 certificateId)。",
1948
1948
  inputSchema: {
1949
1949
  action: zod_1.z
1950
1950
  .enum(["create", "delete"])
@@ -2772,7 +2772,7 @@ ${envIdSection}
2772
2772
  ## 环境信息
2773
2773
  - 操作系统: ${os_1.default.type()} ${os_1.default.release()}
2774
2774
  - Node.js版本: ${process.version}
2775
- - MCP 版本:${process.env.npm_package_version || "2.25.5" || 0}
2775
+ - MCP 版本:${process.env.npm_package_version || "2.25.7" || 0}
2776
2776
  - 系统架构: ${os_1.default.arch()}
2777
2777
  - 时间: ${new Date().toISOString()}
2778
2778
  - 请求ID: ${requestId}
@@ -2853,7 +2853,7 @@ function createWrappedHandler(name, handler, server) {
2853
2853
  requestId: (typeof error === 'object' && error && 'requestId' in error) ? error.requestId : '',
2854
2854
  ide: server.ide || process.env.INTEGRATION_IDE || ''
2855
2855
  });
2856
- const mcpVersion = true ? "2.25.5" : 0;
2856
+ const mcpVersion = true ? "2.25.7" : 0;
2857
2857
  const requestIdSuffix = requestId ? `\n🆔 RequestId: ${requestId}` : '';
2858
2858
  const enhancedErrorMessage = `${errorMessage}${requestIdSuffix}\n\n📦 CloudBase MCP v${mcpVersion}\n🔗 遇到问题?请复制以下链接到浏览器打开\n即可自动携带错误详情快速创建 GitHub Issue:\n${issueLink}`;
2859
2859
  // 创建新的错误对象,保持原有的错误类型但更新消息
@@ -7165,7 +7165,7 @@ const KnowledgeBaseIdMap = {
7165
7165
  const CACHE_BASE_DIR = path.join(os.homedir(), ".cloudbase-mcp");
7166
7166
  const CACHE_META_FILE = path.join(CACHE_BASE_DIR, "cache-meta.json");
7167
7167
  const LOCK_FILE = path.join(CACHE_BASE_DIR, ".download.lock");
7168
- const DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 默认 24 小时
7168
+ const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // default 1 hour (was 24h; templates/docs refresh more often)
7169
7169
  // Promise wrapper for lockfile methods
7170
7170
  function acquireLock(lockPath, options) {
7171
7171
  return new Promise((resolve, reject) => {
@@ -8056,7 +8056,7 @@ function registerGatewayTools(server) {
8056
8056
  {
8057
8057
  tool: "queryGateway",
8058
8058
  action: "getRoute",
8059
- reason: "等待 30 秒到 3 分钟后再确认访问入口是否已生效",
8059
+ reason: "创建后通常数秒到约 30 秒内生效;请立刻轮询 getRoute 或探测 accessUrl,勿盲等 60 秒以上",
8060
8060
  },
8061
8061
  {
8062
8062
  tool: "queryPermissions",
@@ -8204,7 +8204,7 @@ function registerGatewayTools(server) {
8204
8204
  const privilege = await getGatewayPrivilege();
8205
8205
  if (privilege.EnableService !== true) {
8206
8206
  privilegeHint =
8207
- "⚠️ HTTP 网关总开关未开启,路由创建成功后访问仍将返回 HTTPSERVICE_NONACTIVATED(403);请先调用 manageGateway(action=\"enableService\", enable=true) 开启,再等待 30 秒到 3 分钟访问。";
8207
+ "⚠️ HTTP 网关总开关未开启,路由创建成功后访问仍将返回 HTTPSERVICE_NONACTIVATED(403);请先调用 manageGateway(action=\"enableService\", enable=true) 开启,再立刻探测 accessUrl(通常数秒到约 30 秒内生效,勿盲等 60 秒以上)。";
8208
8208
  privilegeNextActions = [
8209
8209
  {
8210
8210
  tool: "manageGateway",
@@ -8240,7 +8240,7 @@ function registerGatewayTools(server) {
8240
8240
  : payload.resolved.enablePathTransmission === false
8241
8241
  ? ";路径透传关闭(网关会剥掉触发路径前缀后再转发给后端)"
8242
8242
  : ";路径透传未显式设置(平台默认 false,会剥掉触发路径前缀)") +
8243
- `。注意:路由配置传播通常需要等待 30 秒到 3 分钟,请勿立即访问。该操作只创建网关入口,不会自动放开上游权限;若上游是云函数且需要匿名或浏览器直接访问,请继续检查函数资源权限。` +
8243
+ `。注意:路由配置传播通常数秒到约 30 秒;请立刻用 queryGateway(getRoute) 或探测 accessUrl 确认,勿盲等 60 秒以上。该操作只创建网关入口,不会自动放开上游权限;若上游是云函数且需要匿名或浏览器直接访问,请继续检查函数资源权限。` +
8244
8244
  (privilegeHint ? ` ${privilegeHint}` : ""), [
8245
8245
  ...privilegeNextActions,
8246
8246
  ...(routeMutationNextActions(payload.resolved.upstreamResourceName) ??
@@ -11208,7 +11208,7 @@ class TelemetryReporter {
11208
11208
  const nodeVersion = process.version; // Node.js版本
11209
11209
  const arch = os_1.default.arch(); // 系统架构
11210
11210
  // 从构建时注入的版本号获取MCP版本信息
11211
- const mcpVersion = process.env.npm_package_version || "2.25.5" || 0;
11211
+ const mcpVersion = process.env.npm_package_version || "2.25.7" || 0;
11212
11212
  return {
11213
11213
  userAgent: `${osType} ${osRelease} ${arch} ${nodeVersion} CloudBase-MCP/${mcpVersion}`,
11214
11214
  deviceId: this.deviceId,
@@ -11545,6 +11545,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
11545
11545
  exports.CUSTOM_IMAGE_RUNTIME = exports.MANAGE_FUNCTION_ACTIONS = exports.QUERY_FUNCTION_ACTIONS = exports.TRIGGER_CONFIG_EXAMPLES = exports.SUPPORTED_TRIGGER_TYPES = exports.DEFAULT_NODEJS_RUNTIME = exports.SUPPORTED_NODEJS_RUNTIMES = exports.RECOMMENDED_RUNTIMES = exports.DEFAULT_RUNTIME = exports.ALL_SUPPORTED_RUNTIMES = exports.SUPPORTED_RUNTIMES = void 0;
11546
11546
  exports.formatRuntimeList = formatRuntimeList;
11547
11547
  exports.validateTimerCron = validateTimerCron;
11548
+ exports.pickManageFunctionName = pickManageFunctionName;
11548
11549
  exports.shouldInstallDependencyForFunction = shouldInstallDependencyForFunction;
11549
11550
  exports.resolveEventFunctionRuntime = resolveEventFunctionRuntime;
11550
11551
  exports.buildFunctionOperationErrorMessage = buildFunctionOperationErrorMessage;
@@ -11773,6 +11774,19 @@ const MANAGE_LAYER_SCHEMA = zod_1.z.object({
11773
11774
  layerName: zod_1.z.string().describe("层名称"),
11774
11775
  layerVersion: zod_1.z.number().describe("层版本号"),
11775
11776
  });
11777
+ /**
11778
+ * Prefer top-level functionName; fall back to func.name for the common
11779
+ * mistake of nesting the target name under func (createFunction shape).
11780
+ */
11781
+ function pickManageFunctionName(input) {
11782
+ if (typeof input.functionName === "string" && input.functionName.trim()) {
11783
+ return input.functionName.trim();
11784
+ }
11785
+ if (typeof input.func?.name === "string" && input.func.name.trim()) {
11786
+ return input.func.name.trim();
11787
+ }
11788
+ return undefined;
11789
+ }
11776
11790
  function normalizeFunctionLayers(layers) {
11777
11791
  if (!Array.isArray(layers)) {
11778
11792
  return [];
@@ -12239,7 +12253,12 @@ function registerFunctionTools(server) {
12239
12253
  throw new Error(`不支持的操作类型: ${input.action}`);
12240
12254
  }
12241
12255
  };
12242
- const handleManageFunctions = async (input) => {
12256
+ const handleManageFunctions = async (rawInput) => {
12257
+ // Accept func.name as a fallback for top-level functionName (common agent mistake).
12258
+ const pickedName = pickManageFunctionName(rawInput);
12259
+ const input = pickedName && rawInput.functionName !== pickedName
12260
+ ? { ...rawInput, functionName: pickedName }
12261
+ : rawInput;
12243
12262
  ensureActionAllowedInCloudMode(input);
12244
12263
  switch (input.action) {
12245
12264
  case "createFunction": {
@@ -12945,7 +12964,12 @@ function registerFunctionTools(server) {
12945
12964
  "此参数传 cloudfunctions 或 functions 目录的绝对路径。" +
12946
12965
  "SDK 会自动拼接函数名子目录,无需预先压缩 zip 或 base64 编码。"),
12947
12966
  force: zod_1.z.boolean().optional().describe("createFunction 时是否覆盖"),
12948
- functionName: zod_1.z.string().optional().describe("函数名称。大多数 action 使用该字段作为统一目标"),
12967
+ functionName: zod_1.z
12968
+ .string()
12969
+ .optional()
12970
+ .describe("目标函数名称(顶层)。updateFunctionCode / updateFunctionConfig / invokeFunction 等 action 使用此字段。" +
12971
+ "不要只写在 func.name:createFunction 用 func.name,其它 action 用顶层 functionName。" +
12972
+ "若误传 func.name,也会被识别为 functionName。"),
12949
12973
  zipFile: zod_1.z.string().optional().describe("仅兼容特殊场景:预先准备好的代码包 base64 编码。普通 createFunction/updateFunctionCode 默认不要先压缩 zip,优先使用 functionRootPath。"),
12950
12974
  handler: zod_1.z.string().optional().describe("函数入口"),
12951
12975
  timeout: zod_1.z.number().optional().describe("配置更新时的超时时间"),
@@ -13000,12 +13024,47 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_ws__;
13000
13024
  /***/ }),
13001
13025
 
13002
13026
  /***/ 6473:
13003
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
13027
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
13004
13028
 
13005
13029
 
13030
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13031
+ if (k2 === undefined) k2 = k;
13032
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13033
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13034
+ desc = { enumerable: true, get: function() { return m[k]; } };
13035
+ }
13036
+ Object.defineProperty(o, k2, desc);
13037
+ }) : (function(o, m, k, k2) {
13038
+ if (k2 === undefined) k2 = k;
13039
+ o[k2] = m[k];
13040
+ }));
13041
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
13042
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
13043
+ }) : function(o, v) {
13044
+ o["default"] = v;
13045
+ });
13046
+ var __importStar = (this && this.__importStar) || (function () {
13047
+ var ownKeys = function(o) {
13048
+ ownKeys = Object.getOwnPropertyNames || function (o) {
13049
+ var ar = [];
13050
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
13051
+ return ar;
13052
+ };
13053
+ return ownKeys(o);
13054
+ };
13055
+ return function (mod) {
13056
+ if (mod && mod.__esModule) return mod;
13057
+ var result = {};
13058
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
13059
+ __setModuleDefault(result, mod);
13060
+ return result;
13061
+ };
13062
+ })();
13006
13063
  Object.defineProperty(exports, "__esModule", ({ value: true }));
13007
13064
  exports.__resetPgReadyCache = __resetPgReadyCache;
13008
13065
  exports.registerPGDatabaseTools = registerPGDatabaseTools;
13066
+ const fs = __importStar(__webpack_require__(4421));
13067
+ const path = __importStar(__webpack_require__(2521));
13009
13068
  const zod_1 = __webpack_require__(2971);
13010
13069
  const cloudbase_manager_js_1 = __webpack_require__(3431);
13011
13070
  const tool_result_js_1 = __webpack_require__(9835);
@@ -13026,6 +13085,8 @@ const MANAGE_ACTIONS = [
13026
13085
  "applyMigration",
13027
13086
  "listMigrations",
13028
13087
  "migrationDetail",
13088
+ "describeMigrationTask",
13089
+ "fetchMigration",
13029
13090
  "rollbackMigration",
13030
13091
  "repairMigration",
13031
13092
  ];
@@ -13186,8 +13247,170 @@ function isSchemaDdlRisk(risk, sql) {
13186
13247
  const verb = getSqlVerb(stripLeadingSqlComments(sql));
13187
13248
  return ["DROP", "TRUNCATE", "ALTER"].includes(verb);
13188
13249
  }
13250
+ /**
13251
+ * Canonical local migration directory — must match CloudBase CLI
13252
+ * `MIGRATIONS_DIR` (`cloudbase/migrations`) so MCP+CLI mixed workflows share files.
13253
+ * Legacy bare `migrations/` is accepted only when an existing matching file is found.
13254
+ */
13255
+ const LOCAL_MIGRATIONS_DIR = "cloudbase/migrations";
13256
+ const LOCAL_MIGRATIONS_DIR_LEGACY = "migrations";
13257
+ /** Same constraint as applyMigration.migrationName (broader than CLI /^[a-z_]+$/ — digits allowed). */
13258
+ const MIGRATION_NAME_PATTERN = /^[a-z][a-z0-9_]*$/;
13259
+ function getMigrationProjectRoot() {
13260
+ const fromEnv = process.env.WORKSPACE_FOLDER_PATHS ||
13261
+ process.env.PROJECT_ROOT ||
13262
+ process.env.GITHUB_WORKSPACE ||
13263
+ process.env.CI_PROJECT_DIR ||
13264
+ process.env.BUILD_SOURCESDIRECTORY;
13265
+ if (fromEnv && fromEnv.trim()) {
13266
+ // IDEs may inject multiple folders separated by path.delimiter.
13267
+ const first = fromEnv.split(path.delimiter).map((p) => p.trim()).find(Boolean);
13268
+ if (first) {
13269
+ return first;
13270
+ }
13271
+ }
13272
+ return process.cwd();
13273
+ }
13274
+ function buildLocalMigrationFileName(version, name) {
13275
+ return `${version}_${name}.sql`;
13276
+ }
13189
13277
  function buildLocalMigrationFileHint(version, name) {
13190
- return `migrations/${version}_${name}.sql`;
13278
+ return `${LOCAL_MIGRATIONS_DIR}/${buildLocalMigrationFileName(version, name)}`;
13279
+ }
13280
+ /** Normalize SQL for local file compare/write (LF + single trailing newline). */
13281
+ function normalizeMigrationSqlForLocalFile(sql) {
13282
+ const normalized = sql.replace(/\r\n/g, "\n").replace(/\s+$/u, "");
13283
+ return `${normalized}\n`;
13284
+ }
13285
+ /**
13286
+ * Ensure the workspace has a Git-truth SQL file for this migration before Push.
13287
+ * Prefer write-to-workspace when missing; fail closed on mismatch or write failure
13288
+ * (Supabase MCP#241 class: remote apply without local truth source).
13289
+ */
13290
+ function ensureLocalMigrationSqlFile(version, name, sql) {
13291
+ const fileName = buildLocalMigrationFileName(version, name);
13292
+ const expected = normalizeMigrationSqlForLocalFile(sql);
13293
+ const projectRoot = getMigrationProjectRoot();
13294
+ const candidates = [
13295
+ {
13296
+ relativePath: `${LOCAL_MIGRATIONS_DIR}/${fileName}`,
13297
+ absolutePath: path.join(projectRoot, LOCAL_MIGRATIONS_DIR, fileName),
13298
+ },
13299
+ {
13300
+ relativePath: `${LOCAL_MIGRATIONS_DIR_LEGACY}/${fileName}`,
13301
+ absolutePath: path.join(projectRoot, LOCAL_MIGRATIONS_DIR_LEGACY, fileName),
13302
+ },
13303
+ ];
13304
+ for (const candidate of candidates) {
13305
+ if (!fs.existsSync(candidate.absolutePath)) {
13306
+ continue;
13307
+ }
13308
+ let existing;
13309
+ try {
13310
+ existing = fs.readFileSync(candidate.absolutePath, "utf8");
13311
+ }
13312
+ catch (error) {
13313
+ return {
13314
+ ok: false,
13315
+ errorCode: "LOCAL_MIGRATION_FILE_WRITE_FAILED",
13316
+ message: `Failed to read existing local migration file ${candidate.relativePath}: ` +
13317
+ `${error instanceof Error ? error.message : String(error)}`,
13318
+ relativePath: candidate.relativePath,
13319
+ absolutePath: candidate.absolutePath,
13320
+ };
13321
+ }
13322
+ if (normalizeMigrationSqlForLocalFile(existing) === expected) {
13323
+ return {
13324
+ ok: true,
13325
+ relativePath: candidate.relativePath,
13326
+ absolutePath: candidate.absolutePath,
13327
+ action: "matched",
13328
+ };
13329
+ }
13330
+ return {
13331
+ ok: false,
13332
+ errorCode: "LOCAL_MIGRATION_FILE_MISMATCH",
13333
+ message: `Local migration file ${candidate.relativePath} already exists but its content does not match the sql argument. ` +
13334
+ "Refuse to overwrite (fail closed). Update the local file to match, or use a new migrationVersion / migrationName. " +
13335
+ "Push was NOT submitted.",
13336
+ relativePath: candidate.relativePath,
13337
+ absolutePath: candidate.absolutePath,
13338
+ };
13339
+ }
13340
+ const target = candidates[0];
13341
+ try {
13342
+ fs.mkdirSync(path.dirname(target.absolutePath), { recursive: true });
13343
+ fs.writeFileSync(target.absolutePath, expected, "utf8");
13344
+ }
13345
+ catch (error) {
13346
+ return {
13347
+ ok: false,
13348
+ errorCode: "LOCAL_MIGRATION_FILE_WRITE_FAILED",
13349
+ message: `Failed to write local migration file ${target.relativePath} under workspace ${projectRoot}: ` +
13350
+ `${error instanceof Error ? error.message : String(error)}. ` +
13351
+ "Push was NOT submitted. Fix workspace permissions or write the file manually, then retry applyMigration.",
13352
+ relativePath: target.relativePath,
13353
+ absolutePath: target.absolutePath,
13354
+ };
13355
+ }
13356
+ return {
13357
+ ok: true,
13358
+ relativePath: target.relativePath,
13359
+ absolutePath: target.absolutePath,
13360
+ action: "created",
13361
+ };
13362
+ }
13363
+ /**
13364
+ * Sync remote migration Query bodies into cloudbase/migrations/ (CLI fetch parity).
13365
+ * Without force, existing files are skipped; with force, overwritten.
13366
+ */
13367
+ function syncFetchedMigrationsToLocalFiles(items, force) {
13368
+ const projectRoot = getMigrationProjectRoot();
13369
+ const migrationsDir = path.join(projectRoot, LOCAL_MIGRATIONS_DIR);
13370
+ const files = [];
13371
+ for (const item of items) {
13372
+ const version = item.Version.trim();
13373
+ const name = item.Name.trim();
13374
+ if (!MIGRATION_NAME_PATTERN.test(name)) {
13375
+ return {
13376
+ ok: false,
13377
+ errorCode: "LOCAL_MIGRATION_FETCH_INVALID_NAME",
13378
+ message: `Remote migration name "${name}" (version=${version}) is invalid. ` +
13379
+ "Names must match /^[a-z][a-z0-9_]*$/ (same as applyMigration.migrationName). " +
13380
+ "Repair the remote history record, then retry fetchMigration.",
13381
+ };
13382
+ }
13383
+ if (!item.Query.trim()) {
13384
+ return {
13385
+ ok: false,
13386
+ errorCode: "LOCAL_MIGRATION_FETCH_EMPTY_QUERY",
13387
+ message: `DescribePGUserMigration returned empty Query for version=${version} (${name}). ` +
13388
+ "Refuse to write an empty local migration file (would poison Git checksums).",
13389
+ };
13390
+ }
13391
+ const relativePath = buildLocalMigrationFileHint(version, name);
13392
+ const absolutePath = path.join(migrationsDir, buildLocalMigrationFileName(version, name));
13393
+ const exists = fs.existsSync(absolutePath);
13394
+ if (exists && !force) {
13395
+ files.push({ version, name, relativePath, absolutePath, action: "skipped" });
13396
+ continue;
13397
+ }
13398
+ try {
13399
+ fs.mkdirSync(migrationsDir, { recursive: true });
13400
+ fs.writeFileSync(absolutePath, normalizeMigrationSqlForLocalFile(item.Query), "utf8");
13401
+ }
13402
+ catch (error) {
13403
+ return {
13404
+ ok: false,
13405
+ errorCode: "LOCAL_MIGRATION_FILE_WRITE_FAILED",
13406
+ message: `Failed to write local migration file ${relativePath} under workspace ${projectRoot}: ` +
13407
+ `${error instanceof Error ? error.message : String(error)}`,
13408
+ relativePath,
13409
+ };
13410
+ }
13411
+ files.push({ version, name, relativePath, absolutePath, action: "written" });
13412
+ }
13413
+ return { ok: true, files };
13191
13414
  }
13192
13415
  /** How many recent migration records to scan when verifying a freshly pushed version. */
13193
13416
  const MIGRATION_VERIFY_LIMIT = 100;
@@ -13222,10 +13445,27 @@ const MIGRATION_VERIFY_DELAY_MS = 1500;
13222
13445
  const MIGRATION_LIST_PAGE_SIZE = 100;
13223
13446
  /**
13224
13447
  * PushPGUserMigrations is async: poll DescribeTaskResult until Succeed/Failed.
13225
- * Aligns with CLI `tcb db pg migration up` and Manager SDK docs.
13448
+ * Default wait aligns with CLI `tcb db pg migration up` (10 minutes).
13449
+ * Override per call via applyMigration.taskPollTimeoutMs.
13226
13450
  */
13227
13451
  const MIGRATION_TASK_POLL_INTERVAL_MS = 1500;
13228
- const MIGRATION_TASK_MAX_WAIT_MS = 90_000;
13452
+ /** CLI parity: `TASK_MAX_WAIT_MS = 10 * 60 * 1000` in cloudbase-cli migration up. */
13453
+ const MIGRATION_TASK_DEFAULT_WAIT_MS = 10 * 60 * 1000;
13454
+ const MIGRATION_TASK_MIN_WAIT_MS = 5_000;
13455
+ const MIGRATION_TASK_MAX_WAIT_MS = MIGRATION_TASK_DEFAULT_WAIT_MS;
13456
+ function resolveMigrationTaskPollTimeoutMs(requested) {
13457
+ if (requested === undefined || !Number.isFinite(requested)) {
13458
+ return MIGRATION_TASK_DEFAULT_WAIT_MS;
13459
+ }
13460
+ const rounded = Math.floor(requested);
13461
+ if (rounded < MIGRATION_TASK_MIN_WAIT_MS) {
13462
+ return MIGRATION_TASK_MIN_WAIT_MS;
13463
+ }
13464
+ if (rounded > MIGRATION_TASK_MAX_WAIT_MS) {
13465
+ return MIGRATION_TASK_MAX_WAIT_MS;
13466
+ }
13467
+ return rounded;
13468
+ }
13229
13469
  /**
13230
13470
  * Confirm a pushed migration version is present in the remote migration history.
13231
13471
  *
@@ -13309,12 +13549,114 @@ async function listAllRemoteMigrationSummaries(context, cloudBaseOptions) {
13309
13549
  }
13310
13550
  return { summaries, latestVersion, total };
13311
13551
  }
13552
+ /** `<version>_<name>.sql` — version is the identity key for completeness checks. */
13553
+ const LOCAL_MIGRATION_FILENAME_RE = /^(\d{14})_(.+)\.sql$/;
13554
+ function listLocalMigrationFilesInDir(dirAbsolute, dirRelative) {
13555
+ const map = new Map();
13556
+ if (!fs.existsSync(dirAbsolute)) {
13557
+ return map;
13558
+ }
13559
+ let stat;
13560
+ try {
13561
+ stat = fs.statSync(dirAbsolute);
13562
+ }
13563
+ catch {
13564
+ return map;
13565
+ }
13566
+ if (!stat.isDirectory()) {
13567
+ return map;
13568
+ }
13569
+ let fileNames;
13570
+ try {
13571
+ fileNames = fs.readdirSync(dirAbsolute);
13572
+ }
13573
+ catch {
13574
+ return map;
13575
+ }
13576
+ for (const fileName of fileNames) {
13577
+ const match = LOCAL_MIGRATION_FILENAME_RE.exec(fileName);
13578
+ if (!match) {
13579
+ continue;
13580
+ }
13581
+ const version = match[1];
13582
+ const name = match[2];
13583
+ const absolutePath = path.join(dirAbsolute, fileName);
13584
+ let query;
13585
+ try {
13586
+ query = fs.readFileSync(absolutePath, "utf8");
13587
+ }
13588
+ catch {
13589
+ continue;
13590
+ }
13591
+ if (!query.trim()) {
13592
+ continue;
13593
+ }
13594
+ map.set(version, {
13595
+ version,
13596
+ name,
13597
+ query,
13598
+ relativePath: `${dirRelative}/${fileName}`,
13599
+ });
13600
+ }
13601
+ return map;
13602
+ }
13603
+ /**
13604
+ * Index local migration SQL by version. Canonical `cloudbase/migrations/` wins over legacy `migrations/`.
13605
+ */
13606
+ function listLocalMigrationFilesByVersion() {
13607
+ const projectRoot = getMigrationProjectRoot();
13608
+ const legacy = listLocalMigrationFilesInDir(path.join(projectRoot, LOCAL_MIGRATIONS_DIR_LEGACY), LOCAL_MIGRATIONS_DIR_LEGACY);
13609
+ const canonical = listLocalMigrationFilesInDir(path.join(projectRoot, LOCAL_MIGRATIONS_DIR), LOCAL_MIGRATIONS_DIR);
13610
+ const merged = new Map(legacy);
13611
+ for (const [version, entry] of canonical) {
13612
+ merged.set(version, entry);
13613
+ }
13614
+ return merged;
13615
+ }
13616
+ function remoteHistoryCoveredByLocalFiles(summaries, localByVersion, pendingVersion) {
13617
+ for (const summary of summaries) {
13618
+ if (summary.Version === pendingVersion) {
13619
+ // Pending SQL comes from the tool args / ensureLocal write.
13620
+ continue;
13621
+ }
13622
+ const local = localByVersion.get(summary.Version);
13623
+ if (!local || !local.query.trim()) {
13624
+ return false;
13625
+ }
13626
+ }
13627
+ return true;
13628
+ }
13312
13629
  /**
13313
- * Build Push/Preview Migrations = remote history Queries + the pending migration.
13314
- * Matches CLI behavior (submit full set; server skips already-applied versions).
13630
+ * Build Push/Preview Migrations = full candidate set (server skips already-applied).
13631
+ *
13632
+ * Prefer CLI parity when the workspace already has SQL for every remote version:
13633
+ * read local files (no per-version DescribePGUserMigration). Otherwise hydrate
13634
+ * remote Queries via List + N×Describe (agent/remote-first when the local tree is incomplete).
13315
13635
  */
13316
13636
  async function buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBaseOptions) {
13317
13637
  const { summaries, latestVersion } = await listAllRemoteMigrationSummaries(context, cloudBaseOptions);
13638
+ const localByVersion = listLocalMigrationFilesByVersion();
13639
+ if (remoteHistoryCoveredByLocalFiles(summaries, localByVersion, pending.Version)) {
13640
+ const migrations = [];
13641
+ for (const summary of summaries) {
13642
+ if (summary.Version === pending.Version) {
13643
+ continue;
13644
+ }
13645
+ const local = localByVersion.get(summary.Version);
13646
+ migrations.push({
13647
+ Version: local.version,
13648
+ Name: local.name || summary.Name,
13649
+ Query: local.query,
13650
+ });
13651
+ }
13652
+ migrations.push(pending);
13653
+ return {
13654
+ migrations,
13655
+ remoteCount: summaries.length,
13656
+ latestVersion,
13657
+ payloadSource: "local",
13658
+ };
13659
+ }
13318
13660
  const migrations = [];
13319
13661
  for (const summary of summaries) {
13320
13662
  if (summary.Version === pending.Version) {
@@ -13334,10 +13676,23 @@ async function buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBa
13334
13676
  });
13335
13677
  }
13336
13678
  migrations.push(pending);
13337
- return { migrations, remoteCount: summaries.length, latestVersion };
13679
+ return {
13680
+ migrations,
13681
+ remoteCount: summaries.length,
13682
+ latestVersion,
13683
+ payloadSource: "hydrate",
13684
+ };
13338
13685
  }
13339
- async function waitPgMigrationTask(context, taskId, cloudBaseOptions) {
13340
- const deadline = Date.now() + MIGRATION_TASK_MAX_WAIT_MS;
13686
+ function formatMigrationsPayloadSourceLabel(source, remoteCount) {
13687
+ if (remoteCount <= 0) {
13688
+ return "no remote history";
13689
+ }
13690
+ return source === "local"
13691
+ ? `local tree covered ${remoteCount} remote migration(s) (CLI parity, skipped Describe hydrate)`
13692
+ : `hydrated ${remoteCount} remote migration(s) via Describe (local tree incomplete)`;
13693
+ }
13694
+ async function waitPgMigrationTask(context, taskId, cloudBaseOptions, maxWaitMs = MIGRATION_TASK_DEFAULT_WAIT_MS) {
13695
+ const deadline = Date.now() + maxWaitMs;
13341
13696
  let last = { TaskId: taskId };
13342
13697
  while (Date.now() <= deadline) {
13343
13698
  last = (await callPgMigrationApi(context, "DescribeTaskResult", { TaskId: taskId }, cloudBaseOptions));
@@ -13347,13 +13702,21 @@ async function waitPgMigrationTask(context, taskId, cloudBaseOptions) {
13347
13702
  }
13348
13703
  await sleep(MIGRATION_TASK_POLL_INTERVAL_MS);
13349
13704
  }
13350
- const error = new Error(`DescribeTaskResult timed out after ${Math.floor(MIGRATION_TASK_MAX_WAIT_MS / 1000)}s ` +
13705
+ const error = new Error(`DescribeTaskResult timed out after ${Math.floor(maxWaitMs / 1000)}s ` +
13351
13706
  `for TaskId=${taskId} (last Status=${last.Status || "-"}, Phase=${last.Phase || "-"}).`);
13352
13707
  error.lastTask = last;
13353
13708
  throw error;
13354
13709
  }
13710
+ function buildMigrationTaskPendingNextActions(version, taskId) {
13711
+ const actions = [];
13712
+ if (taskId) {
13713
+ actions.push(buildNextAction(MANAGE_PG_DATABASE, "describeMigrationTask", "FIRST: poll DescribeTaskResult for Status/Phase/Reason. listMigrations alone cannot explain a Failed task (see issue #857).", { action: "describeMigrationTask", taskId }));
13714
+ }
13715
+ actions.push(buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Check whether migrationVersion landed in remote history. Do NOT re-push the same version while the task may still be running.", { action: "listMigrations", limit: 20 }), buildNextAction(MANAGE_PG_DATABASE, "migrationDetail", "If listMigrations already shows this version, inspect the applied record. If missing after several minutes and the task is Failed/terminal, inspect Conflicts/SQL before considering a NEW migrationVersion.", { action: "migrationDetail", migrationVersion: version }));
13716
+ return actions;
13717
+ }
13355
13718
  const MIGRATION_VERSION_REQUIRED_MESSAGE = "migrationVersion is required (14-digit UTC timestamp YYYYMMDDHHMMSS). " +
13356
- "Decide the version first, write local file migrations/<version>_<migrationName>.sql, " +
13719
+ "Decide the version first, write local file cloudbase/migrations/<version>_<migrationName>.sql, " +
13357
13720
  "then call planMigration/applyMigration with the same migrationVersion and migrationName.";
13358
13721
  function requireExplicitMigrationVersion(args, actionLabel) {
13359
13722
  const version = args.migrationVersion?.trim();
@@ -13690,6 +14053,30 @@ function createDefaultDependencies(server) {
13690
14053
  createClient: async (context) => createManagerPgClient(context, server?.cloudBaseOptions),
13691
14054
  };
13692
14055
  }
14056
+ function isPgMigrationApiDatabase(value) {
14057
+ return Boolean(value &&
14058
+ typeof value === "object" &&
14059
+ "previewPGUserMigrations" in value &&
14060
+ typeof value.previewPGUserMigrations === "function" &&
14061
+ "pushPGUserMigrations" in value &&
14062
+ typeof value.pushPGUserMigrations === "function" &&
14063
+ "listPGUserMigrations" in value &&
14064
+ typeof value.listPGUserMigrations === "function" &&
14065
+ "describeTaskResult" in value &&
14066
+ typeof value.describeTaskResult === "function");
14067
+ }
14068
+ /**
14069
+ * Actions without a native wrapper in manager-node (e.g. RollbackPGUserMigrations)
14070
+ * are intentionally absent and keep using the commonService fallback.
14071
+ */
14072
+ const PgMigrationNativeActionMethods = {
14073
+ PreviewPGUserMigrations: "previewPGUserMigrations",
14074
+ PushPGUserMigrations: "pushPGUserMigrations",
14075
+ RepairPGUserMigrationHistory: "repairPGUserMigrationHistory",
14076
+ ListPGUserMigrations: "listPGUserMigrations",
14077
+ DescribePGUserMigration: "describePGUserMigration",
14078
+ DescribeTaskResult: "describeTaskResult",
14079
+ };
13693
14080
  function isExecutePGSqlDatabase(value) {
13694
14081
  return Boolean(value &&
13695
14082
  typeof value === "object" &&
@@ -13730,13 +14117,22 @@ async function executeManagerPGSql(context, sql, cloudBaseOptions) {
13730
14117
  }
13731
14118
  throw new Error("Current @cloudbase/manager-node runtime does not expose database.executePGSql or commonService fallback. Upgrade to @cloudbase/manager-node >= 5.4.0.");
13732
14119
  }
13733
- /** Call a CloudBase PG migration API via commonService fallback */
14120
+ /**
14121
+ * Call a CloudBase PG migration API, preferring native manager-node methods
14122
+ * (>= 5.6.5) and falling back to the commonService channel for older runtimes
14123
+ * and actions without a native wrapper (RollbackPGUserMigrations).
14124
+ */
13734
14125
  async function callPgMigrationApi(context, action, params, cloudBaseOptions) {
13735
14126
  const manager = await (0, cloudbase_manager_js_1.getCloudBaseManager)({
13736
14127
  cloudBaseOptions: cloudBaseOptions
13737
14128
  ? { ...cloudBaseOptions, envId: context.envId }
13738
14129
  : { envId: context.envId },
13739
14130
  });
14131
+ const nativeMethod = PgMigrationNativeActionMethods[action];
14132
+ if (nativeMethod && isPgMigrationApiDatabase(manager.database)) {
14133
+ const invoke = manager.database[nativeMethod];
14134
+ return invoke.call(manager.database, { EnvId: context.envId, ...params });
14135
+ }
13740
14136
  if (!isCloudBaseWithCommonService(manager)) {
13741
14137
  throw new Error("Current @cloudbase/manager-node runtime does not support migration APIs. Upgrade to @cloudbase/manager-node >= 5.4.0.");
13742
14138
  }
@@ -13977,11 +14373,11 @@ async function handleExecuteSql(args, context, deps) {
13977
14373
  success: false,
13978
14374
  errorCode: "DDL_USE_APPLY_MIGRATION",
13979
14375
  message: "Schema DDL (CREATE/ALTER/DROP/TRUNCATE/...) must use applyMigration with an explicit migrationVersion, " +
13980
- "not execute. Write local migrations/<version>_<name>.sql first, then call applyMigration. " +
14376
+ "not execute. Write local cloudbase/migrations/<version>_<name>.sql first, then call applyMigration. " +
13981
14377
  "Set allowDdlViaExecute=true only for exceptional one-off ops that intentionally bypass migration history.",
13982
14378
  data: {
13983
14379
  classification,
13984
- localFileHintPattern: "migrations/<migrationVersion>_<migrationName>.sql",
14380
+ localFileHintPattern: "cloudbase/migrations/<migrationVersion>_<migrationName>.sql",
13985
14381
  },
13986
14382
  nextActions: [
13987
14383
  buildNextAction(MANAGE_PG_DATABASE, "planMigration", "Preview the schema change as a versioned migration (provide migrationVersion + migrationName).", {
@@ -14090,7 +14486,7 @@ async function handleDryRun(args) {
14090
14486
  wouldExecute: false,
14091
14487
  sqlPreview: args.sql.trim().slice(0, 500),
14092
14488
  preferredAction: "applyMigration",
14093
- localFileHintPattern: "migrations/<migrationVersion>_<migrationName>.sql",
14489
+ localFileHintPattern: "cloudbase/migrations/<migrationVersion>_<migrationName>.sql",
14094
14490
  },
14095
14491
  message: "SQL dry run completed. Schema DDL should use applyMigration with an explicit migrationVersion, not execute.",
14096
14492
  nextActions: [
@@ -14148,10 +14544,13 @@ async function handlePlanMigration(args, context, deps, cloudBaseOptions) {
14148
14544
  }
14149
14545
  try {
14150
14546
  const hydrated = await buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBaseOptions);
14547
+ const includeAll = Boolean(args.includeAll);
14151
14548
  const result = await callPgMigrationApi(context, "PreviewPGUserMigrations", {
14152
14549
  Migrations: hydrated.migrations,
14550
+ IncludeAll: includeAll,
14153
14551
  }, cloudBaseOptions);
14154
14552
  const executable = result.Executable === true;
14553
+ const payloadSourceLabel = formatMigrationsPayloadSourceLabel(hydrated.payloadSource, hydrated.remoteCount);
14155
14554
  return buildPgToolResult({
14156
14555
  success: true,
14157
14556
  data: {
@@ -14160,22 +14559,25 @@ async function handlePlanMigration(args, context, deps, cloudBaseOptions) {
14160
14559
  localFileHint,
14161
14560
  hydratedRemoteCount: hydrated.remoteCount,
14162
14561
  latestRemoteVersion: hydrated.latestVersion || null,
14562
+ payloadSource: hydrated.payloadSource,
14563
+ includeAll,
14163
14564
  executable,
14164
14565
  apiResult: result,
14165
14566
  },
14166
14567
  message: executable
14167
- ? `Migration plan generated via PreviewPGUserMigrations (hydrated ${hydrated.remoteCount} remote migration(s)). Reuse migrationVersion=${version} on applyMigration. Ensure local file ${localFileHint} exists and matches.`
14168
- : `Migration plan is NOT executable (PreviewPGUserMigrations.Executable=false after hydrating ${hydrated.remoteCount} remote migration(s)). Inspect apiResult.Conflicts before applyMigration. Common causes: version older than latest remote (${hydrated.latestVersion || "unknown"}), or checksum mismatch.`,
14568
+ ? `Migration plan generated via PreviewPGUserMigrations (${payloadSourceLabel}${includeAll ? ", includeAll=true" : ""}). Reuse migrationVersion=${version} on applyMigration. Ensure local file ${localFileHint} exists and matches.`
14569
+ : `Migration plan is NOT executable (PreviewPGUserMigrations.Executable=false after ${payloadSourceLabel}${includeAll ? ", includeAll=true" : ""}). Inspect apiResult.Conflicts before applyMigration. Common causes: version older than latest remote (${hydrated.latestVersion || "unknown"}) — retry with includeAll=true (CLI --include-all) if intentionally out-of-order, or pick a newer version; checksum mismatch.`,
14169
14570
  nextActions: [
14170
14571
  buildNextAction(MANAGE_PG_DATABASE, "applyMigration", executable
14171
14572
  ? "Review the plan above. If it looks correct, call applyMigration with the same migrationVersion."
14172
- : "Resolve Conflicts (often pick a migrationVersion newer than LatestVersion), then retry planMigration/applyMigration.", {
14573
+ : "Resolve Conflicts (often pick a migrationVersion newer than LatestVersion, or set includeAll=true for intentional out-of-order), then retry planMigration/applyMigration.", {
14173
14574
  action: "applyMigration",
14174
14575
  migrationName: args.migrationName,
14175
14576
  migrationVersion: version,
14176
14577
  sql: args.sql,
14177
14578
  confirm: true,
14178
14579
  ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}),
14580
+ ...(includeAll ? { includeAll: true } : {}),
14179
14581
  }),
14180
14582
  ],
14181
14583
  });
@@ -14216,6 +14618,33 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14216
14618
  }
14217
14619
  const version = versionOrError;
14218
14620
  const localFileHint = buildLocalMigrationFileHint(version, args.migrationName);
14621
+ // Prefer write-to-workspace (or fail closed) so remote history cannot advance without a Git truth source.
14622
+ const localFile = ensureLocalMigrationSqlFile(version, args.migrationName, args.sql);
14623
+ if (!localFile.ok) {
14624
+ return buildPgToolResult({
14625
+ success: false,
14626
+ errorCode: localFile.errorCode,
14627
+ data: {
14628
+ migrationVersion: version,
14629
+ migrationName: args.migrationName,
14630
+ localFileHint,
14631
+ localFilePath: localFile.relativePath ?? localFileHint,
14632
+ localFileAbsolutePath: localFile.absolutePath ?? null,
14633
+ verified: false,
14634
+ },
14635
+ message: localFile.message,
14636
+ nextActions: [
14637
+ buildNextAction(MANAGE_PG_DATABASE, "planMigration", "After aligning the local SQL file with the sql argument (or choosing a new migrationVersion), preview again.", {
14638
+ action: "planMigration",
14639
+ migrationName: args.migrationName,
14640
+ migrationVersion: version,
14641
+ sql: args.sql,
14642
+ ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}),
14643
+ ...(args.includeAll ? { includeAll: true } : {}),
14644
+ }),
14645
+ ],
14646
+ });
14647
+ }
14219
14648
  const pending = {
14220
14649
  Version: version,
14221
14650
  Name: args.migrationName,
@@ -14224,12 +14653,14 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14224
14653
  if (args.rollbackSql?.trim()) {
14225
14654
  pending.Rollback = args.rollbackSql;
14226
14655
  }
14656
+ const includeAll = Boolean(args.includeAll);
14227
14657
  try {
14228
14658
  // Pushing only the pending migration while remote history exists makes the plan
14229
- // non-executable (remote_history_not_found_locally). Hydrate remote Queries first,
14230
- // matching CLI `tcb db pg migration up` (full list; server skips applied).
14659
+ // non-executable (remote_history_not_found_locally). Prefer local tree when complete
14660
+ // (CLI parity); otherwise hydrate remote Queries (List + N×Describe).
14231
14661
  const hydrated = await buildMigrationsPayloadWithRemoteHistory(context, pending, cloudBaseOptions);
14232
- const preview = await callPgMigrationApi(context, "PreviewPGUserMigrations", { Migrations: hydrated.migrations }, cloudBaseOptions);
14662
+ const payloadSourceLabel = formatMigrationsPayloadSourceLabel(hydrated.payloadSource, hydrated.remoteCount);
14663
+ const preview = await callPgMigrationApi(context, "PreviewPGUserMigrations", { Migrations: hydrated.migrations, IncludeAll: includeAll }, cloudBaseOptions);
14233
14664
  if (preview.Executable !== true) {
14234
14665
  return buildPgToolResult({
14235
14666
  success: false,
@@ -14240,27 +14671,33 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14240
14671
  localFileHint,
14241
14672
  hydratedRemoteCount: hydrated.remoteCount,
14242
14673
  latestRemoteVersion: hydrated.latestVersion || null,
14674
+ payloadSource: hydrated.payloadSource,
14675
+ includeAll,
14243
14676
  previewResult: preview,
14244
14677
  verified: false,
14245
14678
  },
14246
14679
  message: `PreviewPGUserMigrations reported Executable=false for migrationVersion=${version} ` +
14247
- `(latest remote=${hydrated.latestVersion || "unknown"}, hydratedRemoteCount=${hydrated.remoteCount}). ` +
14680
+ `(latest remote=${hydrated.latestVersion || "unknown"}, ${payloadSourceLabel}, includeAll=${includeAll}). ` +
14248
14681
  "Push was NOT submitted. Inspect previewResult.Conflicts — common reasons: " +
14249
- "local_migration_before_latest_remote (pick a newer 14-digit version) or checksum_mismatch. " +
14682
+ "local_migration_before_latest_remote (pick a newer 14-digit version, or set includeAll=true / CLI --include-all for intentional out-of-order) or checksum_mismatch. " +
14250
14683
  "If the schema change is urgent you may fall back to action=execute with allowDdlViaExecute=true, but that bypasses migration history.",
14251
14684
  nextActions: [
14252
14685
  buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Check LatestVersion and pick a migrationVersion strictly newer than it.", { action: "listMigrations", limit: 20 }),
14253
- buildNextAction(MANAGE_PG_DATABASE, "planMigration", "Re-run planMigration after adjusting migrationVersion/SQL.", {
14686
+ buildNextAction(MANAGE_PG_DATABASE, "planMigration", "Re-run planMigration after adjusting migrationVersion/SQL (or includeAll=true for out-of-order).", {
14254
14687
  action: "planMigration",
14255
14688
  migrationName: args.migrationName,
14256
14689
  migrationVersion: version,
14257
14690
  sql: args.sql,
14258
14691
  ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}),
14692
+ ...(includeAll ? { includeAll: true } : {}),
14259
14693
  }),
14260
14694
  ],
14261
14695
  });
14262
14696
  }
14263
- const params = { Migrations: hydrated.migrations };
14697
+ const params = {
14698
+ Migrations: hydrated.migrations,
14699
+ IncludeAll: includeAll,
14700
+ };
14264
14701
  if (args.lockTimeoutMs !== undefined) {
14265
14702
  params.LockTimeoutMs = args.lockTimeoutMs;
14266
14703
  }
@@ -14269,10 +14706,35 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14269
14706
  }
14270
14707
  const result = await callPgMigrationApi(context, "PushPGUserMigrations", params, cloudBaseOptions);
14271
14708
  const taskId = typeof result.TaskId === "string" ? result.TaskId.trim() : "";
14709
+ const pollTimeoutMs = resolveMigrationTaskPollTimeoutMs(args.taskPollTimeoutMs);
14710
+ const shouldWaitForTask = args.waitForTask !== false;
14272
14711
  let taskResult = null;
14712
+ if (taskId && !shouldWaitForTask) {
14713
+ return buildPgToolResult({
14714
+ success: false,
14715
+ errorCode: "MIGRATION_TASK_PENDING",
14716
+ data: {
14717
+ migrationVersion: version,
14718
+ migrationName: args.migrationName,
14719
+ localFileHint,
14720
+ hydratedRemoteCount: hydrated.remoteCount,
14721
+ payloadSource: hydrated.payloadSource,
14722
+ apiResult: result,
14723
+ taskResult: { TaskId: taskId },
14724
+ taskPollTimeoutMs: pollTimeoutMs,
14725
+ waitForTask: false,
14726
+ verified: null,
14727
+ },
14728
+ message: `PushPGUserMigrations accepted TaskId=${taskId} (waitForTask=false). ` +
14729
+ "TaskId means accepted, not applied. Call action=describeMigrationTask FIRST for Status/Phase/Reason, " +
14730
+ `then listMigrations to see whether migrationVersion=${version} landed. ` +
14731
+ "Do NOT re-push the same version while the task may still be running.",
14732
+ nextActions: buildMigrationTaskPendingNextActions(version, taskId),
14733
+ });
14734
+ }
14273
14735
  if (taskId) {
14274
14736
  try {
14275
- taskResult = await waitPgMigrationTask(context, taskId, cloudBaseOptions);
14737
+ taskResult = await waitPgMigrationTask(context, taskId, cloudBaseOptions, pollTimeoutMs);
14276
14738
  }
14277
14739
  catch (error) {
14278
14740
  const lastTask = error.lastTask;
@@ -14286,14 +14748,16 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14286
14748
  hydratedRemoteCount: hydrated.remoteCount,
14287
14749
  apiResult: result,
14288
14750
  taskResult: lastTask ?? { TaskId: taskId },
14751
+ taskPollTimeoutMs: pollTimeoutMs,
14289
14752
  verified: null,
14290
14753
  },
14291
- message: `PushPGUserMigrations returned TaskId=${taskId}, but waiting for DescribeTaskResult timed out: ` +
14292
- `${error instanceof Error ? error.message : String(error)}. ` +
14293
- "The migration may still be running — check action=listMigrations before retrying or falling back to execute.",
14294
- nextActions: [
14295
- buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Check whether the migrationVersion eventually landed.", { action: "listMigrations", limit: 20 }),
14296
- ],
14754
+ message: `PushPGUserMigrations returned TaskId=${taskId}, but waiting for DescribeTaskResult timed out ` +
14755
+ `after ${Math.floor(pollTimeoutMs / 1000)}s: ${error instanceof Error ? error.message : String(error)}. ` +
14756
+ "The migration may STILL be running in the background (large DDL / lock waits). " +
14757
+ "Call action=describeMigrationTask FIRST (Status/Phase/Reason), then listMigrations. " +
14758
+ "Do NOT re-push the same migrationVersion, and do NOT fall back to execute until the task is terminal " +
14759
+ "and listMigrations confirms the version is missing.",
14760
+ nextActions: buildMigrationTaskPendingNextActions(version, taskId),
14297
14761
  });
14298
14762
  }
14299
14763
  const status = String(taskResult.Status || "").toLowerCase();
@@ -14321,6 +14785,7 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14321
14785
  migrationVersion: version,
14322
14786
  sql: args.sql,
14323
14787
  ...(args.rollbackSql ? { rollbackSql: args.rollbackSql } : {}),
14788
+ ...(includeAll ? { includeAll: true } : {}),
14324
14789
  }),
14325
14790
  buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Confirm the version is still absent from remote history.", { action: "listMigrations", limit: 20 }),
14326
14791
  ],
@@ -14338,6 +14803,7 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14338
14803
  migrationName: args.migrationName,
14339
14804
  localFileHint,
14340
14805
  hydratedRemoteCount: hydrated.remoteCount,
14806
+ includeAll,
14341
14807
  apiResult: result,
14342
14808
  taskResult: taskResult,
14343
14809
  verified: false,
@@ -14378,12 +14844,18 @@ async function handleApplyMigration(args, context, deps, cloudBaseOptions) {
14378
14844
  migrationVersion: version,
14379
14845
  migrationName: args.migrationName,
14380
14846
  localFileHint,
14847
+ localFilePath: localFile.relativePath,
14848
+ localFileAbsolutePath: localFile.absolutePath,
14849
+ localFileAction: localFile.action,
14381
14850
  hydratedRemoteCount: hydrated.remoteCount,
14851
+ payloadSource: hydrated.payloadSource,
14852
+ includeAll,
14382
14853
  apiResult: result,
14383
14854
  taskResult: taskResult,
14384
14855
  verified: true,
14385
14856
  },
14386
- message: `Migrations applied via PushPGUserMigrations (hydrated ${hydrated.remoteCount} remote migration(s)), task ${taskId ? `TaskId=${taskId} ` : ""}verified present in the remote migration history. Ensure local file ${localFileHint} exists and matches.`,
14857
+ message: `Migrations applied via PushPGUserMigrations (${payloadSourceLabel}${includeAll ? ", includeAll=true" : ""}), task ${taskId ? `TaskId=${taskId} ` : ""}verified present in the remote migration history. ` +
14858
+ `Local SQL ${localFile.action === "created" ? "written" : "matched"} at ${localFile.relativePath}.`,
14387
14859
  });
14388
14860
  }
14389
14861
  catch (error) {
@@ -14444,6 +14916,156 @@ async function handleMigrationDetail(args, context, deps, cloudBaseOptions) {
14444
14916
  });
14445
14917
  }
14446
14918
  }
14919
+ /**
14920
+ * One-shot DescribeTaskResult for a PushPGUserMigrations TaskId.
14921
+ * Complements listMigrations: history shows whether a version landed; task result shows
14922
+ * Status/Phase/Reason while the job is running or after it Failed (issue #857 evidence path).
14923
+ */
14924
+ async function handleDescribeMigrationTask(args, context, _deps, cloudBaseOptions) {
14925
+ const taskId = args.taskId?.trim();
14926
+ if (!taskId) {
14927
+ return buildPgToolResult({
14928
+ success: false,
14929
+ errorCode: "TASK_ID_REQUIRED",
14930
+ message: "Provide taskId when action=describeMigrationTask " +
14931
+ "(from applyMigration Push / MIGRATION_TASK_PENDING / MIGRATION_TASK_TIMEOUT data.taskResult.TaskId).",
14932
+ });
14933
+ }
14934
+ try {
14935
+ const taskResult = (await callPgMigrationApi(context, "DescribeTaskResult", { TaskId: taskId }, cloudBaseOptions));
14936
+ const status = String(taskResult.Status || "").toLowerCase();
14937
+ const versionHint = args.migrationVersion?.trim();
14938
+ const nextActions = [];
14939
+ if (status === "succeed") {
14940
+ nextActions.push(buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Task Succeed — confirm migrationVersion is present in remote history before dependent SQL.", { action: "listMigrations", limit: 20 }));
14941
+ if (versionHint) {
14942
+ nextActions.push(buildNextAction(MANAGE_PG_DATABASE, "migrationDetail", "Inspect the applied migration record.", { action: "migrationDetail", migrationVersion: versionHint }));
14943
+ }
14944
+ }
14945
+ else if (status === "failed") {
14946
+ nextActions.push(buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Confirm the version is absent from history, then fix Conflicts/SQL and retry with a NEW migrationVersion (do not re-push the failed version blindly).", { action: "listMigrations", limit: 20 }));
14947
+ }
14948
+ else {
14949
+ nextActions.push(buildNextAction(MANAGE_PG_DATABASE, "describeMigrationTask", "Task still running — poll again shortly. Do NOT re-push the same migrationVersion.", { action: "describeMigrationTask", taskId }), buildNextAction(MANAGE_PG_DATABASE, "listMigrations", "Optionally check whether the version already landed while Status is still non-terminal.", { action: "listMigrations", limit: 20 }));
14950
+ }
14951
+ const statusLabel = taskResult.Status || "unknown";
14952
+ return buildPgToolResult({
14953
+ success: true,
14954
+ data: {
14955
+ taskId,
14956
+ taskResult: taskResult,
14957
+ terminal: status === "succeed" || status === "failed",
14958
+ ...(versionHint ? { migrationVersion: versionHint } : {}),
14959
+ },
14960
+ message: status === "succeed"
14961
+ ? `DescribeTaskResult TaskId=${taskId} Status=Succeed (Phase=${taskResult.Phase || "-"}). Verify with listMigrations before treating schema as applied.`
14962
+ : status === "failed"
14963
+ ? `DescribeTaskResult TaskId=${taskId} Status=Failed at phase=${taskResult.Phase || "-"}: ${taskResult.Reason || "unknown reason"}. Do NOT re-push the same migrationVersion until Conflicts/SQL are fixed.`
14964
+ : `DescribeTaskResult TaskId=${taskId} Status=${statusLabel} Phase=${taskResult.Phase || "-"}. Task is not terminal yet — poll again; do not re-push.`,
14965
+ nextActions,
14966
+ });
14967
+ }
14968
+ catch (error) {
14969
+ return buildPgToolResult({
14970
+ success: false,
14971
+ errorCode: "MIGRATION_API_ERROR",
14972
+ message: `DescribeTaskResult failed for TaskId=${taskId}: ${error instanceof Error ? error.message : String(error)}`,
14973
+ });
14974
+ }
14975
+ }
14976
+ /**
14977
+ * Pull remote migration history SQL into cloudbase/migrations/ (CLI `tcb db pg migration fetch`).
14978
+ * Optional migrationVersion fetches one record; omit for full history. force overwrites existing files.
14979
+ */
14980
+ async function handleFetchMigration(args, context, _deps, cloudBaseOptions) {
14981
+ const force = args.force === true;
14982
+ const singleVersion = args.migrationVersion?.trim();
14983
+ try {
14984
+ const remoteItems = [];
14985
+ if (singleVersion) {
14986
+ const detail = await callPgMigrationApi(context, "DescribePGUserMigration", { MigrationVersion: singleVersion }, cloudBaseOptions);
14987
+ const version = typeof detail.Version === "string" && detail.Version.trim()
14988
+ ? detail.Version.trim()
14989
+ : singleVersion;
14990
+ const name = typeof detail.Name === "string" && detail.Name.trim()
14991
+ ? detail.Name.trim()
14992
+ : "";
14993
+ const query = typeof detail.Query === "string" ? detail.Query : "";
14994
+ remoteItems.push({ Version: version, Name: name || version, Query: query });
14995
+ }
14996
+ else {
14997
+ const { summaries } = await listAllRemoteMigrationSummaries(context, cloudBaseOptions);
14998
+ for (const summary of summaries) {
14999
+ const detail = await callPgMigrationApi(context, "DescribePGUserMigration", { MigrationVersion: summary.Version }, cloudBaseOptions);
15000
+ const version = typeof detail.Version === "string" && detail.Version.trim()
15001
+ ? detail.Version.trim()
15002
+ : summary.Version;
15003
+ const name = typeof detail.Name === "string" && detail.Name.trim()
15004
+ ? detail.Name.trim()
15005
+ : summary.Name;
15006
+ const query = typeof detail.Query === "string" ? detail.Query : "";
15007
+ remoteItems.push({ Version: version, Name: name, Query: query });
15008
+ }
15009
+ }
15010
+ const sync = syncFetchedMigrationsToLocalFiles(remoteItems, force);
15011
+ if (!sync.ok) {
15012
+ return buildPgToolResult({
15013
+ success: false,
15014
+ errorCode: sync.errorCode,
15015
+ message: sync.message,
15016
+ data: {
15017
+ migrationsDir: LOCAL_MIGRATIONS_DIR,
15018
+ force,
15019
+ localFilePath: sync.relativePath,
15020
+ },
15021
+ });
15022
+ }
15023
+ const written = sync.files.filter((f) => f.action === "written");
15024
+ const skipped = sync.files.filter((f) => f.action === "skipped");
15025
+ return buildPgToolResult({
15026
+ success: true,
15027
+ data: {
15028
+ migrationsDir: LOCAL_MIGRATIONS_DIR,
15029
+ force,
15030
+ migrationVersion: singleVersion || null,
15031
+ total: remoteItems.length,
15032
+ writtenCount: written.length,
15033
+ skippedCount: skipped.length,
15034
+ written: written.map((f) => f.relativePath),
15035
+ skipped: skipped.map((f) => f.relativePath),
15036
+ files: sync.files.map(({ version, name, relativePath, action }) => ({
15037
+ version,
15038
+ name,
15039
+ relativePath,
15040
+ action,
15041
+ })),
15042
+ },
15043
+ message: remoteItems.length === 0
15044
+ ? "No remote migrations to fetch. Local cloudbase/migrations/ unchanged."
15045
+ : `Fetched ${remoteItems.length} remote migration(s): wrote ${written.length}, skipped ${skipped.length}` +
15046
+ (skipped.length > 0 && !force
15047
+ ? " (existing files left untouched; pass force=true to overwrite, matching CLI --force)."
15048
+ : ".") +
15049
+ ` Directory: ${LOCAL_MIGRATIONS_DIR}/.`,
15050
+ nextActions: skipped.length > 0 && !force
15051
+ ? [
15052
+ buildNextAction(MANAGE_PG_DATABASE, "fetchMigration", "Re-run with force=true to overwrite skipped local files from remote history (checksum realign).", {
15053
+ action: "fetchMigration",
15054
+ force: true,
15055
+ ...(singleVersion ? { migrationVersion: singleVersion } : {}),
15056
+ }),
15057
+ ]
15058
+ : undefined,
15059
+ });
15060
+ }
15061
+ catch (error) {
15062
+ return buildPgToolResult({
15063
+ success: false,
15064
+ errorCode: "MIGRATION_API_ERROR",
15065
+ message: `fetchMigration failed: ${error instanceof Error ? error.message : String(error)}`,
15066
+ });
15067
+ }
15068
+ }
14447
15069
  async function handleRollbackMigration(args, context, deps, cloudBaseOptions) {
14448
15070
  if (args.lastN === undefined || args.lastN < 1 || !Number.isInteger(args.lastN)) {
14449
15071
  return buildPgToolResult({
@@ -14658,11 +15280,11 @@ function registerPGDatabaseTools(server, providedDeps) {
14658
15280
  });
14659
15281
  server.registerTool?.(MANAGE_PG_DATABASE, {
14660
15282
  title: "管理 PostgreSQL 上下文或执行写入 SQL",
14661
- description: "管理 CloudBase PostgreSQL:执行已确认的写入 SQL、SQL 风险预检、迁移管理。建表/ALTER/DROP 等 schema 变更必须使用 applyMigration(显式 migrationVersion + 本地 migrations/ 留档),不要默认用 execute。execute 主要用于 DML 与 GRANT/RLS 等运维 SQL。",
15283
+ description: "管理 CloudBase PostgreSQL:执行已确认的写入 SQL、SQL 风险预检、迁移管理。建表/ALTER/DROP 等 schema 变更必须使用 applyMigration(显式 migrationVersion;成功前自动写入或校验本地 cloudbase/migrations/<version>_<name>.sql,与 CLI tcb db pg migration 一致),不要默认用 execute。execute 主要用于 DML 与 GRANT/RLS 等运维 SQL。",
14662
15284
  inputSchema: {
14663
15285
  action: zod_1.z
14664
15286
  .enum(MANAGE_ACTIONS)
14665
- .describe("操作类型:execute=执行已确认的写入 SQL(DML/GRANT/RLS;schema DDL 默认拒绝,需 allowDdlViaExecute=true);dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + migrationVersion + sql);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + migrationVersion + sql + confirm=true;成功返回前会自动校验该 migrationVersion 已落入远端迁移历史,未落库时返回 success=false 且 errorCode=MIGRATION_NOT_APPLIED);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
15287
+ .describe("操作类型:execute=执行已确认的写入 SQL(DML/GRANT/RLS;schema DDL 默认拒绝,需 allowDdlViaExecute=true);dryRun=只分析 SQL 风险不执行;planMigration=预览迁移计划(需 migrationName + migrationVersion + sql;可选 includeAll=true 允许乱序,对齐 CLI --include-all);applyMigration=应用迁移,建表/改 schema 首选(需 migrationName + migrationVersion + sql + confirm=true;可选 includeAll;本地 SQL 缺失则自动写入 cloudbase/migrations/,内容不一致则 LOCAL_MIGRATION_FILE_MISMATCH fail-closed;成功返回前会轮询 DescribeTaskResult(默认最长 10 分钟,可用 taskPollTimeoutMs / waitForTask 调整)并校验 migrationVersion 已落入远端历史;超时返回 MIGRATION_TASK_TIMEOUT,必须先 describeMigrationTask 再 listMigrations,禁止立刻重推同 version;未落库时返回 success=false 且 errorCode=MIGRATION_NOT_APPLIED);listMigrations=查询已应用的 Migration 列表(可传 limit/offset 分页);migrationDetail=查看单条 Migration 详情(需 migrationVersion);describeMigrationTask=按 TaskId 查询 Push 异步任务状态(DescribeTaskResult:Status/Phase/Reason;需 taskId;用于 waitForTask=false / MIGRATION_TASK_TIMEOUT / 失败诊断,listMigrations 看不到 Reason);fetchMigration=从远端 history 拉取 SQL 写入本地 cloudbase/migrations/(对齐 CLI tcb db pg migration fetch;可选 migrationVersion 拉单条,省略则全量;force=true 覆盖已存在文件,默认跳过);rollbackMigration=回滚最近 N 条 Migration(需 lastN + confirm=true);repairMigration=修复 Migration 历史记录(需 migrationVersion + migrationName + repairStatus + repairReason)"),
14666
15288
  sql: zod_1.z
14667
15289
  .string()
14668
15290
  .optional()
@@ -14700,7 +15322,7 @@ function registerPGDatabaseTools(server, providedDeps) {
14700
15322
  .string()
14701
15323
  .regex(/^\d{14}$/)
14702
15324
  .optional()
14703
- .describe("14 位时间戳 YYYYMMDDHHMMSS。plan/apply/detail/repair 必填;禁止由服务端静默生成,避免与本地 migrations/<version>_<name>.sql 分叉。"),
15325
+ .describe("14 位时间戳 YYYYMMDDHHMMSS。plan/apply/detail/repair 必填;fetchMigration 可选(传入则只拉该条,省略则拉全量远端 history);禁止由服务端静默生成,避免与本地 cloudbase/migrations/<version>_<name>.sql 分叉。"),
14704
15326
  rollbackSql: zod_1.z
14705
15327
  .string()
14706
15328
  .optional()
@@ -14734,6 +15356,21 @@ function registerPGDatabaseTools(server, providedDeps) {
14734
15356
  .int()
14735
15357
  .optional()
14736
15358
  .describe("apply 可选:单条 SQL 执行最长时间(毫秒),默认 300000。"),
15359
+ taskPollTimeoutMs: zod_1.z
15360
+ .number()
15361
+ .int()
15362
+ .min(MIGRATION_TASK_MIN_WAIT_MS)
15363
+ .max(MIGRATION_TASK_MAX_WAIT_MS)
15364
+ .optional()
15365
+ .describe(`apply 可选:轮询 DescribeTaskResult 的最长等待(毫秒)。默认 ${MIGRATION_TASK_DEFAULT_WAIT_MS}(与 CLI tcb db pg migration up 的 10 分钟对齐)。范围 ${MIGRATION_TASK_MIN_WAIT_MS}-${MIGRATION_TASK_MAX_WAIT_MS}。超时后务必先 describeMigrationTask(taskId) 再 listMigrations,禁止立刻重推同 version。`),
15366
+ waitForTask: zod_1.z
15367
+ .boolean()
15368
+ .optional()
15369
+ .describe("apply 可选,默认 true。设为 false 时 Push 后立即返回 TaskId(errorCode=MIGRATION_TASK_PENDING),由调用方用 describeMigrationTask 轮询任务终态,再用 listMigrations 确认是否落库;适合 MCP host 工具调用超时较短的场景。默认 true 会同步等到任务终态。"),
15370
+ taskId: zod_1.z
15371
+ .string()
15372
+ .optional()
15373
+ .describe("describeMigrationTask 必填:PushPGUserMigrations / applyMigration 返回的 TaskId。用于一次性查询 DescribeTaskResult(Status/Phase/Reason),不轮询等待。"),
14737
15374
  repairStatus: zod_1.z
14738
15375
  .enum(["applied", "reverted"])
14739
15376
  .optional()
@@ -14742,6 +15379,14 @@ function registerPGDatabaseTools(server, providedDeps) {
14742
15379
  .string()
14743
15380
  .optional()
14744
15381
  .describe("repair 必填:修复原因。"),
15382
+ force: zod_1.z
15383
+ .boolean()
15384
+ .optional()
15385
+ .describe("fetchMigration 可选,默认 false。true=覆盖本地已存在的同名 SQL 文件(对齐 CLI tcb db pg migration fetch --force);false=跳过已存在文件。用于从远端 history 重新对齐 Git checksum。"),
15386
+ includeAll: zod_1.z
15387
+ .boolean()
15388
+ .optional()
15389
+ .describe("planMigration / applyMigration 可选,默认 false。true=允许 out-of-order(version 小于远端 LatestVersion)仍可 Preview/Push,对齐 CLI tcb db pg migration up --include-all;仅在确认要补历史/乱序迁移时使用,日常应选更大的 migrationVersion。"),
14745
15390
  allowDdlViaExecute: zod_1.z
14746
15391
  .boolean()
14747
15392
  .optional()
@@ -14789,6 +15434,8 @@ function registerPGDatabaseTools(server, providedDeps) {
14789
15434
  case "applyMigration":
14790
15435
  case "listMigrations":
14791
15436
  case "migrationDetail":
15437
+ case "describeMigrationTask":
15438
+ case "fetchMigration":
14792
15439
  case "rollbackMigration":
14793
15440
  case "repairMigration": {
14794
15441
  switch (args.action) {
@@ -14796,6 +15443,8 @@ function registerPGDatabaseTools(server, providedDeps) {
14796
15443
  case "applyMigration": return handleApplyMigration(args, context, deps, cbOpts);
14797
15444
  case "listMigrations": return handleListMigrations(args, context, deps, cbOpts);
14798
15445
  case "migrationDetail": return handleMigrationDetail(args, context, deps, cbOpts);
15446
+ case "describeMigrationTask": return handleDescribeMigrationTask(args, context, deps, cbOpts);
15447
+ case "fetchMigration": return handleFetchMigration(args, context, deps, cbOpts);
14799
15448
  case "rollbackMigration": return handleRollbackMigration(args, context, deps, cbOpts);
14800
15449
  case "repairMigration": return handleRepairMigration(args, context, deps, cbOpts);
14801
15450
  default: return buildPgToolResult({ success: false, errorCode: "UNSUPPORTED_ACTION", message: `Unsupported action: ${args.action}` });
@@ -14848,7 +15497,7 @@ function buildWriteVerificationHint(resourceId) {
14848
15497
  return `对于 ${resourceId} 这类有后端权限控制的集合,前端调用 .doc(id).update() / .doc(id).remove() 后,不能只看是否没有抛异常。请显式检查返回结果中的 updated / deleted 是否大于 0;如果 result.code、result.message 存在,或 updated / deleted 为 0,要把它当作真实失败并向上抛错。`;
14849
15498
  }
14850
15499
  function buildPermissionPropagationHint(resourceId) {
14851
- return `刚更新完 ${resourceId} 的安全规则时,后端权限可能需要一小段传播时间。若紧接着的真实写操作仍返回 DATABASE_PERMISSION_DENIED,请先等待一小段时间,再用同一登录态重试同一条 .doc(id).update() / .doc(id).remove();不要立刻连续重写规则,也不要在传播窗口里把旧拒绝直接当成规则表达式仍然错误。`;
15500
+ return `刚更新完 ${resourceId} 的安全规则时,后端权限通常在数秒到约 30 秒内生效。若紧接着的真实写操作仍返回 DATABASE_PERMISSION_DENIED,请先间隔数秒用同一登录态重试同一条 .doc(id).update() / .doc(id).remove();不要盲等数分钟,也不要立刻连续重写规则,更不要在短暂传播窗口里把旧拒绝直接当成规则表达式仍然错误。`;
14852
15501
  }
14853
15502
  function buildEnvelope(data, message) {
14854
15503
  return {
@@ -14873,6 +15522,171 @@ function mapResourceType(resourceType) {
14873
15522
  };
14874
15523
  return resourceTypeMap[resourceType];
14875
15524
  }
15525
+ /**
15526
+ * Platform ModifyResourcePermission / DescribeResourcePermission reject PG envs.
15527
+ * CLI migrated `tcb permission` → `tcb policy` (OPA Rego via
15528
+ * permission.modifyEnvAuthzConfig / describeEnvAuthzConfig).
15529
+ */
15530
+ function isPostgresqlPermissionApiUnsupported(error) {
15531
+ const message = error instanceof Error ? error.message : String(error);
15532
+ return /does not support PostgreSQL type environments/i.test(message);
15533
+ }
15534
+ const AUTHZ_USER_REGO_KEY = "authz.user.rego";
15535
+ function looksLikeUserRego(value) {
15536
+ return /^\s*package\s+authz\.user\b/m.test(value);
15537
+ }
15538
+ /** Detect legacy function securityRule shapes that mean "public invoke". */
15539
+ function isPublicFunctionInvokeRule(securityRule) {
15540
+ if (!securityRule || securityRule.trim() === "") {
15541
+ return false;
15542
+ }
15543
+ const trimmed = securityRule.trim();
15544
+ if (trimmed === "true") {
15545
+ return true;
15546
+ }
15547
+ try {
15548
+ const parsed = JSON.parse(trimmed);
15549
+ if (parsed === true || parsed === "true") {
15550
+ return true;
15551
+ }
15552
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15553
+ return false;
15554
+ }
15555
+ const obj = parsed;
15556
+ if (obj.invoke === true || obj.invoke === "true") {
15557
+ return true;
15558
+ }
15559
+ for (const value of Object.values(obj)) {
15560
+ if (value &&
15561
+ typeof value === "object" &&
15562
+ !Array.isArray(value) &&
15563
+ (value.invoke === true ||
15564
+ value.invoke === "true")) {
15565
+ return true;
15566
+ }
15567
+ }
15568
+ }
15569
+ catch {
15570
+ return false;
15571
+ }
15572
+ return false;
15573
+ }
15574
+ /**
15575
+ * Build a CLI-aligned user Rego that opens cloud-function access for
15576
+ * anonymous + unauthenticated callers (tcb policy set example + HTTP no-token).
15577
+ */
15578
+ function buildPublicFunctionsUserRego(resourceId) {
15579
+ const comment = resourceId && resourceId !== "*"
15580
+ ? `# Public HTTP/API access for function ${resourceId} (aligned with tcb policy set)`
15581
+ : `# Public HTTP/API access for cloud functions (aligned with tcb policy set)`;
15582
+ return [
15583
+ "package authz.user",
15584
+ "",
15585
+ "default allow := false",
15586
+ "",
15587
+ comment,
15588
+ "allow if {",
15589
+ ' input.cloudbase.resource_type == "functions"',
15590
+ ' input.subject.auth_type in {"anonymous", "unauthenticated"}',
15591
+ "}",
15592
+ "",
15593
+ ].join("\n");
15594
+ }
15595
+ function resolveFunctionAuthzRegoInput(securityRule, resourceId) {
15596
+ if (securityRule && looksLikeUserRego(securityRule)) {
15597
+ return securityRule;
15598
+ }
15599
+ if (isPublicFunctionInvokeRule(securityRule)) {
15600
+ return buildPublicFunctionsUserRego(resourceId);
15601
+ }
15602
+ throw new Error(`PostgreSQL environments manage HTTP/function gateway auth via OPA Rego ` +
15603
+ `(same as CLI \`tcb policy set\`), not ModifyResourcePermission / function security-rule JSON. ` +
15604
+ `Pass either:\n` +
15605
+ `1) permission="CUSTOM" with securityRule as a full Rego document starting with \`package authz.user\`, or\n` +
15606
+ `2) permission="CUSTOM" with securityRule='{"invoke":true}' to generate a public-functions allow policy.\n` +
15607
+ `See https://docs.cloudbase.net/cli-v1/policy/management`);
15608
+ }
15609
+ async function describeEnvAuthzUserRego(cloudbase) {
15610
+ if (!cloudbase?.permission?.describeEnvAuthzConfig) {
15611
+ throw new Error("Current @cloudbase/manager-node does not expose permission.describeEnvAuthzConfig. Upgrade manager-node (>= 5.5.5) to align with CLI tcb policy get.");
15612
+ }
15613
+ const result = await cloudbase.permission.describeEnvAuthzConfig({
15614
+ key: AUTHZ_USER_REGO_KEY,
15615
+ });
15616
+ const value = typeof result?.Item?.Value === "string"
15617
+ ? result.Item.Value
15618
+ : typeof result?.Value === "string"
15619
+ ? result.Value
15620
+ : "";
15621
+ return { value, raw: result };
15622
+ }
15623
+ async function modifyEnvAuthzUserRego(cloudbase, value) {
15624
+ if (!cloudbase?.permission?.modifyEnvAuthzConfig) {
15625
+ throw new Error("Current @cloudbase/manager-node does not expose permission.modifyEnvAuthzConfig. Upgrade manager-node (>= 5.5.5) to align with CLI tcb policy set.");
15626
+ }
15627
+ return cloudbase.permission.modifyEnvAuthzConfig({
15628
+ key: AUTHZ_USER_REGO_KEY,
15629
+ value,
15630
+ });
15631
+ }
15632
+ async function describeResourcePermissionWithFunctionPgFallback(options) {
15633
+ const { cloudbase, envId, resourceType, resources } = options;
15634
+ try {
15635
+ const result = await cloudbase.permission.describeResourcePermission({
15636
+ resourceType: mapResourceType(resourceType),
15637
+ resources,
15638
+ });
15639
+ return result;
15640
+ }
15641
+ catch (error) {
15642
+ if (resourceType !== "function" || !isPostgresqlPermissionApiUnsupported(error)) {
15643
+ throw error;
15644
+ }
15645
+ const fallback = await describeEnvAuthzUserRego(cloudbase);
15646
+ const targets = resources && resources.length > 0 ? resources : ["*"];
15647
+ const permissionList = targets.map((resourceId) => ({
15648
+ ResourceType: "function",
15649
+ Resource: resourceId,
15650
+ Permission: "CUSTOM",
15651
+ SecurityRule: fallback.value || "",
15652
+ }));
15653
+ return {
15654
+ Data: {
15655
+ TotalCount: permissionList.length,
15656
+ PermissionList: permissionList,
15657
+ },
15658
+ RequestId: fallback.raw?.RequestId,
15659
+ fallback: "describeEnvAuthzConfig",
15660
+ raw: fallback.raw,
15661
+ };
15662
+ }
15663
+ }
15664
+ async function modifyFunctionPermissionWithPgFallback(options) {
15665
+ const { cloudbase, resourceId, permission, securityRule } = options;
15666
+ try {
15667
+ const result = await cloudbase.permission.modifyResourcePermission({
15668
+ resourceType: "function",
15669
+ resource: resourceId,
15670
+ permission,
15671
+ securityRule,
15672
+ });
15673
+ return { result };
15674
+ }
15675
+ catch (error) {
15676
+ if (!isPostgresqlPermissionApiUnsupported(error)) {
15677
+ throw error;
15678
+ }
15679
+ if (permission !== "CUSTOM") {
15680
+ throw new Error(`PostgreSQL environments do not support ModifyResourcePermission. ` +
15681
+ `Align with CLI \`tcb policy set\`: use permission="CUSTOM" and pass OPA Rego ` +
15682
+ `(package authz.user) or securityRule='{"invoke":true}' for public functions. ` +
15683
+ `Underlying error: ${error instanceof Error ? error.message : String(error)}`);
15684
+ }
15685
+ const rego = resolveFunctionAuthzRegoInput(securityRule, resourceId);
15686
+ const result = await modifyEnvAuthzUserRego(cloudbase, rego);
15687
+ return { result, fallback: "modifyEnvAuthzConfig", rego };
15688
+ }
15689
+ }
14876
15690
  function normalizeRecordArray(value, label) {
14877
15691
  if (value === undefined) {
14878
15692
  return undefined;
@@ -15048,7 +15862,7 @@ function registerPermissionTools(server) {
15048
15862
  };
15049
15863
  server.registerTool?.("queryPermissions", {
15050
15864
  title: "查询 CloudBase 权限与用户配置",
15051
- description: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`。`resourceType=\"noSqlDatabase\"` 查询的是 CloudBase NoSQL 集合规则,与 CloudBase PostgreSQL(PG)表的行级安全(RLS)是两套独立机制——同一个 PG 环境里 NoSQL 集合若仍在使用,对那些集合查询本工具结果**仍然有效**。要查 PG 表 RLS,请改用 `queryPgDatabase(action=\"sql\", sql=\"SELECT * FROM pg_policies WHERE tablename=...\")`。本工具不涉及 MySQL 权限。",
15865
+ description: "查询 CloudBase 权限与用户配置,支持查询资源权限(数据库/云函数/存储桶等)、角色列表/详情、应用用户列表/详情。\n\n示例:\n- 查询存储桶权限:`action=\"getResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\"`\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`。`resourceType=\"noSqlDatabase\"` 查询的是 CloudBase NoSQL 集合规则,与 CloudBase PostgreSQL(PG)表的行级安全(RLS)是两套独立机制——同一个 PG 环境里 NoSQL 集合若仍在使用,对那些集合查询本工具结果**仍然有效**。要查 PG 表 RLS,请改用 `queryPgDatabase(action=\"sql\", sql=\"SELECT * FROM pg_policies WHERE tablename=...\")`。本工具不涉及 MySQL 权限。\n\n⚠️ PostgreSQL 环境:平台 `DescribeResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `describeEnvAuthzConfig`(与 CLI `tcb policy get` 一致,读取 `authz.user.rego`)。",
15052
15866
  inputSchema: {
15053
15867
  action: zod_1.z.enum(QUERY_PERMISSION_ACTIONS),
15054
15868
  resourceType: zod_1.z
@@ -15080,8 +15894,10 @@ function registerPermissionTools(server) {
15080
15894
  if (resourceType === "storage") {
15081
15895
  await ensureStorageBucketsExist(cloudbase, [resourceId]);
15082
15896
  }
15083
- const result = await cloudbase.permission.describeResourcePermission({
15084
- resourceType: mapResourceType(resourceType),
15897
+ const result = await describeResourcePermissionWithFunctionPgFallback({
15898
+ cloudbase,
15899
+ envId,
15900
+ resourceType,
15085
15901
  resources: [resourceId],
15086
15902
  });
15087
15903
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
@@ -15097,8 +15913,11 @@ function registerPermissionTools(server) {
15097
15913
  aclTag: matchedPermission?.Permission,
15098
15914
  permissions,
15099
15915
  hints,
15100
- raw: result,
15101
- }, "资源权限查询成功");
15916
+ ...(result.fallback ? { fallback: result.fallback } : {}),
15917
+ raw: result.raw ?? result,
15918
+ }, result.fallback
15919
+ ? "资源权限查询成功(PostgreSQL 环境已回退到 describeEnvAuthzConfig / tcb policy get)"
15920
+ : "资源权限查询成功");
15102
15921
  }
15103
15922
  case "listResourcePermissions": {
15104
15923
  if (!resourceType) {
@@ -15107,8 +15926,10 @@ function registerPermissionTools(server) {
15107
15926
  if (resourceType === "storage" && resourceIds?.length) {
15108
15927
  await ensureStorageBucketsExist(cloudbase, resourceIds);
15109
15928
  }
15110
- const result = await cloudbase.permission.describeResourcePermission({
15111
- resourceType: mapResourceType(resourceType),
15929
+ const result = await describeResourcePermissionWithFunctionPgFallback({
15930
+ cloudbase,
15931
+ envId,
15932
+ resourceType,
15112
15933
  resources: resourceIds,
15113
15934
  });
15114
15935
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
@@ -15129,8 +15950,11 @@ function registerPermissionTools(server) {
15129
15950
  permissions,
15130
15951
  resourceHints,
15131
15952
  total: result.Data.TotalCount ?? 0,
15132
- raw: result,
15133
- }, "资源权限列表查询成功");
15953
+ ...(result.fallback ? { fallback: result.fallback } : {}),
15954
+ raw: result.raw ?? result,
15955
+ }, result.fallback
15956
+ ? "资源权限列表查询成功(PostgreSQL 环境已回退到 describeEnvAuthzConfig / tcb policy get)"
15957
+ : "资源权限列表查询成功");
15134
15958
  }
15135
15959
  case "listRoles": {
15136
15960
  const result = await cloudbase.permission.describeRoleList({
@@ -15209,7 +16033,7 @@ function registerPermissionTools(server) {
15209
16033
  }));
15210
16034
  server.registerTool?.("managePermissions", {
15211
16035
  title: "管理 CloudBase 权限与用户配置",
15212
- description: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n\n注意:`createUser` / `updateUser` 是环境侧应用用户管理能力,适合测试账号、管理员或预置用户,不应替代浏览器里的 Web SDK 注册表单;前端用户名密码注册应使用 `auth.signUp({ username, password })`,登录应使用 `auth.signInWithPassword({ username, password })`。直接在浏览器里用 `auth.signUp` 创建用户名密码用户取决于 SDK/provider 支持,使用前必须验证;不支持时应走后端或管理端边界,不能在浏览器暴露密钥。`securityRule` 的详细语义取决于 `resourceType`:`doc._openid`、`auth.openid`、查询条件子集校验,以及 `create` / `update` / `delete` JSON 模板仅适用于 `resourceType=\"noSqlDatabase\"` 的文档数据库安全规则;配置 `function` 或 `storage` 时,请参考各自官方安全规则文档,而不是复用 NoSQL 模板。\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`:\n- `resourceType=\"noSqlDatabase\"` 仅作用于 CloudBase NoSQL 文档数据库的集合;CloudBase PostgreSQL(PG)表的行级权限**不**受它控制——PG 表请改用 RLS:`managePgDatabase(action=\"execute\", confirm=true)` 跑 `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` 与 `CREATE POLICY ...`。同一个 PG 环境里如果还有 NoSQL 集合在用,对那些**集合**继续使用 `noSqlDatabase` 规则是正确的——不是\"PG 环境就禁用本工具\"。\n- `resourceType=\"storage\"` 控制的是 NoSQL/COS 存储桶 ACL;PG 的 `pgstore` bucket 不在此 `resourceType` 覆盖范围内。\n- 本工具不涉及 MySQL;MySQL 数据库权限请走 MySQL 自身的 GRANT/REVOKE 语句(通过 `manageMysqlDatabase`)。",
16036
+ description: "管理 CloudBase 权限与用户配置,支持修改资源权限(数据库/云函数/存储桶等)、角色管理、成员与策略增删、应用用户 CRUD。\n\n示例:\n- 设置存储桶为私有:`action=\"updateResourcePermission\", resourceType=\"storage\", resourceId=\"bucket-name\", permission=\"PRIVATE\"`\n- 创建角色:`action=\"createRole\", roleName=\"admin\", roleIdentity=\"admin\"`\n- 放开云函数匿名/未登录访问(PG 会走 OPA,对齐 CLI `tcb policy set`):`action=\"updateResourcePermission\", resourceType=\"function\", resourceId=\"myFn\", permission=\"CUSTOM\", securityRule='{\"invoke\":true}'`\n\n注意:`createUser` / `updateUser` 是环境侧应用用户管理能力,适合测试账号、管理员或预置用户,不应替代浏览器里的 Web SDK 注册表单;前端用户名密码注册应使用 `auth.signUp({ username, password })`,登录应使用 `auth.signInWithPassword({ username, password })`。直接在浏览器里用 `auth.signUp` 创建用户名密码用户取决于 SDK/provider 支持,使用前必须验证;不支持时应走后端或管理端边界,不能在浏览器暴露密钥。`securityRule` 的详细语义取决于 `resourceType`:`doc._openid`、`auth.openid`、查询条件子集校验,以及 `create` / `update` / `delete` JSON 模板仅适用于 `resourceType=\"noSqlDatabase\"` 的文档数据库安全规则;配置 `function` 或 `storage` 时,请参考各自官方安全规则文档,而不是复用 NoSQL 模板。\n\n📌 跨后端边界提示:调用前先用 `envQuery(action=\"info\", envId=...)` 看 `EnvInfo.RuntimeBackends`:\n- `resourceType=\"noSqlDatabase\"` 仅作用于 CloudBase NoSQL 文档数据库的集合;CloudBase PostgreSQL(PG)表的行级权限**不**受它控制——PG 表请改用 RLS:`managePgDatabase(action=\"execute\", confirm=true)` 跑 `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` 与 `CREATE POLICY ...`。同一个 PG 环境里如果还有 NoSQL 集合在用,对那些**集合**继续使用 `noSqlDatabase` 规则是正确的——不是\"PG 环境就禁用本工具\"。\n- `resourceType=\"storage\"` 控制的是 NoSQL/COS 存储桶 ACL;PG 的 `pgstore` bucket 不在此 `resourceType` 覆盖范围内。\n- 本工具不涉及 MySQL;MySQL 数据库权限请走 MySQL 自身的 GRANT/REVOKE 语句(通过 `manageMysqlDatabase`)。\n\n⚠️ PostgreSQL 环境:平台 `ModifyResourcePermission` 对 PG 环境会直接拒绝。当 `resourceType=\"function\"` 时,本工具会自动回退到 Manager SDK `modifyEnvAuthzConfig`(与 CLI `tcb policy set` 一致,写入 `authz.user.rego`)。`securityRule` 可传完整 Rego(`package authz.user`)或 `'{\"invoke\":true}'`(自动生成放通 anonymous/unauthenticated 调 functions 的策略)。设置 Rego 后旧网关鉴权会失效,行为与 CLI 相同。",
15213
16037
  inputSchema: {
15214
16038
  action: zod_1.z.enum(MANAGE_PERMISSION_ACTIONS),
15215
16039
  resourceType: zod_1.z
@@ -15257,12 +16081,29 @@ function registerPermissionTools(server) {
15257
16081
  if (!resourceType || !resourceId || !permission) {
15258
16082
  throw new Error("action=updateResourcePermission 时必须提供 resourceType、resourceId 和 permission");
15259
16083
  }
15260
- const result = await cloudbase.permission.modifyResourcePermission({
15261
- resourceType: mapResourceType(resourceType),
15262
- resource: resourceId,
15263
- permission,
15264
- securityRule,
15265
- });
16084
+ let result;
16085
+ let fallback;
16086
+ let appliedRego;
16087
+ if (resourceType === "function") {
16088
+ const updated = await modifyFunctionPermissionWithPgFallback({
16089
+ cloudbase,
16090
+ envId,
16091
+ resourceId,
16092
+ permission,
16093
+ securityRule,
16094
+ });
16095
+ result = updated.result;
16096
+ fallback = updated.fallback;
16097
+ appliedRego = updated.rego;
16098
+ }
16099
+ else {
16100
+ result = await cloudbase.permission.modifyResourcePermission({
16101
+ resourceType: mapResourceType(resourceType),
16102
+ resource: resourceId,
16103
+ permission,
16104
+ securityRule,
16105
+ });
16106
+ }
15266
16107
  (0, cloudbase_manager_js_1.logCloudBaseResult)(server.logger, result);
15267
16108
  const hints = permission === "CUSTOM" ? buildPermissionHints(securityRule, resourceId) : [];
15268
16109
  return buildEnvelope({
@@ -15272,6 +16113,8 @@ function registerPermissionTools(server) {
15272
16113
  resourceId,
15273
16114
  permission,
15274
16115
  hints,
16116
+ ...(fallback ? { fallback } : {}),
16117
+ ...(appliedRego ? { rego: appliedRego } : {}),
15275
16118
  verificationHint: resourceType === "noSqlDatabase" && permission === "CUSTOM"
15276
16119
  ? buildWriteVerificationHint(resourceId)
15277
16120
  : undefined,
@@ -15279,7 +16122,9 @@ function registerPermissionTools(server) {
15279
16122
  ? buildPermissionPropagationHint(resourceId)
15280
16123
  : undefined,
15281
16124
  raw: result,
15282
- }, "资源权限更新成功");
16125
+ }, fallback
16126
+ ? "资源权限更新成功(PostgreSQL 环境已回退到 modifyEnvAuthzConfig / tcb policy set)"
16127
+ : "资源权限更新成功");
15283
16128
  }
15284
16129
  case "createRole": {
15285
16130
  if (!roleName || !roleIdentity) {
@@ -15898,7 +16743,7 @@ function registerSetupTools(server) {
15898
16743
  server.registerTool("downloadTemplate", {
15899
16744
  title: "下载项目模板",
15900
16745
  description: `自动下载并部署CloudBase项目模板。
15901
- **Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.25.5" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
16746
+ **Note**: Call this tool when the user requests to create a new project using a CloudBase template.\n\n支持的模板:\n- react: React + CloudBase 全栈应用模板\n- vue: Vue + CloudBase 全栈应用模板\n- miniprogram: 微信小程序 + 云开发模板 \n- uniapp: UniApp + CloudBase 跨端应用模板\n- rules: 只包含AI编辑器配置文件(包含Cursor、WindSurf、CodeBuddy等所有主流编辑器配置),适合在已有项目中补充AI编辑器配置\n\n支持的IDE类型:\n- all: 下载所有IDE配置\n- cursor: Cursor AI编辑器\n- 其他IDE类型见下方列表\n\n注意:如果未传入 ide 参数且无法从环境变量检测到 IDE,将提示错误并要求传入 ide 参数\n- windsurf: WindSurf AI编辑器\n- codebuddy: CodeBuddy AI编辑器\n- claude-code: Claude Code AI编辑器\n- cline: Cline AI编辑器\n- gemini-cli: Gemini CLI\n- opencode: OpenCode AI编辑器\n- qwen-code: 通义灵码\n- baidu-comate: 百度Comate\n- openai-codex-cli: OpenAI Codex CLI\n- augment-code: Augment Code\n- github-copilot: GitHub Copilot\n- roocode: RooCode AI编辑器\n- tongyi-lingma: 通义灵码\n- trae: Trae AI编辑器\n- qoder: Qoder AI编辑器\n- antigravity: Google Antigravity AI编辑器\n- vscode: Visual Studio Code\n- kiro: Kiro AI编辑器\n- aider: Aider AI编辑器\n\n特别说明:\n- rules 模板会自动包含当前 mcp 版本号信息(版本号:${ true ? "2.25.7" : 0}),便于后续维护和版本追踪\n- 下载 rules 模板时,如果项目中已存在 README.md 文件,系统会自动保护该文件不被覆盖(除非设置 overwrite=true)`,
15902
16747
  inputSchema: {
15903
16748
  template: zod_1.z
15904
16749
  .enum(["react", "vue", "miniprogram", "uniapp", "rules"])