rainskills 0.1.14 → 0.1.16
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/README.md +1 -1
- package/SKILL.md +3 -1
- package/bin/rainskills-tools.js +204 -10
- package/bin/rainskills.js +39 -3
- package/install.sh +5 -0
- package/marketplace/rainskills/.claude-plugin/plugin.json +1 -1
- package/marketplace/rainskills/.codex-plugin/plugin.json +1 -1
- package/marketplace/rainskills/skills/rainskills/SKILL.md +4 -2
- package/package.json +3 -2
- package/rainbond-app-assistant/SKILL.md +16 -10
- package/rainbond-app-version-assistant/SKILL.md +6 -2
- package/rainbond-delivery-verifier/SKILL.md +6 -2
- package/rainbond-env-sync/SKILL.md +6 -2
- package/rainbond-fullstack-bootstrap/SKILL.md +6 -2
- package/rainbond-fullstack-bootstrap/modules/30-creation-rules.md +7 -1
- package/rainbond-fullstack-bootstrap/modules/40-source-and-package-rules.md +1 -1
- package/rainbond-fullstack-troubleshooter/SKILL.md +6 -2
- package/rainbond-opensource-app-deploy/SKILL.md +6 -2
- package/rainbond-platform-installer/SKILL.md +7 -7
- package/rainbond-platform-installer/references/installation-policy.md +2 -1
- package/rainbond-platform-installer/scripts/host-cluster-installer.js +135 -49
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-installer/scripts/platform-installer.js +42 -4
- package/rainbond-platform-installer/scripts/runtime-context-resolver.js +184 -0
- package/rainbond-platform-installer/scripts/runtime-operations.js +238 -27
- package/rainbond-platform-installer/scripts/windows-onboarding.js +8 -0
- package/rainbond-platform-query/SKILL.md +17 -13
- package/rainbond-project-init/SKILL.md +6 -2
- package/rainbond-template-installer/SKILL.md +6 -2
- package/scripts/build-skill-manifest.mjs +13 -3
package/README.md
CHANGED
|
@@ -228,7 +228,7 @@ node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-
|
|
|
228
228
|
|
|
229
229
|
Rainskills 会在用户下一次发起业务动作时,由本地运行时立即返回环境查询结果,并另行启动后台任务静默检查更新。更新只跟随 npm `latest` 指向的正式版。当前版本是 RC 或其他预发布版本时不会查询、不会自动升级;npm 上的新 RC 版本也不参与正式版自动升级。
|
|
230
230
|
|
|
231
|
-
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.
|
|
231
|
+
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.16`,不会执行浮动的 `@latest` 业务代码。新版本原子刷新已经安装的 Rainskills Skills;当前业务继续使用启动时已经加载的版本,最迟从下一条新任务开始使用新版。npm 超时、版本检查失败、安装位置不安全或文件迁移失败时会保留旧版本,且不会阻塞或改变当前操作。
|
|
232
232
|
|
|
233
233
|
升级只更新 Rainskills 自身,不触发 Rainbond 安装、运行环境选择、登录授权或重新对接,也不会新增 Agent MCP 配置。更新内容仅包括 Skills 和本地 CLI。原始业务操作会继续执行;只有该业务操作本身需要运行环境时,才按既有门禁检查当前连接。可用连接直接复用,401 只重新授权一次,403 立即停止,从未连接过运行环境时才进入环境选择。
|
|
234
234
|
|
package/SKILL.md
CHANGED
|
@@ -42,7 +42,9 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
42
42
|
|
|
43
43
|
受限沙箱(包括 Codex)执行本地状态命令时,必须申请用户级受保护目录访问权限;在 Codex 中使用 `require_escalated`。不得修改 `~/.rainbond` 权限、复制受保护状态到工作区,或因沙箱权限错误建议重装。
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
涉及浏览器或设备授权的 `runtime connect`,以及恢复/安装场景中的 `rainskills <target> --self-hosted`,必须在附加交互终端(TTY)中运行;在 Codex 中设置 `tty: true` 并保持进程附着直到授权完成。禁止通过非交互命令要求用户粘贴 JWT;非交互模式只可复用已存在的受保护凭据。
|
|
46
|
+
|
|
47
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.16`)。
|
|
46
48
|
|
|
47
49
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
48
50
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/bin/rainskills-tools.js
CHANGED
|
@@ -54,6 +54,12 @@ const PLATFORM_QUERY_TO_RESOURCE = Object.freeze({
|
|
|
54
54
|
rainbond_get_team_apps: "team-apps",
|
|
55
55
|
rainbond_query_components: "components",
|
|
56
56
|
});
|
|
57
|
+
const ENTERPRISE_SCOPED_PLATFORM_QUERY_TOOLS = new Set([
|
|
58
|
+
"rainbond_query_teams",
|
|
59
|
+
"rainbond_query_regions",
|
|
60
|
+
"rainbond_query_apps",
|
|
61
|
+
"rainbond_query_components",
|
|
62
|
+
]);
|
|
57
63
|
|
|
58
64
|
const EXIT = Object.freeze({
|
|
59
65
|
USAGE: 2,
|
|
@@ -195,6 +201,7 @@ function loadOperationConfig({ homeDir, operationId, includeRuntime }) {
|
|
|
195
201
|
environmentId: environment.id,
|
|
196
202
|
intent: operation.intent,
|
|
197
203
|
requiredSkillId,
|
|
204
|
+
operationStore,
|
|
198
205
|
...(includeRuntime ? {
|
|
199
206
|
homeDir,
|
|
200
207
|
allowInsecureHttp: parsed.protocol === "http:",
|
|
@@ -325,20 +332,51 @@ function parseCommand(args) {
|
|
|
325
332
|
throw new BridgeError("every tools command requires --operation-id <uuid>", EXIT.USAGE);
|
|
326
333
|
}
|
|
327
334
|
const operationId = args[operationIndex + 1];
|
|
328
|
-
const
|
|
335
|
+
const operationArgs = [
|
|
329
336
|
...args.slice(0, operationIndex),
|
|
330
337
|
...args.slice(operationIndex + 2),
|
|
331
338
|
];
|
|
339
|
+
const skillIndex = operationArgs.indexOf("--skill-id");
|
|
340
|
+
if (
|
|
341
|
+
skillIndex < 0
|
|
342
|
+
|| !SKILL_ID_PATTERN.test(operationArgs[skillIndex + 1] || "")
|
|
343
|
+
|| operationArgs.indexOf("--skill-id", skillIndex + 1) !== -1
|
|
344
|
+
) {
|
|
345
|
+
throw new BridgeError(
|
|
346
|
+
"every tools command requires --skill-id <active-skill-id>",
|
|
347
|
+
EXIT.USAGE
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const skillId = operationArgs[skillIndex + 1];
|
|
351
|
+
const remaining = [
|
|
352
|
+
...operationArgs.slice(0, skillIndex),
|
|
353
|
+
...operationArgs.slice(skillIndex + 2),
|
|
354
|
+
];
|
|
332
355
|
const command = remaining[0];
|
|
333
|
-
|
|
356
|
+
const context = { operationId, skillId };
|
|
357
|
+
if (
|
|
358
|
+
command === "context"
|
|
359
|
+
&& ["resolve", "select"].includes(remaining[1])
|
|
360
|
+
&& remaining.length === 4
|
|
361
|
+
&& remaining[2] === "--input"
|
|
362
|
+
&& remaining[3] === "-"
|
|
363
|
+
) {
|
|
364
|
+
return {
|
|
365
|
+
command,
|
|
366
|
+
action: remaining[1],
|
|
367
|
+
input: remaining[3],
|
|
368
|
+
...context,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (command === "status" && remaining.length === 1) return { command, ...context };
|
|
334
372
|
if (command === "list") {
|
|
335
|
-
if (remaining.length === 1) return { command,
|
|
373
|
+
if (remaining.length === 1) return { command, ...context };
|
|
336
374
|
if (remaining.length === 3 && remaining[1] === "--prefix" && remaining[2]) {
|
|
337
|
-
return { command, prefix: remaining[2],
|
|
375
|
+
return { command, prefix: remaining[2], ...context };
|
|
338
376
|
}
|
|
339
377
|
}
|
|
340
378
|
if (command === "describe" && remaining.length === 2 && remaining[1]) {
|
|
341
|
-
return { command, toolName: remaining[1],
|
|
379
|
+
return { command, toolName: remaining[1], ...context };
|
|
342
380
|
}
|
|
343
381
|
if (
|
|
344
382
|
command === "package-upload"
|
|
@@ -349,13 +387,13 @@ function parseCommand(args) {
|
|
|
349
387
|
&& remaining[3] === "--input"
|
|
350
388
|
&& remaining[4] === "-"
|
|
351
389
|
) {
|
|
352
|
-
return { command, archive: remaining[2], input: remaining[4],
|
|
390
|
+
return { command, archive: remaining[2], input: remaining[4], ...context };
|
|
353
391
|
}
|
|
354
392
|
if (command === "read" && remaining.length === 4 && remaining[1] && remaining[2] === "--input" && remaining[3] === "-") {
|
|
355
|
-
return { command, toolName: remaining[1], input: remaining[3],
|
|
393
|
+
return { command, toolName: remaining[1], input: remaining[3], ...context };
|
|
356
394
|
}
|
|
357
395
|
if (command === "call" && remaining.length >= 4 && remaining[1] && remaining[2] === "--input" && remaining[3] === "-") {
|
|
358
|
-
const parsed = { command, toolName: remaining[1], input: remaining[3],
|
|
396
|
+
const parsed = { command, toolName: remaining[1], input: remaining[3], ...context };
|
|
359
397
|
for (let index = 4; index < remaining.length; index += 2) {
|
|
360
398
|
const option = remaining[index];
|
|
361
399
|
const value = remaining[index + 1];
|
|
@@ -403,6 +441,27 @@ function platformQueryIntent(toolName, argumentsValue) {
|
|
|
403
441
|
}
|
|
404
442
|
}
|
|
405
443
|
|
|
444
|
+
async function resolvePlatformQueryArguments(toolName, argumentsValue, config) {
|
|
445
|
+
if (
|
|
446
|
+
!ENTERPRISE_SCOPED_PLATFORM_QUERY_TOOLS.has(toolName)
|
|
447
|
+
|| (typeof argumentsValue.enterprise_id === "string" && argumentsValue.enterprise_id)
|
|
448
|
+
) {
|
|
449
|
+
return argumentsValue;
|
|
450
|
+
}
|
|
451
|
+
const identity = await execute({
|
|
452
|
+
command: "read",
|
|
453
|
+
toolName: "rainbond_get_current_user",
|
|
454
|
+
argumentsValue: {},
|
|
455
|
+
}, config);
|
|
456
|
+
if (!identity || typeof identity.enterprise_id !== "string" || !identity.enterprise_id) {
|
|
457
|
+
throw new BridgeError(
|
|
458
|
+
"current Rainbond identity does not include an enterprise",
|
|
459
|
+
EXIT.TOOL
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return { ...argumentsValue, enterprise_id: identity.enterprise_id };
|
|
463
|
+
}
|
|
464
|
+
|
|
406
465
|
function readArguments(input) {
|
|
407
466
|
if (input !== "-") {
|
|
408
467
|
throw new BridgeError("read and call accept JSON only through --input - (stdin)", EXIT.USAGE);
|
|
@@ -1024,7 +1083,128 @@ async function listTools(config) {
|
|
|
1024
1083
|
return result.tools;
|
|
1025
1084
|
}
|
|
1026
1085
|
|
|
1086
|
+
function schemaTypeMatches(value, type) {
|
|
1087
|
+
if (type === "null") return value === null;
|
|
1088
|
+
if (type === "array") return Array.isArray(value);
|
|
1089
|
+
if (type === "object") return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1090
|
+
if (type === "integer") return Number.isInteger(value);
|
|
1091
|
+
if (type === "number") return typeof value === "number" && Number.isFinite(value);
|
|
1092
|
+
return typeof value === type;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function validateSchemaValue(value, schema, fieldPath) {
|
|
1096
|
+
if (!schema || typeof schema !== "object") return null;
|
|
1097
|
+
if (Array.isArray(schema.oneOf)) {
|
|
1098
|
+
const matches = schema.oneOf.filter((candidate) => !validateSchemaValue(value, candidate, fieldPath));
|
|
1099
|
+
return matches.length === 1 ? null : `field ${fieldPath} must match exactly one allowed schema`;
|
|
1100
|
+
}
|
|
1101
|
+
if (Array.isArray(schema.anyOf)) {
|
|
1102
|
+
const matches = schema.anyOf.some((candidate) => !validateSchemaValue(value, candidate, fieldPath));
|
|
1103
|
+
return matches ? null : `field ${fieldPath} does not match an allowed schema`;
|
|
1104
|
+
}
|
|
1105
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => candidate === value)) {
|
|
1106
|
+
return `field ${fieldPath} has an unsupported value`;
|
|
1107
|
+
}
|
|
1108
|
+
if (schema.type) {
|
|
1109
|
+
const allowedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
1110
|
+
if (!allowedTypes.some((type) => schemaTypeMatches(value, type))) {
|
|
1111
|
+
return `field ${fieldPath} must be ${allowedTypes.join(" or ")}`;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (typeof value === "string") {
|
|
1115
|
+
if (Number.isInteger(schema.minLength) && value.length < schema.minLength) {
|
|
1116
|
+
return `field ${fieldPath} is shorter than the allowed minimum`;
|
|
1117
|
+
}
|
|
1118
|
+
if (Number.isInteger(schema.maxLength) && value.length > schema.maxLength) {
|
|
1119
|
+
return `field ${fieldPath} exceeds the allowed maximum`;
|
|
1120
|
+
}
|
|
1121
|
+
if (typeof schema.pattern === "string") {
|
|
1122
|
+
try {
|
|
1123
|
+
if (!new RegExp(schema.pattern).test(value)) return `field ${fieldPath} has an invalid format`;
|
|
1124
|
+
} catch (_error) {
|
|
1125
|
+
throw new BridgeError("Rainbond tool schema contains an invalid pattern", EXIT.TRANSPORT);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (typeof value === "number") {
|
|
1130
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) {
|
|
1131
|
+
return `field ${fieldPath} is below the allowed minimum`;
|
|
1132
|
+
}
|
|
1133
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) {
|
|
1134
|
+
return `field ${fieldPath} exceeds the allowed maximum`;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (Array.isArray(value)) {
|
|
1138
|
+
if (Number.isInteger(schema.minItems) && value.length < schema.minItems) {
|
|
1139
|
+
return `field ${fieldPath} has too few items`;
|
|
1140
|
+
}
|
|
1141
|
+
if (Number.isInteger(schema.maxItems) && value.length > schema.maxItems) {
|
|
1142
|
+
return `field ${fieldPath} has too many items`;
|
|
1143
|
+
}
|
|
1144
|
+
if (schema.items) {
|
|
1145
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1146
|
+
const error = validateSchemaValue(value[index], schema.items, `${fieldPath}[${index}]`);
|
|
1147
|
+
if (error) return error;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1152
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
1153
|
+
for (const field of required) {
|
|
1154
|
+
if (!Object.prototype.hasOwnProperty.call(value, field)) return `missing required field: ${field}`;
|
|
1155
|
+
}
|
|
1156
|
+
const properties = schema.properties && typeof schema.properties === "object"
|
|
1157
|
+
? schema.properties
|
|
1158
|
+
: {};
|
|
1159
|
+
for (const [field, item] of Object.entries(value)) {
|
|
1160
|
+
if (Object.prototype.hasOwnProperty.call(properties, field)) {
|
|
1161
|
+
const error = validateSchemaValue(item, properties[field], `${fieldPath}.${field}`);
|
|
1162
|
+
if (error) return error;
|
|
1163
|
+
} else if (schema.additionalProperties === false) {
|
|
1164
|
+
return `unsupported field: ${field}`;
|
|
1165
|
+
} else if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
1166
|
+
const error = validateSchemaValue(item, schema.additionalProperties, `${fieldPath}.${field}`);
|
|
1167
|
+
if (error) return error;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
async function validateToolArguments(config, toolName, argumentsValue) {
|
|
1175
|
+
const tools = await listTools(config);
|
|
1176
|
+
const tool = tools.find((candidate) => candidate && candidate.name === toolName);
|
|
1177
|
+
if (!tool) throw new BridgeError("Rainbond tool was not found", EXIT.TOOL);
|
|
1178
|
+
if (!tool.inputSchema || typeof tool.inputSchema !== "object") {
|
|
1179
|
+
throw new BridgeError("Rainbond tool schema is invalid", EXIT.TRANSPORT);
|
|
1180
|
+
}
|
|
1181
|
+
const validationError = validateSchemaValue(argumentsValue, tool.inputSchema, "$input");
|
|
1182
|
+
if (validationError) throw new BridgeError(validationError, EXIT.USAGE);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1027
1185
|
async function execute(command, config) {
|
|
1186
|
+
if (command.command === "context") {
|
|
1187
|
+
const { createRuntimeContextResolver } = requireRuntimeModule("runtime-context-resolver.js");
|
|
1188
|
+
const resolver = createRuntimeContextResolver({
|
|
1189
|
+
operationStore: config.operationStore,
|
|
1190
|
+
queryTool: async (toolName, argumentsValue) => execute({
|
|
1191
|
+
command: "read",
|
|
1192
|
+
toolName,
|
|
1193
|
+
argumentsValue,
|
|
1194
|
+
}, config),
|
|
1195
|
+
});
|
|
1196
|
+
if (command.action === "resolve") {
|
|
1197
|
+
return resolver.resolve({
|
|
1198
|
+
operationId: config.operationId,
|
|
1199
|
+
required: command.argumentsValue.required,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
return resolver.select({
|
|
1203
|
+
operationId: config.operationId,
|
|
1204
|
+
selectionId: command.argumentsValue.selection_id,
|
|
1205
|
+
optionId: command.argumentsValue.option_id,
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1028
1208
|
if (command.command === "package-upload") {
|
|
1029
1209
|
return executePackageUpload(command, config);
|
|
1030
1210
|
}
|
|
@@ -1073,6 +1253,7 @@ async function execute(command, config) {
|
|
|
1073
1253
|
}
|
|
1074
1254
|
skillBinding = loadSkillBinding(config, command.skillId, command.rootSkillId);
|
|
1075
1255
|
if (!command.confirmation) {
|
|
1256
|
+
await validateToolArguments(config, command.toolName, argumentsValue);
|
|
1076
1257
|
operation = prepareOperation(
|
|
1077
1258
|
config,
|
|
1078
1259
|
command.toolName,
|
|
@@ -1140,14 +1321,19 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1140
1321
|
let argumentRedactions = [];
|
|
1141
1322
|
try {
|
|
1142
1323
|
command = parseCommand(args);
|
|
1143
|
-
if (["read", "call", "package-upload", "query"].includes(command.command)) {
|
|
1324
|
+
if (["read", "call", "package-upload", "query", "context"].includes(command.command)) {
|
|
1144
1325
|
const argumentsValue = readArguments(command.input);
|
|
1145
1326
|
command.argumentsValue = argumentsValue;
|
|
1146
1327
|
argumentRedactions = collectArgumentRedactions(argumentsValue);
|
|
1147
1328
|
}
|
|
1148
1329
|
if (command.command === "query") {
|
|
1149
|
-
platformQueryIntent(command.toolName, command.argumentsValue);
|
|
1150
1330
|
config = loadEnvironmentConfig({ environmentId: command.environmentId });
|
|
1331
|
+
command.argumentsValue = await resolvePlatformQueryArguments(
|
|
1332
|
+
command.toolName,
|
|
1333
|
+
command.argumentsValue,
|
|
1334
|
+
config
|
|
1335
|
+
);
|
|
1336
|
+
platformQueryIntent(command.toolName, command.argumentsValue);
|
|
1151
1337
|
command = {
|
|
1152
1338
|
command: "read",
|
|
1153
1339
|
toolName: command.toolName,
|
|
@@ -1164,6 +1350,12 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1164
1350
|
includeRuntime: true,
|
|
1165
1351
|
operationId: command.operationId,
|
|
1166
1352
|
});
|
|
1353
|
+
if (command.skillId !== config.requiredSkillId) {
|
|
1354
|
+
throw new BridgeError(
|
|
1355
|
+
"Skill does not match the protected runtime operation intent",
|
|
1356
|
+
EXIT.CONFIG
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1167
1359
|
if (config.isInsecureHttp) {
|
|
1168
1360
|
process.stderr.write('{"warning":"using insecure HTTP transport"}\n');
|
|
1169
1361
|
}
|
|
@@ -1195,6 +1387,8 @@ module.exports = {
|
|
|
1195
1387
|
platformQueryIntent,
|
|
1196
1388
|
loadEnvironmentConfig,
|
|
1197
1389
|
rpcRequest,
|
|
1390
|
+
validateSchemaValue,
|
|
1391
|
+
validateToolArguments,
|
|
1198
1392
|
validatePackageUploadRequest,
|
|
1199
1393
|
};
|
|
1200
1394
|
|
package/bin/rainskills.js
CHANGED
|
@@ -412,15 +412,44 @@ async function runBuiltin(args, {
|
|
|
412
412
|
const getOperationStore = () => operationStore || getEnvironmentServices().operationStore;
|
|
413
413
|
const getEnvironmentCredentialStore = () => environmentCredentialStore
|
|
414
414
|
|| getEnvironmentServices().environmentCredentialStore;
|
|
415
|
-
const getRuntimeStateManager = (operationId, {
|
|
415
|
+
const getRuntimeStateManager = (operationId, {
|
|
416
|
+
scoped = false,
|
|
417
|
+
protectedOperation = null,
|
|
418
|
+
} = {}) => {
|
|
416
419
|
if (runtimeStateManager) return runtimeStateManager;
|
|
417
420
|
const operationScoped = scoped || (
|
|
418
421
|
credentialEnvironment.RAINSKILLS_RUNTIME_STATE_SCOPE === "operation"
|
|
419
422
|
&& credentialEnvironment.RAINSKILLS_RUNTIME_OPERATION_ID === operationId
|
|
420
423
|
);
|
|
424
|
+
const managerOptions = operationScoped ? { operationId } : {};
|
|
425
|
+
if (protectedOperation?.environment_id) {
|
|
426
|
+
const environment = getEnvironmentRegistry().get(protectedOperation.environment_id);
|
|
427
|
+
if (!environment || environment.connection_state !== "connected") {
|
|
428
|
+
throw new Error("runtime operation 绑定的运行环境当前不可用");
|
|
429
|
+
}
|
|
430
|
+
const credentialStore = getEnvironmentCredentialStore();
|
|
431
|
+
const credential = credentialStore.read({
|
|
432
|
+
environmentId: environment.id,
|
|
433
|
+
expectedOrigin: environment.console_origin,
|
|
434
|
+
});
|
|
435
|
+
managerOptions.env = {
|
|
436
|
+
RAINBOND_JWT: credential.token,
|
|
437
|
+
RAINBOND_URL: credential.origin,
|
|
438
|
+
};
|
|
439
|
+
managerOptions.credentialWriter = ({ token, baseUrl }) => {
|
|
440
|
+
if (baseUrl !== environment.console_origin) {
|
|
441
|
+
throw new Error("runtime renewed credential origin 不匹配");
|
|
442
|
+
}
|
|
443
|
+
credentialStore.write({
|
|
444
|
+
environmentId: environment.id,
|
|
445
|
+
origin: baseUrl,
|
|
446
|
+
token,
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
}
|
|
421
450
|
return require(
|
|
422
451
|
"../rainbond-platform-installer/scripts/runtime-state.js"
|
|
423
|
-
).createRuntimeStateManager(
|
|
452
|
+
).createRuntimeStateManager(managerOptions);
|
|
424
453
|
};
|
|
425
454
|
|
|
426
455
|
if (args[0] === "environment" && args[1] === "list") {
|
|
@@ -908,8 +937,15 @@ async function runBuiltin(args, {
|
|
|
908
937
|
const protectedOperation = runtimeStateManager ? null : getOperationStore().read(args[3]);
|
|
909
938
|
const manager = getRuntimeStateManager(args[3], {
|
|
910
939
|
scoped: protectedOperation?.intent?.type === "environment-add",
|
|
940
|
+
protectedOperation,
|
|
911
941
|
});
|
|
912
|
-
manager.assertContinuationEligible(args[3]);
|
|
942
|
+
const eligible = manager.assertContinuationEligible(args[3]);
|
|
943
|
+
if (protectedOperation?.environment_id) {
|
|
944
|
+
const environment = getEnvironmentRegistry().get(protectedOperation.environment_id);
|
|
945
|
+
if (!environment || eligible.console_origin !== environment.console_origin) {
|
|
946
|
+
throw new Error("runtime operation 与已锁定运行环境不匹配");
|
|
947
|
+
}
|
|
948
|
+
}
|
|
913
949
|
const status = await manager.status();
|
|
914
950
|
if (status.state !== "connected" || status.usable !== true) {
|
|
915
951
|
throw new Error("runtime 尚未 connected 且通过 live probe,不能恢复 intent");
|
package/install.sh
CHANGED
|
@@ -2652,6 +2652,11 @@ main() {
|
|
|
2652
2652
|
done < <(find "$SCRIPT_DIR" -maxdepth 1 -mindepth 1 -type d -name 'rainbond-*' | sort)
|
|
2653
2653
|
[[ "${#skills[@]}" -gt 0 ]] || die "在 $SCRIPT_DIR 下没有找到 rainbond-* skill 目录。"
|
|
2654
2654
|
|
|
2655
|
+
local root_skill_dir="$SCRIPT_DIR/marketplace/rainskills/skills/rainskills"
|
|
2656
|
+
if [[ -d "$root_skill_dir" ]]; then
|
|
2657
|
+
skills+=("$root_skill_dir")
|
|
2658
|
+
fi
|
|
2659
|
+
|
|
2655
2660
|
for skill_dir in "${skills[@]}"; do
|
|
2656
2661
|
validate_skill_dir "$skill_dir"
|
|
2657
2662
|
done
|
|
@@ -14,7 +14,7 @@ This is the single marketplace entry for the complete Rainskills product. The in
|
|
|
14
14
|
3. Keep stdin, stdout, and stderr attached. When `RAINSKILLS_USER_INPUT_REQUIRED` appears, pause for that installer choice. If the installer emits `rainskills.next-action.v1`, execute only its fixed `argv` through the same launcher; never evaluate output as a shell command. If the adjacent `bin/rainskills.js` exists, use it for fixed next actions; otherwise use the same versioned npm package fallback described below.
|
|
15
15
|
4. Stay attached until every independent Skill is installed. Do not select, connect, or configure an application runtime during installation. In the user-facing response, output only the fixed completion message below.
|
|
16
16
|
|
|
17
|
-
If the adjacent installer is missing, check the local Node.js version before choosing the fallback. With `npx` and Node.js 18 or newer, use `npx --yes rainskills@0.1.
|
|
17
|
+
If the adjacent installer is missing, check the local Node.js version before choosing the fallback. With `npx` and Node.js 18 or newer, use `npx --yes rainskills@0.1.16 <target>`. With no Node.js or a version below 18, use `bash <(curl -fsSL https://get.rainbond.com/rainskills/install.sh) <target>` instead. Omit `<target>` only when the host cannot be determined reliably. Keep either command attached to the interactive terminal. For an update or repair, refresh this marketplace Skill first, then run the installer again; it compares and updates every independent internal Skill.
|
|
18
18
|
|
|
19
19
|
Skills-only 安装不需要 Node.js;CDN fallback 只负责安装 Skill 文件,不代表运行环境连接、应用部署或平台安装已经可执行。用户首次提出需要运行环境的动作时,对应业务 Skill 才检查 Node.js;固定 Rainskills launcher 需要 Node.js 18 或更高版本。缺失或版本过低时保留原始 intent 并停止,等待用户或 agent 明确同意安装或升级 Node.js,安装完成消息不得提前提示 Node.js。
|
|
20
20
|
|
|
@@ -42,7 +42,9 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
42
42
|
|
|
43
43
|
受限沙箱(包括 Codex)执行本地状态命令时,必须申请用户级受保护目录访问权限;在 Codex 中使用 `require_escalated`。不得修改 `~/.rainbond` 权限、复制受保护状态到工作区,或因沙箱权限错误建议重装。
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
涉及浏览器或设备授权的 `runtime connect`,以及恢复/安装场景中的 `rainskills <target> --self-hosted`,必须在附加交互终端(TTY)中运行;在 Codex 中设置 `tty: true` 并保持进程附着直到授权完成。禁止通过非交互命令要求用户粘贴 JWT;非交互模式只可复用已存在的受保护凭据。
|
|
46
|
+
|
|
47
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.16`)。
|
|
46
48
|
|
|
47
49
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
48
50
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rainskills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Interactive Rainbond skill installer for Codex, Claude Code, and Pi Agent",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/goodrain/rainskills#readme",
|
|
@@ -58,11 +58,12 @@
|
|
|
58
58
|
"check:runtime-version": "node scripts/sync-runtime-version.mjs --check",
|
|
59
59
|
"version": "npm run sync:runtime-version && npm run build:marketplace",
|
|
60
60
|
"prepack": "node scripts/sync-runtime-version.mjs --check && node scripts/build-marketplace-package.mjs --check --quiet",
|
|
61
|
-
"test": "npm run test:runtime-version && npm run test:auto-update && npm run test:launcher && npm run test:api-bridge && npm run test:skill-profile && npm run test:marketplace && npm run test:runtime-routing && npm run test:telemetry && npm run test:platform && npm run test:windows && npm run test:package-upload && npm run test:package && npm run test:installer && npm run test:signal && npm run test:npx-pty",
|
|
61
|
+
"test": "npm run test:runtime-version && npm run test:auto-update && npm run test:launcher && npm run test:api-bridge && npm run test:console-contract && npm run test:skill-profile && npm run test:marketplace && npm run test:runtime-routing && npm run test:telemetry && npm run test:platform && npm run test:windows && npm run test:package-upload && npm run test:package && npm run test:installer && npm run test:signal && npm run test:npx-pty",
|
|
62
62
|
"test:runtime-version": "node --test tests/runtime-version-sync.test.js",
|
|
63
63
|
"test:auto-update": "node --test tests/auto-update.test.js",
|
|
64
64
|
"test:launcher": "node --test tests/console-origin.test.js tests/environment-credentials.test.js tests/environment-registry.test.js tests/local-runtime-cli.test.js tests/local-cli-installer.test.js tests/npx-launcher.test.js tests/runtime-credentials.test.js tests/runtime-intents.test.js tests/runtime-operations.test.js tests/runtime-state.test.js tests/cli-only-transport.test.js tests/skill-manifest-builder.test.js",
|
|
65
65
|
"test:api-bridge": "node --test tests/api-bridge.test.js",
|
|
66
|
+
"test:console-contract": "python3 tests/console_tool_contract_test.py",
|
|
66
67
|
"test:skill-profile": "node --test tests/skill-profile-builder.test.js tests/markdown-link-integrity.test.js",
|
|
67
68
|
"test:marketplace": "node --test tests/marketplace-entry.test.js",
|
|
68
69
|
"test:runtime-routing": "node --test tests/runtime-onboarding-routing.test.js && python3 tests/run_skill_routing_evals.py",
|
|
@@ -102,19 +102,21 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
102
102
|
|
|
103
103
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
104
104
|
|
|
105
|
-
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.
|
|
105
|
+
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.16`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止使用 `rainskills@latest`,禁止拼接或执行 shell 字符串。
|
|
106
106
|
|
|
107
107
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定本地 launcher。
|
|
108
108
|
|
|
109
109
|
所有 Rainbond 查询和变更必须通过本地 `~/.rainbond/bin/rainskills-tools.js` 执行,并绑定本次 operation ID。禁止 Agent 直接调用 Rainbond MCP,也不得启动本地 Rainskills MCP 服务;只允许执行 CLI 返回的结构化结果与确认续接 argv。
|
|
110
110
|
|
|
111
|
+
业务操作开始后,必须先用固定 argv `["node", "<home>/.rainbond/bin/rainskills-tools.js", "context", "resolve", "--input", "-", "--operation-id", "<uuid>", "--skill-id", "rainbond-app-assistant"]`,通过 stdin 传入 `{"required": ["enterprise", "workspace"]}`。返回 `resolved` 时只使用该 operation 已保存的企业、工作空间和集群上下文;返回 `needs-selection` 时,把 CLI 返回的“工作空间 + 集群”组合选项一次性列给用户,不得逐项询问,也不得让用户提供 enterprise_id。用户选择后执行 `context select`,stdin 只传 `{"selection_id":"<selection-id>","option_id":"<option-id>"}`;选择成功后继续原操作,同一 operation 不再重复查询上下文。
|
|
112
|
+
|
|
111
113
|
只有 CLI 返回并通过校验的 `rainskills.next-action.v1` argv 才能执行续接。普通失败一律禁止自动重试:不得再次执行原命令,不得执行 `--help`、`sleep`、`rg`、`grep`,不得搜索 Rainskills 源码;同一 `operation complete` 最多执行一次。
|
|
112
114
|
|
|
113
115
|
<!-- rainskills-runtime-contract:start -->
|
|
114
116
|
```json
|
|
115
117
|
{
|
|
116
118
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
117
|
-
"package_version": "rainskills@0.1.
|
|
119
|
+
"package_version": "rainskills@0.1.16",
|
|
118
120
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
119
121
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
120
122
|
"local_argv": {
|
|
@@ -147,11 +149,15 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
147
149
|
|
|
148
150
|
连接或平台安装完成后,用固定 `onboarding-id` 执行 launcher + `["intent", "resume", "--onboarding-id", "<同一 onboarding-id>"]`,恢复原始 intent 和 `resume_step`,不得重新猜测动作。
|
|
149
151
|
|
|
152
|
+
同一条附着命令先输出 `runtime.device-authorization`、随后输出 schema 为 `rainskills.runtime-connect-result.v1` 且 `state=connected` 时,最终 connected 结果覆盖前面的等待授权消息。不得再次展示旧授权消息、不得等待用户回复“已授权”;必须在同一轮立即使用同一 `onboarding-id` 执行 `intent resume`。
|
|
153
|
+
|
|
150
154
|
业务 MCP 返回 401 时,先执行 launcher + `["runtime", "record-failure", "--onboarding-id", "<同一 onboarding-id>", "--step", "<当前固定步骤>", "--reason", "credential-expired"]`,再仅一次执行 launcher + `["runtime", "reconnect", "--onboarding-id", "<同一 onboarding-id>"]`,然后 resume 并只重试该步骤;第二次 401 立即停止。403 时执行 launcher + `["runtime", "record-failure", "--onboarding-id", "<同一 onboarding-id>", "--step", "<当前固定步骤>", "--reason", "permission-denied"]` 后停止,不得 reconnect、重新授权或自动重试,只说明缺少的权限。
|
|
151
155
|
<!-- rainskills-runtime-gate:end -->
|
|
152
156
|
|
|
153
157
|
受限沙箱(包括 Codex)执行本地状态命令时,必须申请用户级受保护目录访问权限;在 Codex 中使用 `require_escalated`。不得修改 `~/.rainbond` 权限、复制受保护状态到工作区,或因沙箱权限错误建议重装。
|
|
154
158
|
|
|
159
|
+
涉及浏览器或设备授权的 `runtime connect`,以及恢复/安装场景中的 `rainskills <target> --self-hosted`,必须在附加交互终端(TTY)中运行;在 Codex 中设置 `tty: true` 并保持进程附着直到授权完成。禁止通过非交互命令要求用户粘贴 JWT;非交互模式只可复用已存在的受保护凭据。
|
|
160
|
+
|
|
155
161
|
执行优化:同一会话内只检查一次 Node.js(首次使用本地 CLI 前);仅在 Node.js 或 Rainskills 安装、升级,或 PATH 变更后失效。固定 launcher 和 argv 已在本 Skill 中,禁止读取、搜索或探测 `rainskills.js`,也禁止执行 `npm root -g`。每个新的业务操作仍需要刷新一次环境列表;带已有 `operation_id` 或 `onboarding-id` 的续接复用已绑定的环境 ID,不重复枚举环境。
|
|
156
162
|
|
|
157
163
|
<!-- rainskills-runtime-routing:start -->
|
|
@@ -286,7 +292,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
286
292
|
- 可以用 `rainbond_query_region_rbd_components` 查询并展示它们的状态
|
|
287
293
|
- 不能通过 MCP 对它们执行重启、部署、修改等写操作;当前 MCP 工具集不支持此类操作
|
|
288
294
|
- 如果用户要求操作这些组件,明确告知:需要通过 Kubernetes 命令(如 `kubectl rollout restart deployment/<name> -n rbd-system`)或 Rainbond 集群管理控制台进行,超出本技能的操作范围,不要假装可以执行
|
|
289
|
-
28a.
|
|
295
|
+
28a. 已知 `service_id` 的组件在构建、部署或运行操作后失败或立即异常时,调用 `rainbond_get_operation_failure_context({team_name, region_name, app_id, service_id, event_id?})`,按其 `classified_reason` 决定停下、只读核实或低风险修复;`unknown` 回退既有证据链,禁止盲目重放写操作。CLI 在确认令牌生成前报告的 missing/invalid field 属于参数校验失败,尚未进入组件操作:按 Console Tool schema 修正参数一次,不调用 failure context,也不查询组件残留。
|
|
290
296
|
`event_log_tail` 只作为敏感诊断证据使用,绝不能复制、引用或向用户展示其原文;输出只能使用 `classified_reason`、非敏感摘要和已脱敏字段。
|
|
291
297
|
28b. 运行态或交付健康检查先调用 `rainbond_get_app_health_overview`;仅 `abnormal` 或 `unknown` 组件再读取 component summary、日志、事件或存储明细。
|
|
292
298
|
29. **仅给 bare Git URL 时默认 root + 空 `subdirectories`**:当用户给的只是一个 Git URL(无本地 manifest、无明确子目录提示),默认 `subdirectories=""`(仓库根)进入 source 检测,让后端判断这个仓库结构。**不要**先问用户"根目录还是子目录"。
|
|
@@ -366,7 +372,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
366
372
|
- 从对话历史里随手抓一个看起来像 UUID 的字符串(可能是 `check_event_id` / `check_uuid` / `event_id` / 历史 service_id)
|
|
367
373
|
- 凭"我记得是这个"或"上次也是这个 ID"做猜测
|
|
368
374
|
- 用户消息里粘的、但本会话没验证过的 ID
|
|
369
|
-
强制流程:动手前如果不能 100% 确定 `service_id` 出处,**第一动作**必须是 `rainbond_query_components(
|
|
375
|
+
强制流程:动手前如果不能 100% 确定 `service_id` 出处,**第一动作**必须是 `rainbond_query_components({enterprise_id, app_id})`,必要时携带 `query=<service_cname 或 service_id>`,按 `service_cname` 或 `k8s_component_name` 匹配出真实 `service_id`,再调写工具。
|
|
370
376
|
反例(**禁止**):日志里出现"修改组件 `7059eb62cccc3a16f22c9415c905bbcc` 的构建源" — 这个 ID 在本会话所有 query 结果里都没出现过,是模型从某处幻觉出来的。正确做法:调 update 之前先 `rainbond_query_components` 拿到真实 `service_id`,再 update。
|
|
371
377
|
与 Iron Law 31 配套:31 管"写工具前必须 select skill",34 管"写工具的 service_id 必须有明确出处"。两条共同把"模型自由发挥参数"这条路堵死。
|
|
372
378
|
|
|
@@ -384,7 +390,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
384
390
|
反例(来自真实回归 case `cs_1779149681822_3u`,2026-05-19):模型从 `rainbond_get_component_summary` 响应的 `recent_events` 段看到形如 `{"ID": 17740, "event_id": "...", "opt_type": "build-service"}`,直接把 `17740` 当成 `event_id` 传给 `rainbond_get_component_build_logs`,工具返回 `items: []`(找不到该 UUID 的日志)。正确做法:从同一 `recent_events[i].event_id` 字段取出 UUID 字符串,或调 `rainbond_get_component_events` 重查。
|
|
385
391
|
35. **会话内部叙述纪律 + 内部 preflight 工具不要主动调**。下列四类是"内部会话状态",对用户**无信息量**,禁止外漏到 assistant 可见消息:
|
|
386
392
|
- **`select_skill_*` 工具调用本身**:这是 server 内部 hookup,把指定 skill 的执行手册拼到 system prompt 用的。调用前**不要**说"我先加载 bootstrap 手册"、"现在调用 select_skill_...";调用后**不要**说"Bootstrap 手册已加载"、"skill ready" 这类回声。server 返回的 `loaded_skill` / `already active` ack 是给你看的内部信号,**直接进入下一个真实工具调用**,保持沉默。
|
|
387
|
-
- **`rainbond_get_current_user
|
|
393
|
+
- **`rainbond_get_current_user`**:不得由 Agent 单独调用。企业、工作空间和集群上下文统一由受保护的 `context resolve` 命令解析并绑定到本次 operation;只有 CLI 明确返回需要选择时才询问用户。
|
|
388
394
|
- **`rainbond_query_components` 同入参重复轮询**:服务端 30s 内的同 args 调用会走缓存;你**不要**在每个新 user turn 开头都"先查一下组件列表",priorTurnMessages 里上一次的 query 结果在 contextSignature 不变时仍然有效。
|
|
389
395
|
- **规则推理过程 / MCP 工具内部限制 / 分类决策叙述**:你内部基于哪条 Iron Law / hard rule / 推断信号做的决策、具体 MCP 工具签名 / 字段限制、对组件 / 服务的分类判断("ClickHouse 是公认的列式分析数据库"这类),都属于内部状态,对用户**无信息量**。
|
|
390
396
|
|
|
@@ -414,7 +420,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
414
420
|
反例(**禁止**):上一轮已经 `select_skill_rainbond-fullstack-bootstrap` 过了,本轮 user 简短回复 "java/jar",你又调一次 `select_skill_rainbond-fullstack-bootstrap` 并叙述"先加载 bootstrap"。正确做法:priorTurnMessages 已有 ack → 直接 `rainbond_update_component_build_source(...)` 继续。
|
|
415
421
|
正例:上一轮 `select_skill_rainbond-fullstack-bootstrap`,本轮用户说"组件起不来帮我排查下" → 现在阶段从部署切到排障 → 调一次 `select_skill_rainbond-fullstack-troubleshooter`(新 skill,允许)→ 沉默地进入诊断流程,不复述"troubleshooter 已加载"。
|
|
416
422
|
36. **用户给出的字面值(URL / 镜像地址 / 分支名 / 凭证)必须 verbatim 传给工具,禁止 LLM 凭训练知识"补全"、"修正"、"猜测"**。
|
|
417
|
-
适用字段:`git_url` / `
|
|
423
|
+
适用字段:`git_url` / `image`(用户所说的镜像地址映射到 Console Tool 的 `image` 字段)/ `code_version`(分支/tag/commit)/ `username` / `password` / `token` / `subdirectories` 等任何用户在消息里给出的字面值。
|
|
418
424
|
**禁止行为**:
|
|
419
425
|
- 看到 `service_cname=java-maven-demo` 就自创 `git_url=https://gitee.com/mirrors_123/java-maven-demo.git`(按训练数据里"常见仓库地址"补全)
|
|
420
426
|
- 用户给的 URL 没 `.git` 后缀就自动加上
|
|
@@ -478,11 +484,11 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
478
484
|
- 与 Iron Law 14(delivery mode 切换必须用户确认)配套:14 管 source↔package↔image↔template 的策略切换需用户确认,38 管"用户已经显式给了源码意图时,模板不是默认路径、且切换必须清理残留"。
|
|
479
485
|
39. **轮次纪律:复用已知值,不重复无变化的调用**(与 Iron Law 35 的"内部 preflight 工具不要主动调"配套,35 管哪些工具不该调,39 管已拿到的值要复用):
|
|
480
486
|
- **复用创建返回的 `service_id` / `service_alias`**:`rainbond_create_component_*` 成功后会返回 `service_id` 和 `service_alias`(k8s component name),**记住并在本轮后续 mutating 操作里直接复用**。**禁止**在每次 `rainbond_manage_component_*` / `rainbond_operate_app` 之前都先 `rainbond_query_components` 重查一遍 alias —— 创建时已经返回过,重查只是浪费轮次(仅当 `service_id` 出处不明、按 Iron Law 34 必须重新建立 provenance 时才查)。
|
|
481
|
-
- **不重复调 `rainbond_get_current_user
|
|
487
|
+
- **不重复调 `rainbond_get_current_user`**:同一 operation 复用 `context resolve` 已保存的企业、工作空间和集群上下文;只有上下文失效或用户明确切换目标时才重新解析。
|
|
482
488
|
- **同一 mutating 调用成功后禁止原样重复**:一个写工具用相同入参成功返回后,不要在同一轮再发一次相同调用"确认一下"——成功就是成功,要确认状态用读工具(`rainbond_get_component_summary` 等),不要重发写调用。
|
|
483
|
-
40. **对外访问地址必须引用工具返回的真实值,禁止按格式拼装。**
|
|
484
|
-
-
|
|
485
|
-
-
|
|
489
|
+
40. **对外访问地址必须引用工具返回的真实值,禁止按格式拼装。** 组件的对外访问地址是**事实信息**,权威来源是 `rainbond_get_component_detail` 或 `rainbond_get_component_summary` 返回的 `access_infos` 字段(都来自网关真实绑定)。只需状态和访问地址时优先调用轻量的 detail;只有异常组件确实需要端口、env、存储或事件证据时才调用 summary。
|
|
490
|
+
- 本轮已通过上述任一工具拿到真实地址 → 报告里引用 `access_infos` 的真实值。
|
|
491
|
+
- 本轮**未**通过上述工具拿到可用地址 → 最终报告必须写"请在控制台该组件的端口页查看对外访问地址",**禁止**按记忆或文档示例的 URL 格式(`<name>.<ip>.nip.io`、`<service>-<port>-<team>.<ip>.nip.io` 等)拼装一个地址当成真的。
|
|
486
492
|
- **禁止**把任何拼装/猜测出来的访问地址写进任何组件 env(如 `APP_WEB_URL` / `*_BASE_URL` / `*_PUBLIC_URL` 等)。需要把对外地址回填给某个组件时,同样只能用 `access_infos` 的真实值;拿不到真实值就停下来让用户在控制台确认后提供,不要先拼一个填进去。
|
|
487
493
|
- 真实事故:模型在没有任何工具返回访问地址的情况下,照文档示例格式拼出 `http://dify.<ip>.nip.io`,既写进最终报告又配进了组件 `APP_WEB_URL`,全是编造的。这是 Iron Law 违反。
|
|
488
494
|
41. **部署位置和访问地址必须分开。** `project.deployment_location_url` 是 Rainbond 控制台应用概览地址;仅在可信 Console base(`RAINBOND_URL` 或等价 session context)、`team_name`、`region_name` 和 `app_id` 全部已知时生成:
|
|
@@ -18,19 +18,21 @@ description: "Use when a user explicitly asks for an existing Rainbond app versi
|
|
|
18
18
|
|
|
19
19
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
20
20
|
|
|
21
|
-
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.
|
|
21
|
+
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.16`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
22
22
|
|
|
23
23
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定本地 launcher。
|
|
24
24
|
|
|
25
25
|
所有 Rainbond 查询和变更必须通过本地 `~/.rainbond/bin/rainskills-tools.js` 执行,并绑定本次 operation ID。禁止 Agent 直接调用 Rainbond MCP,也不得启动本地 Rainskills MCP 服务;只允许执行 CLI 返回的结构化结果与确认续接 argv。
|
|
26
26
|
|
|
27
|
+
业务操作开始后,必须先用固定 argv `["node", "<home>/.rainbond/bin/rainskills-tools.js", "context", "resolve", "--input", "-", "--operation-id", "<uuid>", "--skill-id", "rainbond-app-version-assistant"]`,通过 stdin 传入 `{"required": ["enterprise", "workspace"]}`。返回 `resolved` 时只使用该 operation 已保存的企业、工作空间和集群上下文;返回 `needs-selection` 时,把 CLI 返回的“工作空间 + 集群”组合选项一次性列给用户,不得逐项询问,也不得让用户提供 enterprise_id。用户选择后执行 `context select`,stdin 只传 `{"selection_id":"<selection-id>","option_id":"<option-id>"}`;选择成功后继续原操作,同一 operation 不再重复查询上下文。
|
|
28
|
+
|
|
27
29
|
只有 CLI 返回并通过校验的 `rainskills.next-action.v1` argv 才能执行续接。普通失败一律禁止自动重试:不得再次执行原命令,不得执行 `--help`、`sleep`、`rg`、`grep`,不得搜索 Rainskills 源码;同一 `operation complete` 最多执行一次。
|
|
28
30
|
|
|
29
31
|
<!-- rainskills-runtime-contract:start -->
|
|
30
32
|
```json
|
|
31
33
|
{
|
|
32
34
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
35
|
+
"package_version": "rainskills@0.1.16",
|
|
34
36
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
37
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
38
|
"local_argv": {
|
|
@@ -60,6 +62,8 @@ target 只允许 `codex`、`claude`、`pi`、`all`。校验 intent 后只执行
|
|
|
60
62
|
|
|
61
63
|
受限沙箱(包括 Codex)执行本地状态命令时,必须申请用户级受保护目录访问权限;在 Codex 中使用 `require_escalated`。不得修改 `~/.rainbond` 权限、复制受保护状态到工作区,或因沙箱权限错误建议重装。
|
|
62
64
|
|
|
65
|
+
涉及浏览器或设备授权的 `runtime connect`,以及恢复/安装场景中的 `rainskills <target> --self-hosted`,必须在附加交互终端(TTY)中运行;在 Codex 中设置 `tty: true` 并保持进程附着直到授权完成。禁止通过非交互命令要求用户粘贴 JWT;非交互模式只可复用已存在的受保护凭据。
|
|
66
|
+
|
|
63
67
|
执行优化:同一会话内只检查一次 Node.js(首次使用本地 CLI 前);仅在 Node.js 或 Rainskills 安装、升级,或 PATH 变更后失效。固定 launcher 和 argv 已在本 Skill 中,禁止读取、搜索或探测 `rainskills.js`,也禁止执行 `npm root -g`。每个新的业务操作仍需要刷新一次环境列表;带已有 `operation_id` 或 `onboarding-id` 的续接复用已绑定的环境 ID,不重复枚举环境。
|
|
64
68
|
|
|
65
69
|
<!-- rainskills-runtime-routing:start -->
|