rainskills 0.1.13 → 0.1.15
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 +1 -1
- package/bin/rainskills-tools.js +31 -8
- package/bin/rainskills.js +43 -6
- 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 +2 -2
- package/package.json +1 -1
- package/rainbond-app-assistant/SKILL.md +4 -2
- package/rainbond-app-version-assistant/SKILL.md +2 -2
- package/rainbond-delivery-verifier/SKILL.md +2 -2
- package/rainbond-env-sync/SKILL.md +2 -2
- package/rainbond-fullstack-bootstrap/SKILL.md +2 -2
- package/rainbond-fullstack-bootstrap/modules/40-source-and-package-rules.md +1 -1
- package/rainbond-fullstack-troubleshooter/SKILL.md +2 -2
- package/rainbond-opensource-app-deploy/SKILL.md +2 -2
- package/rainbond-platform-installer/SKILL.md +2 -2
- package/rainbond-platform-installer/references/installation-policy.md +1 -1
- package/rainbond-platform-installer/scripts/host-cluster-installer.js +65 -44
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-installer/scripts/platform-installer.js +1 -1
- package/rainbond-platform-query/SKILL.md +2 -2
- package/rainbond-project-init/SKILL.md +2 -2
- package/rainbond-template-installer/SKILL.md +2 -2
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.15`,不会执行浮动的 `@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,7 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
42
42
|
|
|
43
43
|
受限沙箱(包括 Codex)执行本地状态命令时,必须申请用户级受保护目录访问权限;在 Codex 中使用 `require_escalated`。不得修改 `~/.rainbond` 权限、复制受保护状态到工作区,或因沙箱权限错误建议重装。
|
|
44
44
|
|
|
45
|
-
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.
|
|
45
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.15`)。
|
|
46
46
|
|
|
47
47
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
48
48
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/bin/rainskills-tools.js
CHANGED
|
@@ -325,20 +325,37 @@ function parseCommand(args) {
|
|
|
325
325
|
throw new BridgeError("every tools command requires --operation-id <uuid>", EXIT.USAGE);
|
|
326
326
|
}
|
|
327
327
|
const operationId = args[operationIndex + 1];
|
|
328
|
-
const
|
|
328
|
+
const operationArgs = [
|
|
329
329
|
...args.slice(0, operationIndex),
|
|
330
330
|
...args.slice(operationIndex + 2),
|
|
331
331
|
];
|
|
332
|
+
const skillIndex = operationArgs.indexOf("--skill-id");
|
|
333
|
+
if (
|
|
334
|
+
skillIndex < 0
|
|
335
|
+
|| !SKILL_ID_PATTERN.test(operationArgs[skillIndex + 1] || "")
|
|
336
|
+
|| operationArgs.indexOf("--skill-id", skillIndex + 1) !== -1
|
|
337
|
+
) {
|
|
338
|
+
throw new BridgeError(
|
|
339
|
+
"every tools command requires --skill-id <active-skill-id>",
|
|
340
|
+
EXIT.USAGE
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const skillId = operationArgs[skillIndex + 1];
|
|
344
|
+
const remaining = [
|
|
345
|
+
...operationArgs.slice(0, skillIndex),
|
|
346
|
+
...operationArgs.slice(skillIndex + 2),
|
|
347
|
+
];
|
|
332
348
|
const command = remaining[0];
|
|
333
|
-
|
|
349
|
+
const context = { operationId, skillId };
|
|
350
|
+
if (command === "status" && remaining.length === 1) return { command, ...context };
|
|
334
351
|
if (command === "list") {
|
|
335
|
-
if (remaining.length === 1) return { command,
|
|
352
|
+
if (remaining.length === 1) return { command, ...context };
|
|
336
353
|
if (remaining.length === 3 && remaining[1] === "--prefix" && remaining[2]) {
|
|
337
|
-
return { command, prefix: remaining[2],
|
|
354
|
+
return { command, prefix: remaining[2], ...context };
|
|
338
355
|
}
|
|
339
356
|
}
|
|
340
357
|
if (command === "describe" && remaining.length === 2 && remaining[1]) {
|
|
341
|
-
return { command, toolName: remaining[1],
|
|
358
|
+
return { command, toolName: remaining[1], ...context };
|
|
342
359
|
}
|
|
343
360
|
if (
|
|
344
361
|
command === "package-upload"
|
|
@@ -349,13 +366,13 @@ function parseCommand(args) {
|
|
|
349
366
|
&& remaining[3] === "--input"
|
|
350
367
|
&& remaining[4] === "-"
|
|
351
368
|
) {
|
|
352
|
-
return { command, archive: remaining[2], input: remaining[4],
|
|
369
|
+
return { command, archive: remaining[2], input: remaining[4], ...context };
|
|
353
370
|
}
|
|
354
371
|
if (command === "read" && remaining.length === 4 && remaining[1] && remaining[2] === "--input" && remaining[3] === "-") {
|
|
355
|
-
return { command, toolName: remaining[1], input: remaining[3],
|
|
372
|
+
return { command, toolName: remaining[1], input: remaining[3], ...context };
|
|
356
373
|
}
|
|
357
374
|
if (command === "call" && remaining.length >= 4 && remaining[1] && remaining[2] === "--input" && remaining[3] === "-") {
|
|
358
|
-
const parsed = { command, toolName: remaining[1], input: remaining[3],
|
|
375
|
+
const parsed = { command, toolName: remaining[1], input: remaining[3], ...context };
|
|
359
376
|
for (let index = 4; index < remaining.length; index += 2) {
|
|
360
377
|
const option = remaining[index];
|
|
361
378
|
const value = remaining[index + 1];
|
|
@@ -1164,6 +1181,12 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1164
1181
|
includeRuntime: true,
|
|
1165
1182
|
operationId: command.operationId,
|
|
1166
1183
|
});
|
|
1184
|
+
if (command.skillId !== config.requiredSkillId) {
|
|
1185
|
+
throw new BridgeError(
|
|
1186
|
+
"Skill does not match the protected runtime operation intent",
|
|
1187
|
+
EXIT.CONFIG
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1167
1190
|
if (config.isInsecureHttp) {
|
|
1168
1191
|
process.stderr.write('{"warning":"using insecure HTTP transport"}\n');
|
|
1169
1192
|
}
|
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") {
|
|
@@ -905,10 +934,18 @@ async function runBuiltin(args, {
|
|
|
905
934
|
if (args.length !== 4 || args[2] !== "--onboarding-id" || !args[3]) {
|
|
906
935
|
throw new Error("intent resume 需要固定参数 --onboarding-id <uuid>");
|
|
907
936
|
}
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
937
|
+
const protectedOperation = runtimeStateManager ? null : getOperationStore().read(args[3]);
|
|
938
|
+
const manager = getRuntimeStateManager(args[3], {
|
|
939
|
+
scoped: protectedOperation?.intent?.type === "environment-add",
|
|
940
|
+
protectedOperation,
|
|
941
|
+
});
|
|
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
|
+
}
|
|
912
949
|
const status = await manager.status();
|
|
913
950
|
if (status.state !== "connected" || status.usable !== true) {
|
|
914
951
|
throw new Error("runtime 尚未 connected 且通过 live probe,不能恢复 intent");
|
|
@@ -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.15 <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,7 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
42
42
|
|
|
43
43
|
受限沙箱(包括 Codex)执行本地状态命令时,必须申请用户级受保护目录访问权限;在 Codex 中使用 `require_escalated`。不得修改 `~/.rainbond` 权限、复制受保护状态到工作区,或因沙箱权限错误建议重装。
|
|
44
44
|
|
|
45
|
-
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.
|
|
45
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.15`)。
|
|
46
46
|
|
|
47
47
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
48
48
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/package.json
CHANGED
|
@@ -102,7 +102,7 @@ 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.15`,且必须与本技能包 `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
|
|
|
@@ -114,7 +114,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
114
114
|
```json
|
|
115
115
|
{
|
|
116
116
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
117
|
-
"package_version": "rainskills@0.1.
|
|
117
|
+
"package_version": "rainskills@0.1.15",
|
|
118
118
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
119
119
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
120
120
|
"local_argv": {
|
|
@@ -147,6 +147,8 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
147
147
|
|
|
148
148
|
连接或平台安装完成后,用固定 `onboarding-id` 执行 launcher + `["intent", "resume", "--onboarding-id", "<同一 onboarding-id>"]`,恢复原始 intent 和 `resume_step`,不得重新猜测动作。
|
|
149
149
|
|
|
150
|
+
同一条附着命令先输出 `runtime.device-authorization`、随后输出 schema 为 `rainskills.runtime-connect-result.v1` 且 `state=connected` 时,最终 connected 结果覆盖前面的等待授权消息。不得再次展示旧授权消息、不得等待用户回复“已授权”;必须在同一轮立即使用同一 `onboarding-id` 执行 `intent resume`。
|
|
151
|
+
|
|
150
152
|
业务 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
153
|
<!-- rainskills-runtime-gate:end -->
|
|
152
154
|
|
|
@@ -18,7 +18,7 @@ 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.15`,且必须与本技能包 `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
|
|
|
@@ -30,7 +30,7 @@ description: "Use when a user explicitly asks for an existing Rainbond app versi
|
|
|
30
30
|
```json
|
|
31
31
|
{
|
|
32
32
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
33
|
+
"package_version": "rainskills@0.1.15",
|
|
34
34
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
35
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
36
|
"local_argv": {
|
|
@@ -50,7 +50,7 @@ description: "Use only when the user explicitly asks for final delivery or acces
|
|
|
50
50
|
|
|
51
51
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
52
52
|
|
|
53
|
-
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.
|
|
53
|
+
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.15`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
54
54
|
|
|
55
55
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定本地 launcher。
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ description: "Use only when the user explicitly asks for final delivery or acces
|
|
|
62
62
|
```json
|
|
63
63
|
{
|
|
64
64
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
65
|
-
"package_version": "rainskills@0.1.
|
|
65
|
+
"package_version": "rainskills@0.1.15",
|
|
66
66
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
67
67
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
68
68
|
"local_argv": {
|
|
@@ -18,7 +18,7 @@ description: "Use when a user explicitly asks to sync non-sensitive preview or p
|
|
|
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.15`,且必须与本技能包 `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
|
|
|
@@ -30,7 +30,7 @@ description: "Use when a user explicitly asks to sync non-sensitive preview or p
|
|
|
30
30
|
```json
|
|
31
31
|
{
|
|
32
32
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
33
|
+
"package_version": "rainskills@0.1.15",
|
|
34
34
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
35
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
36
|
"local_argv": {
|
|
@@ -18,7 +18,7 @@ description: "Use only when the user explicitly asks to create the Rainbond app
|
|
|
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.15`,且必须与本技能包 `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
|
|
|
@@ -30,7 +30,7 @@ description: "Use only when the user explicitly asks to create the Rainbond app
|
|
|
30
30
|
```json
|
|
31
31
|
{
|
|
32
32
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
33
|
+
"package_version": "rainskills@0.1.15",
|
|
34
34
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
35
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
36
|
"local_argv": {
|
|
@@ -240,7 +240,7 @@ Follow this transaction in the exact order below:
|
|
|
240
240
|
- capture the helper's `archive_path`, `file_name`, `generated`, and `staging_root` result fields
|
|
241
241
|
- a supported package file is reused directly; a directory is converted to a zip archive by the helper
|
|
242
242
|
2. Call `rainbond_init_package_upload` with Rainbond context only. It must return a non-empty `event_id` and an `upload_request`. Do not continue when either is absent or malformed.
|
|
243
|
-
3. Run the protected local CLI command `package-upload --archive <archive_path> --input - --operation-id <uuid
|
|
243
|
+
3. Run the protected local CLI command `package-upload --archive <archive_path> --input - --operation-id <uuid> --skill-id rainbond-fullstack-bootstrap` using the fixed CLI launcher defined by the active Skill, passing the complete `upload_request` object through stdin as JSON:
|
|
244
244
|
- the CLI resolves the Console origin from the environment already bound to the protected operation; never ask the user for `RAINBOND_URL` and never take it from the current shell
|
|
245
245
|
- do not add `RAINBOND_URL`, JWT, upload credentials, or a Console address to argv or stdin
|
|
246
246
|
- do not invent or rewrite the URL, authorization mode, form field, HTTP method, content type, or timeout; the CLI validates the same-Console-origin upload contract before invoking the local helper
|
|
@@ -50,7 +50,7 @@ description: "Use only when the user explicitly asks for a bounded build, runtim
|
|
|
50
50
|
|
|
51
51
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
52
52
|
|
|
53
|
-
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.
|
|
53
|
+
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.15`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
54
54
|
|
|
55
55
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定本地 launcher。
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ description: "Use only when the user explicitly asks for a bounded build, runtim
|
|
|
62
62
|
```json
|
|
63
63
|
{
|
|
64
64
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
65
|
-
"package_version": "rainskills@0.1.
|
|
65
|
+
"package_version": "rainskills@0.1.15",
|
|
66
66
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
67
67
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
68
68
|
"local_argv": {
|
|
@@ -18,7 +18,7 @@ Node.js 前置检查通过后,每次请求先执行本地 launcher + `["enviro
|
|
|
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.15`,且必须与本技能包 `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
|
|
|
@@ -30,7 +30,7 @@ Node.js 前置检查通过后,每次请求先执行本地 launcher + `["enviro
|
|
|
30
30
|
```json
|
|
31
31
|
{
|
|
32
32
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
33
|
+
"package_version": "rainskills@0.1.15",
|
|
34
34
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
35
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
36
|
"local_argv": {
|
|
@@ -32,7 +32,7 @@ Do not use it to deploy an application to an existing Rainbond. Route those requ
|
|
|
32
32
|
## Workflow
|
|
33
33
|
|
|
34
34
|
1. Read [installation-policy.md](references/installation-policy.md).
|
|
35
|
-
2. Use the installed local launcher `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`; its protected runtime package marker is `rainskills@0.1.
|
|
35
|
+
2. Use the installed local launcher `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`; its protected runtime package marker is `rainskills@0.1.15` and must equal this package's `package.json`. For a Rainskills marker, first validate schema `rainskills.next-action.v1`, action, onboarding id, and the bounded `argv` array, then append that array to the launcher. Never use `latest` or evaluate a shell string from output.
|
|
36
36
|
3. 在任何平台安装命令之前,先执行 launcher + `["runtime", "message", "--id", "private-deployment-location"]`,并原样输出固定的三项部署位置消息。选择 1 后执行带 `["--location", "local", "--mode", "single-node"]` 的 `platform install`;选择 2 后执行带 `["--location", "server"]` 的 `platform install`,由 helper 继续显示固定的服务器类型消息;选择 3 后执行 launcher + `["runtime", "message", "--id", "private-console-origin"]` 并进入已有环境连接,不得执行 `platform install`。若是没有原始业务 intent 的直接平台安装请求,已有环境连接使用受限 `{"type":"environment-add"}` intent。
|
|
37
37
|
4. Let the helper perform one read-only preflight against the already selected local or remote target, then show resources, blockers, and applicable host changes. Never invoke `platform install` without an explicit `--location`; the helper must not ask for the deployment location again.
|
|
38
38
|
5. Obtain explicit confirmation before sending `y` to the waiting process or rerunning the exact command with `--yes`.
|
|
@@ -63,7 +63,7 @@ Do not use it to deploy an application to an existing Rainbond. Route those requ
|
|
|
63
63
|
3、已有 Kubernetes 集群
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
主机集群普通入口接受 `3-100` 台 Linux 主机。第一次进入 configuration 阶段时,必须只生成权限为 `0600`(Windows 为仅当前用户可读写)的受保护 `servers.txt` 并停止等待,不得先生成 `cluster.yaml`、发起 SSH
|
|
66
|
+
主机集群普通入口接受 `3-100` 台 Linux 主机。第一次进入 configuration 阶段时,必须只生成权限为 `0600`(Windows 为仅当前用户可读写)的受保护 `servers.txt` 并停止等待,不得先生成 `cluster.yaml`、发起 SSH、下载或安装。新文件使用简洁中文表单,默认包含三个连续的 `【第 N 台服务器】` 区块,每个区块只显示“公网 IP:”“内网 IP:”“SSH 端口:22”“登录密码:”;用户可复制完整区块扩展节点。逐节点 SSH 端口按实际值填写,不要求为 22。固定消息必须包含可点击的 `servers.txt` 链接、当前系统的打开命令、公网 IP、内网 IP、SSH 端口、登录密码四个字段说明,以及“编辑完成后回复‘已完成’”。普通用户不编辑 `cluster.yaml`、YAML 或节点角色。为兼容已生成的文件,读取时仍接受旧的 `[server-N]`、`public_ip`、`private_ip`、`ssh_port` 和 `password` 格式。
|
|
67
67
|
|
|
68
68
|
用户继续同一 onboarding 时,读取同一份受保护 `servers.txt`(UTF-8,最大 1 MiB)并一次性列出全部可确定的区块、字段、地址、端口、重复节点和 3-100 节点数量问题。存在问题时保持原文件供修改,不创建 YAML 或产生其他副作用。校验通过后,自动生成受保护的 `cluster.yaml` 并自动分配拓扑:前三台承担 etcd/master,全部节点承担 worker/rbd-chaos,前两台承担 rbd-gateway,第一台承担 bootstrap/nfs-server。普通入口固定使用 3 个 etcd 节点;高级导入的 etcd 节点数仍必须是正奇数。在第一次 SSH 动作之前展示不含密码的完整逐节点角色拓扑、bootstrap 和存储摘要,再检查并准备全部节点的 SSH 免密连接。
|
|
69
69
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
## ROI 主机集群策略
|
|
19
19
|
|
|
20
20
|
- 普通入口支持 `3-100` 台 Linux 节点。首次进入时只创建受保护的 `servers.txt` 并停止等待,不生成 `cluster.yaml`、不发起 SSH,也不下载或执行 ROI。固定消息提供可点击链接、macOS/Linux/Windows 对应打开命令、四字段说明和“编辑完成后回复‘已完成’”;普通用户不编辑 `cluster.yaml`、YAML 或节点角色。
|
|
21
|
-
- `servers.txt` 使用 UTF-8
|
|
21
|
+
- `servers.txt` 使用 UTF-8 简洁中文表单,默认包含三个连续的 `【第 N 台服务器】` 区块。每个区块只显示“公网 IP:”“内网 IP:”“SSH 端口:22”“登录密码:”;超过三台时复制完整区块并连续编号。中文字段接受全角或半角冒号;为兼容已生成的文件,读取时仍接受旧的 `[server-N]`、`public_ip`、`private_ip`、`ssh_port` 和 `password` 格式。每台服务器可使用不同端口,SSH 端口范围为 1-65535,不要求为 22。文件最大 1 MiB,最多 100 个节点;一次汇总全部格式、字段、地址、端口、重复 endpoint 和节点数量问题。
|
|
22
22
|
- `servers.txt` 必须是当前用户拥有的普通文件;POSIX 权限精确为 `0600`,Windows 使用仅当前用户可读写的 ACL。通过受保护 descriptor 读取并拒绝符号链接、替换、读取期间变化、非法 UTF-8、NUL/控制字符和超限内容。校验失败时保留同一文件供修改,不创建 YAML 或产生 SSH/下载副作用。
|
|
23
23
|
- 校验成功后自动生成受保护的 `cluster.yaml`:节点依次命名为 node1...nodeN,node1 为 bootstrap;前三台自动承担 etcd/master,全部节点承担 worker/rbd-chaos,前两台承担 rbd-gateway,node1 承担 nfs-server。使用内置 NFS、内置数据库和内置镜像仓库的最小配置。用户不选择或修改节点角色。
|
|
24
24
|
- SSH 前先展示不含凭据的完整逐节点角色拓扑摘要,包括节点数、每节点角色、bootstrap、存储模式和受保护配置路径。密码只保存在受保护的本地 `servers.txt`、自动生成的 `cluster.yaml` 和 ROI 安装/恢复所需的受保护远端配置中。允许内部 `parseHostServerInput` 返回的 host 对象携带 `password`,仅供生成受保护的 `cluster.yaml` 和后续 ROI 安装。对外用户消息、状态、遥测、日志、错误和对外返回值不得反射密码或原始输入,所有对外摘要都不含凭据;日志仍对 password、database、registry、token、secret 等字段脱敏。
|
|
@@ -28,6 +28,18 @@ const MAX_SERVER_INPUT_BYTES = 1024 * 1024;
|
|
|
28
28
|
const MAX_SERVER_INPUT_HOSTS = 100;
|
|
29
29
|
const MAX_SERVER_INPUT_ISSUES = 200;
|
|
30
30
|
const SERVER_INPUT_FIELDS = ["public_ip", "private_ip", "ssh_port", "password"];
|
|
31
|
+
const SERVER_INPUT_FIELD_LABELS = Object.freeze({
|
|
32
|
+
public_ip: "公网 IP",
|
|
33
|
+
private_ip: "内网 IP",
|
|
34
|
+
ssh_port: "SSH 端口",
|
|
35
|
+
password: "登录密码",
|
|
36
|
+
});
|
|
37
|
+
const SERVER_INPUT_CHINESE_FIELDS = new Map([
|
|
38
|
+
["公网IP", "public_ip"],
|
|
39
|
+
["内网IP", "private_ip"],
|
|
40
|
+
["SSH端口", "ssh_port"],
|
|
41
|
+
["登录密码", "password"],
|
|
42
|
+
]);
|
|
31
43
|
const SERVER_INPUT_VALIDATION_ERROR = "servers.txt 解析结果无效";
|
|
32
44
|
const CHILD_OUTPUT_LIMIT_ERROR = "子进程输出超过安全上限";
|
|
33
45
|
const TEMPLATE_ADDRESSES = new Map([
|
|
@@ -76,20 +88,15 @@ function renderHostClusterConfigPrompt({ configPath, platform = process.platform
|
|
|
76
88
|
|
|
77
89
|
function createHostServerInputTemplate() {
|
|
78
90
|
const heading = [
|
|
79
|
-
"#
|
|
80
|
-
"#
|
|
81
|
-
"# 超过三台服务器时,复制完整的 [server-N] 区块并按顺序编号。",
|
|
91
|
+
"# 没有公网 IP 时,公网 IP 和内网 IP 填写相同地址",
|
|
92
|
+
"# 增加服务器时,复制完整的一组并连续编号",
|
|
82
93
|
];
|
|
83
94
|
const sections = Array.from({ length: 3 }, (_, index) => [
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"# SSH 端口:按服务器实际端口填写,不要求为 22",
|
|
90
|
-
"ssh_port=22",
|
|
91
|
-
"# root 密码:保留所有特殊字符;不要添加引号",
|
|
92
|
-
"password=",
|
|
95
|
+
`【第 ${index + 1} 台服务器】`,
|
|
96
|
+
"公网 IP:",
|
|
97
|
+
"内网 IP:",
|
|
98
|
+
"SSH 端口:22",
|
|
99
|
+
"登录密码:",
|
|
93
100
|
].join("\n"));
|
|
94
101
|
return Buffer.from(`${heading.join("\n")}\n\n${sections.join("\n\n")}\n`, "utf8");
|
|
95
102
|
}
|
|
@@ -120,11 +127,7 @@ function renderHostServerInputPrompt({ inputPath, platform = process.platform, i
|
|
|
120
127
|
hostServerInputOpenCommand(inputPath, platform),
|
|
121
128
|
"```",
|
|
122
129
|
"",
|
|
123
|
-
"
|
|
124
|
-
"- 公网 IP:控制端 SSH 地址;没有独立公网 IP 时可与内网 IP 相同。",
|
|
125
|
-
"- 内网 IP:节点通信地址。",
|
|
126
|
-
"- SSH 端口:填写每台服务器的实际端口,不强制为 22。",
|
|
127
|
-
"- root 密码:只写入受保护的本地和远端安装文件,不得发送到聊天。",
|
|
130
|
+
"请在文件中填写每台服务器的公网 IP、内网 IP、SSH 端口和登录密码。",
|
|
128
131
|
"密码只写入受保护的本地和远端安装文件,不会写入聊天、日志或状态;不要填写私钥或 Token。",
|
|
129
132
|
"",
|
|
130
133
|
"编辑完成后回复“已完成”,我会检查全部服务器信息。",
|
|
@@ -321,6 +324,27 @@ function normalizeServerInputAddress(value) {
|
|
|
321
324
|
return value.toLowerCase();
|
|
322
325
|
}
|
|
323
326
|
|
|
327
|
+
function parseServerInputSection(line) {
|
|
328
|
+
const value = line.trim();
|
|
329
|
+
const legacy = /^\[server-(\d+)\]$/.exec(value);
|
|
330
|
+
if (legacy) return { number: Number(legacy[1]), numberText: legacy[1] };
|
|
331
|
+
const chinese = /^【第\s*(\d+)\s*台服务器】$/.exec(value);
|
|
332
|
+
if (chinese) return { number: Number(chinese[1]), numberText: chinese[1] };
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function parseServerInputField(line) {
|
|
337
|
+
const legacy = /^\s*(public_ip|private_ip|ssh_port|password)\s*=(.*)$/.exec(line);
|
|
338
|
+
if (legacy) return { field: legacy[1], rawValue: legacy[2] };
|
|
339
|
+
|
|
340
|
+
const chinese = /^\s*(公网\s*IP|内网\s*IP|SSH\s*端口|登录密码)\s*[::](.*)$/.exec(line);
|
|
341
|
+
if (!chinese) return null;
|
|
342
|
+
return {
|
|
343
|
+
field: SERVER_INPUT_CHINESE_FIELDS.get(chinese[1].replace(/\s/g, "")),
|
|
344
|
+
rawValue: chinese[2],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
324
348
|
function parseHostServerInput(input) {
|
|
325
349
|
const bytes = Buffer.isBuffer(input) ? input : Buffer.from(String(input || ""), "utf8");
|
|
326
350
|
const issues = [];
|
|
@@ -367,7 +391,7 @@ function parseHostServerInput(input) {
|
|
|
367
391
|
const line = lines[lineIndex].endsWith("\r") ? lines[lineIndex].slice(0, -1) : lines[lineIndex];
|
|
368
392
|
if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
|
|
369
393
|
|
|
370
|
-
const sectionMatch =
|
|
394
|
+
const sectionMatch = parseServerInputSection(line);
|
|
371
395
|
if (sectionMatch) {
|
|
372
396
|
if (sections.length >= MAX_SERVER_INPUT_HOSTS) {
|
|
373
397
|
add("servers.txt 最多允许 100 个节点区块");
|
|
@@ -376,57 +400,54 @@ function parseHostServerInput(input) {
|
|
|
376
400
|
continue;
|
|
377
401
|
}
|
|
378
402
|
current = {
|
|
379
|
-
number:
|
|
380
|
-
numberText: sectionMatch
|
|
403
|
+
number: sectionMatch.number,
|
|
404
|
+
numberText: sectionMatch.numberText,
|
|
381
405
|
line: lineNumber,
|
|
382
406
|
fields: new Map(),
|
|
383
407
|
};
|
|
384
408
|
sections.push(current);
|
|
385
409
|
continue;
|
|
386
410
|
}
|
|
387
|
-
|
|
411
|
+
const trimmedLine = line.trim();
|
|
412
|
+
if ((trimmedLine.startsWith("[") && trimmedLine.endsWith("]"))
|
|
413
|
+
|| (trimmedLine.startsWith("【") && trimmedLine.endsWith("】"))) {
|
|
388
414
|
add(`第 ${lineNumber} 行是未知区块`);
|
|
389
415
|
current = null;
|
|
390
416
|
continue;
|
|
391
417
|
}
|
|
392
418
|
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
419
|
+
const parsedField = parseServerInputField(line);
|
|
420
|
+
if (!parsedField) {
|
|
395
421
|
add(`第 ${lineNumber} 行字段格式无效`);
|
|
396
422
|
continue;
|
|
397
423
|
}
|
|
398
424
|
if (!current) {
|
|
399
|
-
add(`第 ${lineNumber}
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
const field = line.slice(0, separator);
|
|
403
|
-
if (!SERVER_INPUT_FIELDS.includes(field)) {
|
|
404
|
-
add(`第 ${lineNumber} 行包含未知字段`);
|
|
425
|
+
add(`第 ${lineNumber} 行字段不属于任何服务器区块`);
|
|
405
426
|
continue;
|
|
406
427
|
}
|
|
428
|
+
const { field, rawValue } = parsedField;
|
|
407
429
|
if (current.fields.has(field)) {
|
|
408
|
-
add(`第 ${lineNumber}
|
|
430
|
+
add(`第 ${lineNumber} 行的${SERVER_INPUT_FIELD_LABELS[field]}字段重复`);
|
|
409
431
|
continue;
|
|
410
432
|
}
|
|
411
|
-
const rawValue = line.slice(separator + 1);
|
|
412
433
|
current.fields.set(field, field === "password" ? rawValue : rawValue.trim());
|
|
413
434
|
}
|
|
414
435
|
|
|
415
|
-
if (sections.length < 3) add("servers.txt 至少需要 3
|
|
436
|
+
if (sections.length < 3) add("servers.txt 至少需要 3 个服务器区块");
|
|
416
437
|
const sectionNumbers = new Set();
|
|
417
438
|
for (let index = 0; index < sections.length; index += 1) {
|
|
418
439
|
const section = sections[index];
|
|
419
|
-
const label =
|
|
420
|
-
if (sectionNumbers.has(section.number)) add(`第 ${section.line}
|
|
440
|
+
const label = `第 ${section.number} 台服务器`;
|
|
441
|
+
if (sectionNumbers.has(section.number)) add(`第 ${section.line} 行的${label}区块重复`);
|
|
421
442
|
sectionNumbers.add(section.number);
|
|
422
|
-
if (section.numberText !== String(index + 1)) add(`第 ${section.line}
|
|
443
|
+
if (section.numberText !== String(index + 1)) add(`第 ${section.line} 行的区块编号必须从第 1 台服务器开始连续`);
|
|
423
444
|
for (const field of SERVER_INPUT_FIELDS) {
|
|
424
445
|
if (!section.fields.has(field)) {
|
|
425
|
-
add(`${label}
|
|
446
|
+
add(`${label}的${SERVER_INPUT_FIELD_LABELS[field]}字段缺失`);
|
|
426
447
|
} else if (field === "password" && section.fields.get(field).trim() === "") {
|
|
427
|
-
add(`${label}
|
|
448
|
+
add(`${label}的登录密码未填写或只有空白`);
|
|
428
449
|
} else if (field !== "password" && section.fields.get(field) === "") {
|
|
429
|
-
add(`${label}
|
|
450
|
+
add(`${label}的${SERVER_INPUT_FIELD_LABELS[field]}未填写`);
|
|
430
451
|
}
|
|
431
452
|
}
|
|
432
453
|
}
|
|
@@ -442,7 +463,7 @@ function parseHostServerInput(input) {
|
|
|
442
463
|
for (let index = 0; index < sections.length; index += 1) {
|
|
443
464
|
const section = sections[index];
|
|
444
465
|
const host = hosts[index];
|
|
445
|
-
const label =
|
|
466
|
+
const label = `第 ${section.number} 台服务器`;
|
|
446
467
|
const publicIp = section.fields.get("public_ip");
|
|
447
468
|
const privateIp = section.fields.get("private_ip");
|
|
448
469
|
const portText = section.fields.get("ssh_port");
|
|
@@ -456,16 +477,16 @@ function parseHostServerInput(input) {
|
|
|
456
477
|
const privateValid = privateNormalized !== null;
|
|
457
478
|
const portValid = typeof portText === "string" && /^\d+$/.test(portText)
|
|
458
479
|
&& Number.isInteger(host.sshPort) && host.sshPort >= 1 && host.sshPort <= 65535;
|
|
459
|
-
if (typeof publicIp === "string" && publicIp !== "" && !publicValid) add(`${label}
|
|
460
|
-
if (typeof privateIp === "string" && privateIp !== "" && !privateValid) add(`${label}
|
|
461
|
-
if (typeof portText === "string" && portText !== "" && !portValid) add(`${label}
|
|
480
|
+
if (typeof publicIp === "string" && publicIp !== "" && !publicValid) add(`${label}的公网 IP 地址无效`);
|
|
481
|
+
if (typeof privateIp === "string" && privateIp !== "" && !privateValid) add(`${label}的内网 IP 地址无效`);
|
|
482
|
+
if (typeof portText === "string" && portText !== "" && !portValid) add(`${label}的 SSH 端口必须是 1 到 65535 的整数`);
|
|
462
483
|
if (publicValid && portValid) {
|
|
463
484
|
const endpoint = `${publicNormalized}\0${host.sshPort}`;
|
|
464
|
-
if (sshEndpoints.has(endpoint)) add(`${label}
|
|
485
|
+
if (sshEndpoints.has(endpoint)) add(`${label}的 SSH 地址和端口与前面的区块重复`);
|
|
465
486
|
sshEndpoints.add(endpoint);
|
|
466
487
|
}
|
|
467
488
|
if (privateValid) {
|
|
468
|
-
if (privateAddresses.has(privateNormalized)) add(`${label}
|
|
489
|
+
if (privateAddresses.has(privateNormalized)) add(`${label}的内网 IP 与前面的区块重复`);
|
|
469
490
|
privateAddresses.add(privateNormalized);
|
|
470
491
|
}
|
|
471
492
|
}
|
|
@@ -2680,7 +2680,7 @@ async function waitForHostClusterConfiguration({ write = (value) => process.stdo
|
|
|
2680
2680
|
writeUserMessage(
|
|
2681
2681
|
write,
|
|
2682
2682
|
"platform.host-cluster-configuration",
|
|
2683
|
-
"多节点主机集群模式已选择。Rainskills
|
|
2683
|
+
"多节点主机集群模式已选择。Rainskills 将生成受保护的中文 servers.txt 表单;填写服务器信息后,会自动生成集群配置。",
|
|
2684
2684
|
);
|
|
2685
2685
|
return { waiting: true };
|
|
2686
2686
|
}
|
|
@@ -20,7 +20,7 @@ Node.js 前置检查通过后,平台只读查询只执行一次本地 `rainski
|
|
|
20
20
|
|
|
21
21
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
22
22
|
|
|
23
|
-
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.
|
|
23
|
+
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.15`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
24
24
|
|
|
25
25
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。仅在用户明确指定环境时使用 `environment list`;`runtime message` 仍使用本地 launcher。本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。
|
|
26
26
|
|
|
@@ -32,7 +32,7 @@ Node.js 前置检查通过后,平台只读查询只执行一次本地 `rainski
|
|
|
32
32
|
```json
|
|
33
33
|
{
|
|
34
34
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
35
|
-
"package_version": "rainskills@0.1.
|
|
35
|
+
"package_version": "rainskills@0.1.15",
|
|
36
36
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
37
37
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
38
38
|
"local_argv": {
|
|
@@ -18,7 +18,7 @@ description: "Use only when the user explicitly asks to initialize or link a loc
|
|
|
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.15`,且必须与本技能包 `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
|
|
|
@@ -30,7 +30,7 @@ description: "Use only when the user explicitly asks to initialize or link a loc
|
|
|
30
30
|
```json
|
|
31
31
|
{
|
|
32
32
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
33
|
+
"package_version": "rainskills@0.1.15",
|
|
34
34
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
35
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
36
|
"local_argv": {
|
|
@@ -18,7 +18,7 @@ description: "Use when the user explicitly asks to install a local or cloud Rain
|
|
|
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.15`,且必须与本技能包 `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
|
|
|
@@ -30,7 +30,7 @@ description: "Use when the user explicitly asks to install a local or cloud Rain
|
|
|
30
30
|
```json
|
|
31
31
|
{
|
|
32
32
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
33
|
-
"package_version": "rainskills@0.1.
|
|
33
|
+
"package_version": "rainskills@0.1.15",
|
|
34
34
|
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
35
35
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
36
36
|
"local_argv": {
|