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 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.13`,不会执行浮动的 `@latest` 业务代码。新版本原子刷新已经安装的 Rainskills Skills;当前业务继续使用启动时已经加载的版本,最迟从下一条新任务开始使用新版。npm 超时、版本检查失败、安装位置不安全或文件迁移失败时会保留旧版本,且不会阻塞或改变当前操作。
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.13`)。
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>`。
@@ -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 remaining = [
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
- if (command === "status" && remaining.length === 1) return { command, operationId };
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, operationId };
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], operationId };
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], operationId };
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], operationId };
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], operationId };
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], operationId };
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, { scoped = false } = {}) => {
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(operationScoped ? { operationId } : {});
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 manager = runtimeStateManager || require(
909
- "../rainbond-platform-installer/scripts/runtime-state.js"
910
- ).createRuntimeStateManager();
911
- manager.assertContinuationEligible(args[3]);
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");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainskills",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Install the complete Rainskills AI deployment skill suite as one product.",
5
5
  "author": {
6
6
  "name": "Goodrain",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainskills",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Install the complete Rainskills AI deployment skill suite as one product.",
5
5
  "author": {
6
6
  "name": "Goodrain",
@@ -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.13 <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.
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.13`)。
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainskills",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",
@@ -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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止使用 `rainskills@latest`,禁止拼接或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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>` using the fixed CLI launcher defined by the active Skill, passing the complete `upload_request` object through stdin as JSON:
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest`,禁止拼接或执行 shell 字符串。
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.13",
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.13` 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.
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、下载或安装。该文件带中文标题、中文注释和三个节点区块;用户可复制完整区块扩展节点,只填写每台服务器的公网 IP、内网 IPSSH 端口和 root 密码。逐节点 SSH 端口按实际值填写,不要求为 22。固定消息必须包含可点击的 `servers.txt` 链接、当前系统的打开命令、`public_ip`、`private_ip`、`ssh_port`、`password` 四字段说明,以及“编辑完成后回复‘已完成’”。普通用户不编辑 `cluster.yaml`、YAML 或节点角色。
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 文本,包含中文标题、中文注释和三个连续的 `[server-N]` 区块。每个区块只允许 `public_ip`(公网 IP)、`private_ip`(内网 IP)、`ssh_port`(逐节点 SSH 端口)和 `password`(root 密码);超过三台时复制完整区块并连续编号。每台服务器可使用不同端口,SSH 端口范围为 1-65535,不要求为 22。文件最大 1 MiB,最多 100 个节点;一次汇总全部格式、字段、地址、端口、重复 endpoint 和节点数量问题。
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
- "# Rainbond 多节点服务器信息",
80
- "# 密码只写入受保护的本地和远端安装文件,不会写入聊天、日志或状态;不要填写私钥或 Token。",
81
- "# 超过三台服务器时,复制完整的 [server-N] 区块并按顺序编号。",
91
+ "# 没有公网 IP 时,公网 IP 和内网 IP 填写相同地址",
92
+ "# 增加服务器时,复制完整的一组并连续编号",
82
93
  ];
83
94
  const sections = Array.from({ length: 3 }, (_, index) => [
84
- `[server-${index + 1}]`,
85
- "# 公网 IP:控制端通过 SSH 访问该服务器的地址;没有独立公网 IP 时可与内网 IP 相同",
86
- "public_ip=",
87
- "# 内网 IP:集群节点之间通信使用的地址",
88
- "private_ip=",
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
- "请为每台服务器填写 public_ipprivate_ip、ssh_port 和 password。",
124
- "- 公网 IP:控制端 SSH 地址;没有独立公网 IP 时可与内网 IP 相同。",
125
- "- 内网 IP:节点通信地址。",
126
- "- SSH 端口:填写每台服务器的实际端口,不强制为 22。",
127
- "- root 密码:只写入受保护的本地和远端安装文件,不得发送到聊天。",
130
+ "请在文件中填写每台服务器的公网 IP、内网 IPSSH 端口和登录密码。",
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 = /^\[server-(\d+)\]$/.exec(line);
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: Number(sectionMatch[1]),
380
- numberText: sectionMatch[1],
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
- if (line.startsWith("[") && line.endsWith("]")) {
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 separator = line.indexOf("=");
394
- if (separator < 0) {
419
+ const parsedField = parseServerInputField(line);
420
+ if (!parsedField) {
395
421
  add(`第 ${lineNumber} 行字段格式无效`);
396
422
  continue;
397
423
  }
398
424
  if (!current) {
399
- add(`第 ${lineNumber} 行字段不属于任何 server 区块`);
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} 行的 ${field} 字段重复`);
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 = `server-${section.number}`;
420
- if (sectionNumbers.has(section.number)) add(`第 ${section.line} 行的 ${label} 区块重复`);
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} 行的区块编号必须从 server-1 开始连续`);
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} 的 ${field} 字段缺失`);
446
+ add(`${label}的${SERVER_INPUT_FIELD_LABELS[field]}字段缺失`);
426
447
  } else if (field === "password" && section.fields.get(field).trim() === "") {
427
- add(`${label} 的 password 未填写或只有空白`);
448
+ add(`${label}的登录密码未填写或只有空白`);
428
449
  } else if (field !== "password" && section.fields.get(field) === "") {
429
- add(`${label} 的 ${field} 字段未填写`);
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 = `server-${section.number}`;
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} public_ip 地址无效`);
460
- if (typeof privateIp === "string" && privateIp !== "" && !privateValid) add(`${label} private_ip 地址无效`);
461
- if (typeof portText === "string" && portText !== "" && !portValid) add(`${label} ssh_port 必须是 1 到 65535 的整数`);
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} 的 SSH endpoint 与前面的区块重复`);
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} private_ip 与前面的区块重复`);
489
+ if (privateAddresses.has(privateNormalized)) add(`${label}的内网 IP 与前面的区块重复`);
469
490
  privateAddresses.add(privateNormalized);
470
491
  }
471
492
  }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
 
3
3
  module.exports = Object.freeze({
4
- version: "0.1.13",
4
+ version: "0.1.15",
5
5
  });
@@ -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 将生成带中文说明的受保护 servers.txt;填写服务器信息后,会自动生成集群配置。",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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.13`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
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.13",
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": {