rainskills 0.1.26 → 0.1.28
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 +5 -1
- package/bin/rainskills-tools.js +67 -7
- package/bin/rainskills.js +24 -5
- package/install.sh +24 -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 +6 -2
- package/package.json +3 -2
- package/rainbond-app-assistant/SKILL.md +3 -1
- package/rainbond-app-assistant/evals/01-linked-topology-missing.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/02-linked-topology-building.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/03-runtime-healthy-delivery-unknown.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/04-delivered-stop-without-promotion.expected.yaml +1 -1
- package/rainbond-app-assistant/evals/05-delivered-explicit-dev-to-test.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/06-manual-validation-stop.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/07-cluster-capacity-blocked.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/08-code-build-handoff-stop.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/09-source-backed-failure-no-fallback.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/10-identity-ambiguous-stop.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/11-delivered-with-proxy-and-local-binding.expected.yaml +1 -1
- package/rainbond-app-assistant/evals/12-complex-suite-needs-descriptor-stop.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/13-explicit-git-url-locks-source.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/14-strategy-switch-cleans-residue.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/15-turn-discipline-reuse-alias.expected.yaml +1 -0
- package/rainbond-app-assistant/evals/16-source-delivered-manual-validation.expected.yaml +1 -1
- package/rainbond-app-assistant/references/operational-reference.md +1 -1
- package/rainbond-app-assistant/references/output-contract.md +8 -23
- package/rainbond-app-assistant/references/runtime-gate.md +6 -2
- package/rainbond-app-assistant/references/workflow-rules.md +1 -1
- package/rainbond-app-assistant/scripts/validate_app_assistant_output.py +10 -66
- package/rainbond-app-version-assistant/SKILL.md +17 -4
- package/rainbond-delivery-verifier/SKILL.md +5 -1
- package/rainbond-delivery-verifier/evals/01-delivered-verified.expected.yaml +1 -0
- package/rainbond-delivery-verifier/evals/02-delivered-manual-validation.expected.yaml +1 -0
- package/rainbond-delivery-verifier/evals/03-partially-delivered-building.expected.yaml +1 -0
- package/rainbond-delivery-verifier/evals/04-blocked-capacity.expected.yaml +1 -0
- package/rainbond-delivery-verifier/evals/05-blocked-no-usable-url.expected.yaml +1 -0
- package/rainbond-delivery-verifier/evals/06-blocked-runtime-unhealthy.expected.yaml +1 -0
- package/rainbond-delivery-verifier/scripts/validate_delivery_verifier_output.py +12 -0
- package/rainbond-env-sync/SKILL.md +17 -4
- package/rainbond-fullstack-bootstrap/SKILL.md +43 -4
- package/rainbond-fullstack-bootstrap/evals/01-reuse-only.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/02-deferred-dependency.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/03-capacity-blocked.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/04-template-skip.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/05-source-ambiguity-stop.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/06-external-artifact-unreachable.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/07-provider-connection-contract.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/08-explicit-dependency-after-manual-components.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/09-inferred-provider-consumer-gate.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/10-compose-service-name-not-hostname.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/11-reverse-proxy-not-dropped.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/12-optional-services-disclose-not-deploy.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/13-env-reference-dependency-completeness.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/14-client-package-upload.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/15-unfinished-dockerfile-recovery.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/16-complete-cnb-recreate-confirmed.expected.yaml +1 -0
- package/rainbond-fullstack-bootstrap/evals/17-existing-app-mysql-preflight-stop.expected.yaml +29 -0
- package/rainbond-fullstack-bootstrap/evals/17-existing-app-mysql-preflight-stop.prompt.md +1 -0
- package/rainbond-fullstack-bootstrap/evals/17-existing-app-mysql-preflight-stop.response.md +42 -0
- package/rainbond-fullstack-bootstrap/modules/30-creation-rules.md +4 -1
- package/rainbond-fullstack-bootstrap/modules/70-output-contract.md +2 -2
- package/rainbond-fullstack-bootstrap/references/quick-reference.md +2 -2
- package/rainbond-fullstack-bootstrap/scripts/validate_bootstrap_output.py +12 -0
- package/rainbond-fullstack-troubleshooter/SKILL.md +5 -1
- package/rainbond-fullstack-troubleshooter/evals/01-source-build-failed.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/02-source-build-still-running.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/03-dependency-missing.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/04-wrong-connection-values.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/05-env-naming-incompatibility.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/06-frontend-access-path-issue.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/07-cluster-capacity-blocked.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/08-code-or-build-handoff-needed.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/09-external-artifact-unreachable.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/evals/10-config-file-configmap-missing.expected.yaml +1 -0
- package/rainbond-fullstack-troubleshooter/references/output-contract.md +3 -3
- package/rainbond-fullstack-troubleshooter/scripts/validate_troubleshoot_output.py +12 -0
- package/rainbond-opensource-app-deploy/SKILL.md +1 -1
- package/rainbond-opensource-app-deploy/references/runtime-gate.md +5 -1
- package/rainbond-platform-installer/SKILL.md +1 -1
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-installer/scripts/runtime-state.js +4 -2
- package/rainbond-platform-installer/scripts/windows-client-config.js +68 -43
- package/rainbond-platform-query/SKILL.md +5 -1
- package/rainbond-project-init/SKILL.md +21 -8
- package/rainbond-project-init/references/operational-reference.md +1 -1
- package/rainbond-project-init/references/output-contract.md +6 -6
- package/rainbond-template-installer/SKILL.md +17 -4
- package/scripts/validate_customer_output.py +59 -0
package/README.md
CHANGED
|
@@ -223,7 +223,7 @@ node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-
|
|
|
223
223
|
|
|
224
224
|
Rainskills 会在用户下一次发起业务动作时,由本地运行时立即返回环境查询结果,并另行启动后台任务静默检查更新。更新只跟随 npm `latest` 指向的正式版。当前版本是 RC 或其他预发布版本时不会查询、不会自动升级;npm 上的新 RC 版本也不参与正式版自动升级。
|
|
225
225
|
|
|
226
|
-
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.
|
|
226
|
+
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.28`,不会执行浮动的 `@latest` 业务代码。新版本原子刷新已经安装的 Rainskills Skills;当前业务继续使用启动时已经加载的版本,最迟从下一条新任务开始使用新版。npm 超时、版本检查失败、安装位置不安全或文件迁移失败时会保留旧版本,且不会阻塞或改变当前操作。
|
|
227
227
|
|
|
228
228
|
升级只更新 Rainskills 自身,不触发 Rainbond 安装、运行环境选择、登录授权或重新对接,也不会新增 Agent MCP 配置。更新内容仅包括 Skills 和本地 CLI。原始业务操作会继续执行;只有该业务操作本身需要运行环境时,才按既有门禁检查当前连接。可用连接直接复用,401 只重新授权一次,403 立即停止,从未连接过运行环境时才进入环境选择。
|
|
229
229
|
|
package/SKILL.md
CHANGED
|
@@ -44,7 +44,11 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
44
44
|
|
|
45
45
|
`runtime connect` 的 Device Flow 不依赖 stdin TTY;Agent 必须执行固定 argv 并保持进程附着直到授权完成。能打开本机浏览器时由连接器自动跳转,SSH、容器等无浏览器场景原样展示授权地址并继续轮询。只有 Rainbond 不支持 Device Flow 且进入旧版 loopback 手动粘贴时才需要交互终端;不得要求用户在聊天中粘贴 JWT。
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
授权命令是同步门禁。执行工具返回“进程仍在运行”或会话 ID 时,必须只等待或轮询同一个命令会话;在该会话结束前,禁止读取专项 Skill、解析 context、调用业务 CLI 或执行任何后续业务步骤。浏览器页面显示成功不代表连接完成;只有原命令退出码为 0,并输出 `rainskills.runtime-connect-result.v1` 且 `state=connected`,才可继续。不得另起 `runtime status` 猜测完成,也不得重复提示用户授权。
|
|
48
|
+
|
|
49
|
+
Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id` 反复调用 `write_stdin`(空输入轮询),直到工具返回 `exit_code`。连接器输出 `[RAINSKILLS_AGENT_WAIT_REQUIRED:runtime-connect]` 后进入上述轮询;看到 `[RAINSKILLS_AGENT_WAIT_COMPLETE:runtime-connect]` 后仍须继续轮询,直到取得退出码和最终 JSON。
|
|
50
|
+
|
|
51
|
+
Rainskills 只保存一个全局运行环境,不维护环境列表、名称、默认值或环境 ID。使用固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.28`):
|
|
48
52
|
|
|
49
53
|
- 状态:执行 `runtime status --json`。
|
|
50
54
|
- 首次连接:执行 `runtime connect <target> --saas` 或 `runtime connect <target> --rainbond-url <Console origin>`。
|
package/bin/rainskills-tools.js
CHANGED
|
@@ -29,6 +29,9 @@ const CONTEXT_HINT_FIELDS = new Set(["team_id", "team_name", "region_name"]);
|
|
|
29
29
|
const SKILL_CONTENT_MAX_BYTES = 128 * 1024;
|
|
30
30
|
const SKILL_MANIFEST_MAX_BYTES = 4 * 1024 * 1024;
|
|
31
31
|
const SENSITIVE_RESPONSE_KEY_PATTERN = /(?:authorization|jwt|token|password|secret|credential|private[_-]?key|key[_-]?file|certificate|cert[_-]?file|ssl[_-]?ca[_-]?cert)/i;
|
|
32
|
+
const SENSITIVE_ENV_NAME_PATTERN = /(?:^|_)(?:PASSWORD|PASSWD|PASS|SECRET|TOKEN|API_?KEY|PRIVATE_?KEY|CREDENTIALS?|AUTH(?:ORIZATION)?|COOKIE|CERT(?:IFICATE)?)(?:_|$)/i;
|
|
33
|
+
const ENV_NAME_FIELDS = Object.freeze(["attr_name", "name", "note", "env_name"]);
|
|
34
|
+
const ENV_VALUE_FIELDS = new Set(["attr_value", "value"]);
|
|
32
35
|
const RISK_POLICY = Object.freeze({
|
|
33
36
|
version: "1",
|
|
34
37
|
readPrefixes: [
|
|
@@ -37,6 +40,12 @@ const RISK_POLICY = Object.freeze({
|
|
|
37
40
|
],
|
|
38
41
|
destructiveFragments: ["delete", "remove", "purge", "destroy"],
|
|
39
42
|
});
|
|
43
|
+
const MIXED_TOOL_OPERATION_POLICY = Object.freeze({
|
|
44
|
+
rainbond_manage_component_dependency: Object.freeze({
|
|
45
|
+
readOperations: Object.freeze(["summary"]),
|
|
46
|
+
destructiveOperations: Object.freeze(["delete"]),
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
40
49
|
const CALL_OPTION_DEFINITIONS = Object.freeze({
|
|
41
50
|
"--confirm": Object.freeze({ property: "confirmation", pattern: OPERATION_ID_PATTERN }),
|
|
42
51
|
"--skill-id": Object.freeze({ property: "skillId", pattern: SKILL_ID_PATTERN }),
|
|
@@ -489,8 +498,16 @@ function loadSkillBinding(config, skillId, rootSkillId) {
|
|
|
489
498
|
};
|
|
490
499
|
}
|
|
491
500
|
|
|
492
|
-
function classifyTool(toolName) {
|
|
501
|
+
function classifyTool(toolName, argumentsValue = {}) {
|
|
493
502
|
const normalized = String(toolName || "").toLowerCase();
|
|
503
|
+
const operation = typeof argumentsValue.operation === "string"
|
|
504
|
+
? argumentsValue.operation.trim().toLowerCase()
|
|
505
|
+
: "";
|
|
506
|
+
const mixedPolicy = MIXED_TOOL_OPERATION_POLICY[normalized];
|
|
507
|
+
if (mixedPolicy) {
|
|
508
|
+
if (mixedPolicy.destructiveOperations.includes(operation)) return "destructive";
|
|
509
|
+
if (mixedPolicy.readOperations.includes(operation)) return "read";
|
|
510
|
+
}
|
|
494
511
|
if (RISK_POLICY.destructiveFragments.some((fragment) => normalized.includes(fragment))) {
|
|
495
512
|
return "destructive";
|
|
496
513
|
}
|
|
@@ -679,12 +696,47 @@ function fitOutput(value) {
|
|
|
679
696
|
};
|
|
680
697
|
}
|
|
681
698
|
|
|
682
|
-
function
|
|
699
|
+
function redactString(value, secrets) {
|
|
700
|
+
let safe = value;
|
|
701
|
+
for (const secret of secrets) {
|
|
702
|
+
if (secret) safe = safe.split(secret).join("[REDACTED]");
|
|
703
|
+
}
|
|
704
|
+
return safe;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function hasSensitiveEnvName(value) {
|
|
708
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
709
|
+
return ENV_NAME_FIELDS.some((field) => (
|
|
710
|
+
typeof value[field] === "string" && SENSITIVE_ENV_NAME_PATTERN.test(value[field])
|
|
711
|
+
));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function collectSensitiveArgumentRedactions(value, output = []) {
|
|
715
|
+
if (Array.isArray(value)) {
|
|
716
|
+
for (const item of value) collectSensitiveArgumentRedactions(item, output);
|
|
717
|
+
return output;
|
|
718
|
+
}
|
|
719
|
+
if (!value || typeof value !== "object") return output;
|
|
720
|
+
|
|
721
|
+
const sensitiveEnv = hasSensitiveEnvName(value);
|
|
722
|
+
for (const [key, item] of Object.entries(value)) {
|
|
723
|
+
const sensitiveField = SENSITIVE_RESPONSE_KEY_PATTERN.test(key)
|
|
724
|
+
|| SENSITIVE_ENV_NAME_PATTERN.test(key)
|
|
725
|
+
|| (sensitiveEnv && ENV_VALUE_FIELDS.has(key));
|
|
726
|
+
if (sensitiveField && typeof item === "string" && item.length >= 4) output.push(item);
|
|
727
|
+
collectSensitiveArgumentRedactions(item, output);
|
|
728
|
+
}
|
|
729
|
+
return output;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function sanitizeToolResult(value, pathParts = [], sensitiveArguments = []) {
|
|
683
733
|
if (Array.isArray(value)) {
|
|
684
|
-
return value.map((item) => sanitizeToolResult(item, pathParts));
|
|
734
|
+
return value.map((item) => sanitizeToolResult(item, pathParts, sensitiveArguments));
|
|
685
735
|
}
|
|
736
|
+
if (typeof value === "string") return redactString(value, sensitiveArguments);
|
|
686
737
|
if (!value || typeof value !== "object") return value;
|
|
687
738
|
const safe = {};
|
|
739
|
+
const sensitiveEnv = hasSensitiveEnvName(value);
|
|
688
740
|
for (const [key, item] of Object.entries(value)) {
|
|
689
741
|
// Console destructive operations may return this one-time control value.
|
|
690
742
|
// It is not a credential and must remain available for the confirmation flow.
|
|
@@ -695,7 +747,11 @@ function sanitizeToolResult(value, pathParts = []) {
|
|
|
695
747
|
if (key !== "confirmation_token"
|
|
696
748
|
&& !isPackageUploadAuthMode
|
|
697
749
|
&& SENSITIVE_RESPONSE_KEY_PATTERN.test(key)) continue;
|
|
698
|
-
|
|
750
|
+
if (sensitiveEnv && ENV_VALUE_FIELDS.has(key)) {
|
|
751
|
+
safe[key] = "[REDACTED]";
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
safe[key] = sanitizeToolResult(item, [...pathParts, key], sensitiveArguments);
|
|
699
755
|
}
|
|
700
756
|
return safe;
|
|
701
757
|
}
|
|
@@ -1205,14 +1261,14 @@ async function execute(command, config) {
|
|
|
1205
1261
|
};
|
|
1206
1262
|
}
|
|
1207
1263
|
|
|
1208
|
-
const
|
|
1264
|
+
const argumentsValue = command.argumentsValue || readArguments(command.input);
|
|
1265
|
+
const operationClass = classifyTool(command.toolName, argumentsValue);
|
|
1209
1266
|
if (command.command === "read" && operationClass !== "read") {
|
|
1210
1267
|
throw new BridgeError(
|
|
1211
1268
|
"read is read-only and rejects tools classified as write or destructive",
|
|
1212
1269
|
EXIT.USAGE
|
|
1213
1270
|
);
|
|
1214
1271
|
}
|
|
1215
|
-
const argumentsValue = command.argumentsValue || readArguments(command.input);
|
|
1216
1272
|
let operation = null;
|
|
1217
1273
|
let skillBinding = null;
|
|
1218
1274
|
if (operationClass !== "read") {
|
|
@@ -1274,7 +1330,11 @@ async function execute(command, config) {
|
|
|
1274
1330
|
throw new BridgeError("Rainbond tool response lacks structuredContent", EXIT.TRANSPORT);
|
|
1275
1331
|
}
|
|
1276
1332
|
if (operation) updateOperation(config, operation, "succeeded");
|
|
1277
|
-
return fitOutput(sanitizeToolResult(
|
|
1333
|
+
return fitOutput(sanitizeToolResult(
|
|
1334
|
+
result.structuredContent,
|
|
1335
|
+
[],
|
|
1336
|
+
collectSensitiveArgumentRedactions(argumentsValue)
|
|
1337
|
+
));
|
|
1278
1338
|
} catch (error) {
|
|
1279
1339
|
if (operation) {
|
|
1280
1340
|
updateOperation(config, operation, error.exitCode === EXIT.TRANSPORT ? "unknown" : "failed");
|
package/bin/rainskills.js
CHANGED
|
@@ -167,11 +167,29 @@ function runtimeConnectRetryAction(options, origin) {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
function runAttached(executable, args, {
|
|
170
|
+
function runAttached(executable, args, {
|
|
171
|
+
env = process.env,
|
|
172
|
+
signalSource = process,
|
|
173
|
+
spawnImpl = spawn,
|
|
174
|
+
} = {}) {
|
|
171
175
|
return new Promise((resolve, reject) => {
|
|
172
|
-
const child =
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
const child = spawnImpl(executable, args, { env, stdio: "inherit" });
|
|
177
|
+
const forwardSigint = () => child.kill("SIGINT");
|
|
178
|
+
const forwardSigterm = () => child.kill("SIGTERM");
|
|
179
|
+
const removeSignalListeners = () => {
|
|
180
|
+
signalSource.removeListener("SIGINT", forwardSigint);
|
|
181
|
+
signalSource.removeListener("SIGTERM", forwardSigterm);
|
|
182
|
+
};
|
|
183
|
+
signalSource.on("SIGINT", forwardSigint);
|
|
184
|
+
signalSource.on("SIGTERM", forwardSigterm);
|
|
185
|
+
child.once("error", () => {
|
|
186
|
+
removeSignalListeners();
|
|
187
|
+
reject(new Error("无法启动 RainSkills 运行环境连接器"));
|
|
188
|
+
});
|
|
189
|
+
child.once("close", (code, signal) => {
|
|
190
|
+
removeSignalListeners();
|
|
191
|
+
resolve({ code, signal });
|
|
192
|
+
});
|
|
175
193
|
});
|
|
176
194
|
}
|
|
177
195
|
|
|
@@ -416,7 +434,7 @@ async function runBuiltin(args, {
|
|
|
416
434
|
intent: null,
|
|
417
435
|
operation_id: operationId,
|
|
418
436
|
};
|
|
419
|
-
manager.startConnecting(connection);
|
|
437
|
+
manager.startConnecting(connection, { replaceExisting: true });
|
|
420
438
|
try {
|
|
421
439
|
const invocation = runtimeConnectionInvocation(options, inspection.origin);
|
|
422
440
|
let completedWithCredential = false;
|
|
@@ -774,6 +792,7 @@ module.exports = {
|
|
|
774
792
|
classifyNodeMajor,
|
|
775
793
|
parseRuntimeConnectArgs,
|
|
776
794
|
resolveInvocation,
|
|
795
|
+
runAttached,
|
|
777
796
|
runAutoUpdatePhase,
|
|
778
797
|
runBuiltin,
|
|
779
798
|
runtimeChildEnvironment,
|
package/install.sh
CHANGED
|
@@ -1772,6 +1772,20 @@ write_device_token_request_body() {
|
|
|
1772
1772
|
chmod 600 "$body_file"
|
|
1773
1773
|
}
|
|
1774
1774
|
|
|
1775
|
+
assert_device_authorization_operation_current() {
|
|
1776
|
+
local base_url="$1"
|
|
1777
|
+
[[ "${RAINSKILLS_RUNTIME_CONNECT_COMPLETION:-0}" == "1" ]] || return 0
|
|
1778
|
+
valid_runtime_operation_id "${RAINSKILLS_RUNTIME_OPERATION_ID:-}" || return 1
|
|
1779
|
+
|
|
1780
|
+
local environment_kind="private"
|
|
1781
|
+
[[ "$DEPLOYMENT_MODE_INPUT" == "saas" ]] && environment_kind="saas"
|
|
1782
|
+
node "$SCRIPT_DIR/bin/rainskills.js" runtime assert-connect \
|
|
1783
|
+
--onboarding-id "$RAINSKILLS_RUNTIME_OPERATION_ID" \
|
|
1784
|
+
--target "$TARGET" \
|
|
1785
|
+
--environment-kind "$environment_kind" \
|
|
1786
|
+
--console-origin "$base_url" >/dev/null 2>&1
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1775
1789
|
poll_device_authorization() {
|
|
1776
1790
|
local base_url="$1"
|
|
1777
1791
|
local interval="$DEVICE_FLOW_INTERVAL"
|
|
@@ -1790,6 +1804,10 @@ poll_device_authorization() {
|
|
|
1790
1804
|
|
|
1791
1805
|
while true; do
|
|
1792
1806
|
device_flow_sleep "$interval"
|
|
1807
|
+
if ! assert_device_authorization_operation_current "$base_url"; then
|
|
1808
|
+
DEVICE_FLOW_ERROR="本次设备授权已被新的授权尝试替换。"
|
|
1809
|
+
return 1
|
|
1810
|
+
fi
|
|
1793
1811
|
now="$(device_flow_now)"
|
|
1794
1812
|
if [[ "$now" -ge "$deadline" ]]; then
|
|
1795
1813
|
DEVICE_FLOW_ERROR="Rainbond 设备授权超时,请重新运行安装。"
|
|
@@ -1871,6 +1889,7 @@ device_flow_login_to_rainbond() {
|
|
|
1871
1889
|
printf '请在任意能够访问该 Rainbond 平台的电脑上打开上面的地址并完成登录授权。\n' >&2
|
|
1872
1890
|
fi
|
|
1873
1891
|
printf '[RAINSKILLS_USER_MESSAGE_END:runtime.device-authorization]\n' >&2
|
|
1892
|
+
printf '[RAINSKILLS_AGENT_WAIT_REQUIRED:runtime-connect]\n' >&2
|
|
1874
1893
|
|
|
1875
1894
|
if ! poll_device_authorization "$base_url"; then
|
|
1876
1895
|
cleanup_device_flow
|
|
@@ -2101,6 +2120,8 @@ validate_mcp_connectivity() {
|
|
|
2101
2120
|
curl \
|
|
2102
2121
|
--silent \
|
|
2103
2122
|
--show-error \
|
|
2123
|
+
--connect-timeout 10 \
|
|
2124
|
+
--max-time 30 \
|
|
2104
2125
|
--output "$response_file" \
|
|
2105
2126
|
--dump-header "$header_file" \
|
|
2106
2127
|
--write-out '%{http_code}' \
|
|
@@ -2399,6 +2420,7 @@ obtain_rainbond_token() {
|
|
|
2399
2420
|
|
|
2400
2421
|
local device_flow_status
|
|
2401
2422
|
if device_flow_login_to_rainbond "$base_url"; then
|
|
2423
|
+
printf '浏览器授权已完成,正在验证 Rainbond 连接…\n' >&2
|
|
2402
2424
|
return 0
|
|
2403
2425
|
else
|
|
2404
2426
|
device_flow_status=$?
|
|
@@ -2409,6 +2431,7 @@ obtain_rainbond_token() {
|
|
|
2409
2431
|
die "当前 Rainbond Console 仅支持旧版浏览器回调;此模式需要本机浏览器或交互终端。请升级 Rainbond Console 后重试。"
|
|
2410
2432
|
fi
|
|
2411
2433
|
browser_login_to_rainbond "$base_url"
|
|
2434
|
+
printf '浏览器授权已完成,正在验证 Rainbond 连接…\n' >&2
|
|
2412
2435
|
return 0
|
|
2413
2436
|
fi
|
|
2414
2437
|
die "${DEVICE_FLOW_ERROR:-Rainbond 设备授权失败。}"
|
|
@@ -2612,6 +2635,7 @@ main() {
|
|
|
2612
2635
|
if [[ "${RAINSKILLS_RUNTIME_CONNECT_COMPLETION:-0}" == "1" ]]; then
|
|
2613
2636
|
node "$SCRIPT_DIR/bin/rainskills.js" runtime complete-connect \
|
|
2614
2637
|
--onboarding-id "$RAINSKILLS_RUNTIME_OPERATION_ID"
|
|
2638
|
+
printf '[RAINSKILLS_AGENT_WAIT_COMPLETE:runtime-connect]\n' >&2
|
|
2615
2639
|
fi
|
|
2616
2640
|
return 0
|
|
2617
2641
|
fi
|
|
@@ -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.28 <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
|
|
|
@@ -44,7 +44,11 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
44
44
|
|
|
45
45
|
`runtime connect` 的 Device Flow 不依赖 stdin TTY;Agent 必须执行固定 argv 并保持进程附着直到授权完成。能打开本机浏览器时由连接器自动跳转,SSH、容器等无浏览器场景原样展示授权地址并继续轮询。只有 Rainbond 不支持 Device Flow 且进入旧版 loopback 手动粘贴时才需要交互终端;不得要求用户在聊天中粘贴 JWT。
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
授权命令是同步门禁。执行工具返回“进程仍在运行”或会话 ID 时,必须只等待或轮询同一个命令会话;在该会话结束前,禁止读取专项 Skill、解析 context、调用业务 CLI 或执行任何后续业务步骤。浏览器页面显示成功不代表连接完成;只有原命令退出码为 0,并输出 `rainskills.runtime-connect-result.v1` 且 `state=connected`,才可继续。不得另起 `runtime status` 猜测完成,也不得重复提示用户授权。
|
|
48
|
+
|
|
49
|
+
Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id` 反复调用 `write_stdin`(空输入轮询),直到工具返回 `exit_code`。连接器输出 `[RAINSKILLS_AGENT_WAIT_REQUIRED:runtime-connect]` 后进入上述轮询;看到 `[RAINSKILLS_AGENT_WAIT_COMPLETE:runtime-connect]` 后仍须继续轮询,直到取得退出码和最终 JSON。
|
|
50
|
+
|
|
51
|
+
Rainskills 只保存一个全局运行环境,不维护环境列表、名称、默认值或环境 ID。使用固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.28`):
|
|
48
52
|
|
|
49
53
|
- 状态:执行 `runtime status --json`。
|
|
50
54
|
- 首次连接:执行 `runtime connect <target> --saas` 或 `runtime connect <target> --rainbond-url <Console origin>`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rainskills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
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",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"rainbond-template-installer/",
|
|
41
41
|
"scripts/build-skill-manifest.mjs",
|
|
42
42
|
"scripts/install-local-cli.mjs",
|
|
43
|
+
"scripts/validate_customer_output.py",
|
|
43
44
|
"!**/__pycache__/",
|
|
44
45
|
"!**/*.pyc"
|
|
45
46
|
],
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"test:launcher": "node --test tests/console-origin.test.js tests/local-cli-installer.test.js tests/npx-launcher.test.js tests/runtime-state.test.js tests/single-runtime.test.js tests/single-runtime-cli.test.js tests/single-runtime-launcher.test.js tests/skill-manifest-builder.test.js",
|
|
68
69
|
"test:api-bridge": "node --test tests/api-bridge.test.js",
|
|
69
70
|
"test:console-contract": "python3 tests/console_tool_contract_test.py",
|
|
70
|
-
"test:skill-profile": "node --test tests/skill-profile-builder.test.js tests/markdown-link-integrity.test.js",
|
|
71
|
+
"test:skill-profile": "node --test tests/skill-profile-builder.test.js tests/markdown-link-integrity.test.js tests/user-facing-output-contract.test.js",
|
|
71
72
|
"test:marketplace": "node --test tests/marketplace-entry.test.js",
|
|
72
73
|
"test:runtime-routing": "node --test tests/runtime-onboarding-routing.test.js && python3 rainbond-app-assistant/scripts/validate_progressive_loading.py && python3 rainbond-app-assistant/scripts/validate_cross_skill_routing.py && python3 tests/progressive-loading-validators.test.py && python3 tests/run_skill_routing_evals.py",
|
|
73
74
|
"test:telemetry": "node --test tests/telemetry.test.js",
|
|
@@ -27,7 +27,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
27
27
|
| 需要对象边界或跨阶段状态语义 | [product object model](references/product-object-model.md) | 不相关工作流 |
|
|
28
28
|
| 需要生成最终结果或自动化契约 | [output contract](references/output-contract.md) | 不需要结果协议时不得读取 |
|
|
29
29
|
|
|
30
|
-
workspace context 包含 `enterprise_id`、`team_id`、`team_name` 和 `region_name
|
|
30
|
+
workspace context 包含 `enterprise_id`、`team_id`、`team_name` 和 `region_name`;`team_id` 可作为内部调用参数继续使用,但不得复制到用户可见的过程消息或最终结果中。app/component 标识只来自用户明确输入或本次实时查询/创建结果。它们都由当前任务携带,不写入 Runtime,也不得生成或传递 CLI 业务 operation ID、运行环境 ID 或 intent JSON。
|
|
31
31
|
|
|
32
32
|
## Runtime Gate
|
|
33
33
|
|
|
@@ -57,6 +57,8 @@ workspace context 包含 `enterprise_id`、`team_id`、`team_name` 和 `region_n
|
|
|
57
57
|
|
|
58
58
|
- 成功:说明项目、运行环境、工作空间、应用、真实 Rainbond 页面/访问地址及本轮实际完成操作;无法确认的字段省略,禁止猜测。
|
|
59
59
|
- 未完成:输出“部署失败。”、一句直接原因;仅在确有安全可执行方案时增加“解决办法”。
|
|
60
|
+
- 默认不得展示内部对象、状态枚举、Skill/工具名、YAML、JSON 或英文编排标题。
|
|
61
|
+
- 过程消息和最终结果中的工作空间一律用 `team_name` 展示,可连同 `region_name` 写成“工作空间 / 集群”;除非用户明确要求调试原始数据,不展示 `team_id`。
|
|
60
62
|
- 用户明确要求结构化结果或自动化契约时,读取 [output contract](references/output-contract.md),不得自行发明字段、枚举或状态。
|
|
61
63
|
|
|
62
64
|
## 停止条件
|
|
@@ -13,7 +13,7 @@ Load this reference only when checking a proposed route or reviewing a completed
|
|
|
13
13
|
- reporting team/region/app identity as a substitute for the clickable Rainbond deployment location
|
|
14
14
|
- using the public service URL as the Rainbond deployment location, or constructing the public service URL from naming conventions
|
|
15
15
|
- stripping useful diagnostic evidence from building, blocked, unhealthy, ambiguous, handoff, or incomplete promotion states
|
|
16
|
-
- omitting
|
|
16
|
+
- omitting `### Structured Output` after explicit structured contract mode was selected
|
|
17
17
|
- replacing the required five human-readable sections with freeform narrative in structured contract mode
|
|
18
18
|
- treating a project as unlinked only because `.rainbond/local.json.metadata.status` is stale even though the platform confirms the same app in the current run
|
|
19
19
|
- omitting `Actions Performed` detail when the run only did inspection/classification and intentionally skipped downstream skills
|
|
@@ -30,22 +30,11 @@
|
|
|
30
30
|
- concise delivery report mode
|
|
31
31
|
- structured contract mode
|
|
32
32
|
|
|
33
|
-
###
|
|
34
|
-
|
|
35
|
-
Use this mode by default when
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `delivery_state.status` is `delivered` or `delivered-but-needs-manual-validation`
|
|
39
|
-
- `delivery_state.verification_mode` is `verified`, `inferred`, or `manual_validation_needed` consistently with that status
|
|
40
|
-
- `next_action` is `stop` or `stop and validate URL manually`
|
|
41
|
-
- `promotion_result = null`
|
|
42
|
-
- there is no unresolved `runtime_state.blocker` or `delivery_state.blocker`
|
|
43
|
-
- `project.deployment_location_url` is non-null
|
|
44
|
-
- `delivery_state.preferred_access_url` is non-null
|
|
45
|
-
- the user did not explicitly request structured output, YAML, JSON, debug output, or machine-readable output
|
|
46
|
-
- no eval/automation consumer explicitly requires structured contract mode
|
|
47
|
-
|
|
48
|
-
In concise delivery report mode:
|
|
33
|
+
### Customer report mode
|
|
34
|
+
|
|
35
|
+
Use this mode by default for successful, incomplete, blocked, ambiguous, and handoff results. Switch away from it only when the user explicitly requests structured output, YAML, JSON, debug details, or machine-readable output, or when an automation/evaluation consumer explicitly declares the structured contract requirement.
|
|
36
|
+
|
|
37
|
+
In customer report mode:
|
|
49
38
|
- do not append `### Structured Output`
|
|
50
39
|
- do not expose the fenced YAML block
|
|
51
40
|
- keep the report short and directly useful to the user
|
|
@@ -93,17 +82,13 @@
|
|
|
93
82
|
Use this mode when any of the following is true:
|
|
94
83
|
- the user asks for structured output, YAML, JSON, debug details, or machine-readable output
|
|
95
84
|
- an eval, wrapper, or automation flow explicitly needs deterministic structured schema validation
|
|
96
|
-
- any concise delivery report condition above is not met
|
|
97
|
-
- the app is building, unhealthy, blocked, identity-ambiguous, or requires handoff
|
|
98
|
-
- `promotion_result` is non-null or the user requested dev-to-test promotion
|
|
99
|
-
- there is any unresolved blocker or handoff
|
|
100
85
|
- another skill or wrapper will consume the result as input
|
|
101
86
|
|
|
102
|
-
Building, unhealthy, blocked, ambiguous, handoff, and incomplete promotion states
|
|
87
|
+
Building, unhealthy, blocked, ambiguous, handoff, and incomplete promotion states do not enable structured mode by themselves. In customer mode, explain the direct result and one actionable next step in concise Chinese without exposing internal enums or action ledgers.
|
|
103
88
|
|
|
104
89
|
In structured contract mode:
|
|
105
90
|
- the human-readable sections below are the narrative view over `AppAssistantResult`
|
|
106
|
-
- the reply
|
|
91
|
+
- the explicitly structured reply ends with a final `### Structured Output` section
|
|
107
92
|
- the `### Structured Output` section must render `AppAssistantResult` in fenced `yaml`
|
|
108
93
|
- the literal section order must be:
|
|
109
94
|
- `### Project State`
|
|
@@ -394,7 +379,7 @@
|
|
|
394
379
|
```
|
|
395
380
|
````
|
|
396
381
|
|
|
397
|
-
|
|
382
|
+
Only in explicit structured contract mode, respond using exactly these sections:
|
|
398
383
|
|
|
399
384
|
### Project State
|
|
400
385
|
- state the current classification
|
|
@@ -9,14 +9,18 @@ Canonical progressive-loading contract: `rainskills.skill-runtime-contract.v1`.
|
|
|
9
9
|
|
|
10
10
|
没有运行环境时,让用户选择 Rainbond Cloud 或一个已有/新建的私有 Rainbond,并执行对应的 `runtime connect`。连接和重新授权必须进入浏览器 Device Flow,不复用 Shell 中缓存的 JWT;新凭据通过 live probe 后才覆盖唯一运行环境。CLI 返回 401 时,只读调用可在 `runtime reconnect` 成功后重试一次;写调用不得自动重放,必须先查询平台真实状态。403 直接停止,不重新授权。
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
授权命令是同步门禁。执行工具返回“进程仍在运行”或会话 ID 时,必须只等待或轮询同一个命令会话;在该会话结束前,禁止读取专项 Skill、解析 context、调用业务 CLI 或执行任何后续业务步骤。浏览器页面显示成功不代表连接完成;只有原命令退出码为 0,并输出 `rainskills.runtime-connect-result.v1` 且 `state=connected`,才可继续。不得另起 `runtime status` 猜测完成,也不得重复提示用户授权。
|
|
13
|
+
|
|
14
|
+
Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id` 反复调用 `write_stdin`(空输入轮询),直到工具返回 `exit_code`。连接器输出 `[RAINSKILLS_AGENT_WAIT_REQUIRED:runtime-connect]` 后进入上述轮询;看到 `[RAINSKILLS_AGENT_WAIT_COMPLETE:runtime-connect]` 后仍须继续轮询,直到取得退出码和最终 JSON。
|
|
15
|
+
|
|
16
|
+
`context resolve` 是无状态调用:单一工作空间直接返回上下文,多个候选返回组合选项;其中 `team_id` 是后续平台调用的内部参数,不得复制到用户可见的过程消息或最终结果中。用户选择后由当前任务直接携带 team/region 参数,不执行 `context select`,不写本地 operation。所有可变 `call` 仍需先取得 confirmation ID,再以完全相同的输入追加 `--confirm` 执行一次。
|
|
13
17
|
|
|
14
18
|
`required` 只声明要解析的维度,企业 ID 始终来自当前登录身份。用户明确给出的 team/region 必须放进 `hints` 做精确匹配;不得把企业名、team 名或选择对象作为顶层 `enterprise` / `workspace` 字段传入。多候选时只展示 CLI 返回的 label;用户选择后再次执行同一个无状态 `context resolve`,通过 `selection.option_id` 让 CLI 重新查询并验证当前候选,不写本地 context 状态。
|
|
15
19
|
|
|
16
20
|
```json
|
|
17
21
|
{
|
|
18
22
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
19
|
-
"package_version": "rainskills@0.1.
|
|
23
|
+
"package_version": "rainskills@0.1.28",
|
|
20
24
|
"runtime_status": [
|
|
21
25
|
"node",
|
|
22
26
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -529,7 +529,7 @@
|
|
|
529
529
|
反例(来自真实回归 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` 重查。
|
|
530
530
|
35. **会话内部叙述纪律 + 内部 preflight 工具不要主动调**。下列四类是"内部会话状态",对用户**无信息量**,禁止外漏到 assistant 可见消息:
|
|
531
531
|
- **专项 Skill 手册加载过程**:这是当前任务的内部准备。不要说"我先加载 bootstrap 手册"、"手册已加载"或"skill ready";读完直接进入下一个真实工具调用。
|
|
532
|
-
- **`rainbond_get_current_user`**:不得由 Agent 单独调用。`enterprise_id`、`team_id`、`team_name` 和 `region_name` 统一由受保护的 `context resolve`
|
|
532
|
+
- **`rainbond_get_current_user`**:不得由 Agent 单独调用。`enterprise_id`、`team_id`、`team_name` 和 `region_name` 统一由受保护的 `context resolve` 命令无状态解析并由当前任务携带;`team_id` 可继续用于后续平台调用,但不得在过程消息或最终报告中展示。只有 CLI 明确返回需要选择时才询问用户,并且只展示名称标签。
|
|
533
533
|
- **`rainbond_query_components` 同入参重复轮询**:服务端 30s 内的同 args 调用会走缓存;你**不要**在每个新 user turn 开头都"先查一下组件列表"。只有本次调用使用的 `enterprise_id`、`app_id` 和查询条件均未变化,且没有新的平台动作时,才可复用 priorTurnMessages 里的最近结果。
|
|
534
534
|
- **规则推理过程 / Rainbond Tool 内部限制 / 分类决策叙述**:你内部基于哪条 Iron Law / hard rule / 推断信号做的决策、具体 Tool 签名 / 字段限制、对组件 / 服务的分类判断("ClickHouse 是公认的列式分析数据库"这类),都属于内部状态,对用户**无信息量**。
|
|
535
535
|
|