rainskills 0.1.25 → 0.1.27
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 +168 -23
- 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 +7 -6
- 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 +11 -6
- package/rainbond-app-assistant/references/workflow-rules.md +42 -42
- package/rainbond-app-assistant/scripts/validate_app_assistant_output.py +10 -66
- package/rainbond-app-assistant/scripts/validate_cross_skill_routing.py +2 -2
- package/rainbond-app-assistant/scripts/validate_progressive_loading.py +5 -5
- package/rainbond-app-version-assistant/SKILL.md +23 -9
- package/rainbond-delivery-verifier/SKILL.md +11 -6
- 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 +23 -9
- package/rainbond-fullstack-bootstrap/SKILL.md +49 -9
- 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/40-source-and-package-rules.md +2 -2
- 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 +11 -6
- 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 +3 -3
- package/rainbond-opensource-app-deploy/references/runtime-gate.md +11 -6
- 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 +11 -6
- package/rainbond-project-init/SKILL.md +27 -13
- 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 +23 -9
- 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.27`,不会执行浮动的 `@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.27`):
|
|
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
|
@@ -23,9 +23,15 @@ const SKILL_MANIFEST_FILENAME = "rainskills-skill-manifest.json";
|
|
|
23
23
|
const OPERATION_ID_PATTERN = /^[a-f0-9]{8}-[a-f0-9-]{27,}$/;
|
|
24
24
|
const SKILL_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
25
25
|
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
26
|
+
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
27
|
+
const CONTEXT_RESOLVE_FIELDS = new Set(["required", "hints", "selection"]);
|
|
28
|
+
const CONTEXT_HINT_FIELDS = new Set(["team_id", "team_name", "region_name"]);
|
|
26
29
|
const SKILL_CONTENT_MAX_BYTES = 128 * 1024;
|
|
27
30
|
const SKILL_MANIFEST_MAX_BYTES = 4 * 1024 * 1024;
|
|
28
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"]);
|
|
29
35
|
const RISK_POLICY = Object.freeze({
|
|
30
36
|
version: "1",
|
|
31
37
|
readPrefixes: [
|
|
@@ -34,6 +40,12 @@ const RISK_POLICY = Object.freeze({
|
|
|
34
40
|
],
|
|
35
41
|
destructiveFragments: ["delete", "remove", "purge", "destroy"],
|
|
36
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
|
+
});
|
|
37
49
|
const CALL_OPTION_DEFINITIONS = Object.freeze({
|
|
38
50
|
"--confirm": Object.freeze({ property: "confirmation", pattern: OPERATION_ID_PATTERN }),
|
|
39
51
|
"--skill-id": Object.freeze({ property: "skillId", pattern: SKILL_ID_PATTERN }),
|
|
@@ -486,8 +498,16 @@ function loadSkillBinding(config, skillId, rootSkillId) {
|
|
|
486
498
|
};
|
|
487
499
|
}
|
|
488
500
|
|
|
489
|
-
function classifyTool(toolName) {
|
|
501
|
+
function classifyTool(toolName, argumentsValue = {}) {
|
|
490
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
|
+
}
|
|
491
511
|
if (RISK_POLICY.destructiveFragments.some((fragment) => normalized.includes(fragment))) {
|
|
492
512
|
return "destructive";
|
|
493
513
|
}
|
|
@@ -676,12 +696,47 @@ function fitOutput(value) {
|
|
|
676
696
|
};
|
|
677
697
|
}
|
|
678
698
|
|
|
679
|
-
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 = []) {
|
|
680
715
|
if (Array.isArray(value)) {
|
|
681
|
-
|
|
716
|
+
for (const item of value) collectSensitiveArgumentRedactions(item, output);
|
|
717
|
+
return output;
|
|
682
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 = []) {
|
|
733
|
+
if (Array.isArray(value)) {
|
|
734
|
+
return value.map((item) => sanitizeToolResult(item, pathParts, sensitiveArguments));
|
|
735
|
+
}
|
|
736
|
+
if (typeof value === "string") return redactString(value, sensitiveArguments);
|
|
683
737
|
if (!value || typeof value !== "object") return value;
|
|
684
738
|
const safe = {};
|
|
739
|
+
const sensitiveEnv = hasSensitiveEnvName(value);
|
|
685
740
|
for (const [key, item] of Object.entries(value)) {
|
|
686
741
|
// Console destructive operations may return this one-time control value.
|
|
687
742
|
// It is not a credential and must remain available for the confirmation flow.
|
|
@@ -692,7 +747,11 @@ function sanitizeToolResult(value, pathParts = []) {
|
|
|
692
747
|
if (key !== "confirmation_token"
|
|
693
748
|
&& !isPackageUploadAuthMode
|
|
694
749
|
&& SENSITIVE_RESPONSE_KEY_PATTERN.test(key)) continue;
|
|
695
|
-
|
|
750
|
+
if (sensitiveEnv && ENV_VALUE_FIELDS.has(key)) {
|
|
751
|
+
safe[key] = "[REDACTED]";
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
safe[key] = sanitizeToolResult(item, [...pathParts, key], sensitiveArguments);
|
|
696
755
|
}
|
|
697
756
|
return safe;
|
|
698
757
|
}
|
|
@@ -1017,17 +1076,96 @@ function boundedContextString(value) {
|
|
|
1017
1076
|
return typeof value === "string"
|
|
1018
1077
|
&& value.length > 0
|
|
1019
1078
|
&& value.length <= 128
|
|
1020
|
-
&&
|
|
1079
|
+
&& !CONTROL_CHARACTER_PATTERN.test(value)
|
|
1021
1080
|
? value
|
|
1022
1081
|
: null;
|
|
1023
1082
|
}
|
|
1024
1083
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1084
|
+
function contextInputUsageError() {
|
|
1085
|
+
return new BridgeError("context input fields are invalid", EXIT.USAGE, {
|
|
1086
|
+
error: "context input fields are invalid",
|
|
1087
|
+
expected: {
|
|
1088
|
+
required: ["enterprise", "workspace"],
|
|
1089
|
+
hints: { team_name: "<team-name>" },
|
|
1090
|
+
},
|
|
1091
|
+
note: "enterprise is resolved from the current authenticated identity",
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function validateContextHints(value) {
|
|
1096
|
+
if (value === undefined) return null;
|
|
1097
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1098
|
+
throw new BridgeError("context workspace hints are invalid", EXIT.USAGE);
|
|
1099
|
+
}
|
|
1100
|
+
const entries = Object.entries(value);
|
|
1101
|
+
if (entries.length === 0
|
|
1102
|
+
|| entries.some(([field, item]) => !CONTEXT_HINT_FIELDS.has(field) || !boundedContextString(item))) {
|
|
1103
|
+
throw new BridgeError("context workspace hints are invalid", EXIT.USAGE);
|
|
1104
|
+
}
|
|
1105
|
+
return Object.fromEntries(entries);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function validateContextSelection(value) {
|
|
1109
|
+
if (value === undefined) return null;
|
|
1110
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
1111
|
+
|| Object.keys(value).length !== 1
|
|
1112
|
+
|| typeof value.option_id !== "string"
|
|
1113
|
+
|| value.option_id.length === 0
|
|
1114
|
+
|| value.option_id.length > 300
|
|
1115
|
+
|| CONTROL_CHARACTER_PATTERN.test(value.option_id)) {
|
|
1116
|
+
throw new BridgeError("context workspace selection is invalid", EXIT.USAGE);
|
|
1117
|
+
}
|
|
1118
|
+
return { option_id: value.option_id };
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function validateContextResolveInput(input) {
|
|
1122
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
1123
|
+
throw contextInputUsageError();
|
|
1124
|
+
}
|
|
1125
|
+
if (Object.keys(input).some((field) => !CONTEXT_RESOLVE_FIELDS.has(field))) {
|
|
1126
|
+
throw contextInputUsageError();
|
|
1127
|
+
}
|
|
1128
|
+
const required = input.required;
|
|
1027
1129
|
if (!Array.isArray(required)
|
|
1028
1130
|
|| required.some((item) => !["enterprise", "workspace"].includes(item))) {
|
|
1029
1131
|
throw new BridgeError("context required dimensions are invalid", EXIT.USAGE);
|
|
1030
1132
|
}
|
|
1133
|
+
|
|
1134
|
+
const hints = validateContextHints(input.hints);
|
|
1135
|
+
const selection = validateContextSelection(input.selection);
|
|
1136
|
+
|
|
1137
|
+
if ((hints || selection) && !required.includes("workspace")) {
|
|
1138
|
+
throw new BridgeError("context workspace input requires the workspace dimension", EXIT.USAGE);
|
|
1139
|
+
}
|
|
1140
|
+
if (hints && selection) {
|
|
1141
|
+
throw new BridgeError("context hints and selection are mutually exclusive", EXIT.USAGE);
|
|
1142
|
+
}
|
|
1143
|
+
return { required, hints, selection };
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function resolvedWorkspaceContext(enterpriseId, option) {
|
|
1147
|
+
return {
|
|
1148
|
+
state: "resolved",
|
|
1149
|
+
context: {
|
|
1150
|
+
enterprise_id: enterpriseId,
|
|
1151
|
+
team_id: option.team_id,
|
|
1152
|
+
team_name: option.team_name,
|
|
1153
|
+
region_name: option.region_name,
|
|
1154
|
+
},
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function workspaceSelectionResult(enterpriseId, options) {
|
|
1159
|
+
return {
|
|
1160
|
+
state: "needs-selection",
|
|
1161
|
+
enterprise_id: enterpriseId,
|
|
1162
|
+
dimension: "workspace-region",
|
|
1163
|
+
options,
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
async function resolveStatelessContext(input, config) {
|
|
1168
|
+
const { required, hints, selection } = validateContextResolveInput(input);
|
|
1031
1169
|
const identity = await execute({
|
|
1032
1170
|
command: "read",
|
|
1033
1171
|
toolName: "rainbond_get_current_user",
|
|
@@ -1071,19 +1209,22 @@ async function resolveStatelessContext(input, config) {
|
|
|
1071
1209
|
}
|
|
1072
1210
|
}
|
|
1073
1211
|
if (options.length === 0) return { state: "blocked", reason: "no-accessible-workspace" };
|
|
1074
|
-
if (
|
|
1075
|
-
const
|
|
1076
|
-
return
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1212
|
+
if (selection) {
|
|
1213
|
+
const selected = options.find((option) => option.id === selection.option_id);
|
|
1214
|
+
return selected
|
|
1215
|
+
? resolvedWorkspaceContext(enterpriseId, selected)
|
|
1216
|
+
: { state: "blocked", reason: "workspace-selection-invalid" };
|
|
1217
|
+
}
|
|
1218
|
+
const matchingOptions = hints
|
|
1219
|
+
? options.filter((option) => Object.entries(hints).every(([field, value]) => option[field] === value))
|
|
1220
|
+
: options;
|
|
1221
|
+
if (matchingOptions.length === 0) {
|
|
1222
|
+
return { state: "blocked", reason: "workspace-hint-not-found" };
|
|
1223
|
+
}
|
|
1224
|
+
if (matchingOptions.length === 1) {
|
|
1225
|
+
return resolvedWorkspaceContext(enterpriseId, matchingOptions[0]);
|
|
1226
|
+
}
|
|
1227
|
+
return workspaceSelectionResult(enterpriseId, matchingOptions);
|
|
1087
1228
|
}
|
|
1088
1229
|
|
|
1089
1230
|
async function execute(command, config) {
|
|
@@ -1120,14 +1261,14 @@ async function execute(command, config) {
|
|
|
1120
1261
|
};
|
|
1121
1262
|
}
|
|
1122
1263
|
|
|
1123
|
-
const
|
|
1264
|
+
const argumentsValue = command.argumentsValue || readArguments(command.input);
|
|
1265
|
+
const operationClass = classifyTool(command.toolName, argumentsValue);
|
|
1124
1266
|
if (command.command === "read" && operationClass !== "read") {
|
|
1125
1267
|
throw new BridgeError(
|
|
1126
1268
|
"read is read-only and rejects tools classified as write or destructive",
|
|
1127
1269
|
EXIT.USAGE
|
|
1128
1270
|
);
|
|
1129
1271
|
}
|
|
1130
|
-
const argumentsValue = command.argumentsValue || readArguments(command.input);
|
|
1131
1272
|
let operation = null;
|
|
1132
1273
|
let skillBinding = null;
|
|
1133
1274
|
if (operationClass !== "read") {
|
|
@@ -1189,7 +1330,11 @@ async function execute(command, config) {
|
|
|
1189
1330
|
throw new BridgeError("Rainbond tool response lacks structuredContent", EXIT.TRANSPORT);
|
|
1190
1331
|
}
|
|
1191
1332
|
if (operation) updateOperation(config, operation, "succeeded");
|
|
1192
|
-
return fitOutput(sanitizeToolResult(
|
|
1333
|
+
return fitOutput(sanitizeToolResult(
|
|
1334
|
+
result.structuredContent,
|
|
1335
|
+
[],
|
|
1336
|
+
collectSensitiveArgumentRedactions(argumentsValue)
|
|
1337
|
+
));
|
|
1193
1338
|
} catch (error) {
|
|
1194
1339
|
if (operation) {
|
|
1195
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.27 <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.27`):
|
|
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.27",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rainbond-app-assistant
|
|
3
|
-
description: "Use whenever a user asks to deploy, run, deliver, publish, inspect, repair, or troubleshoot source code, the current project or local project, a source directory/package, an ordinary bare Git repository URL, a private-image project,
|
|
3
|
+
description: "Use whenever a user asks to deploy, run, deliver, publish, inspect, repair, or troubleshoot source code, the current project or local project, a source directory/package, an ordinary bare Git repository URL, a private-image project, a named application that is not identified as a third-party open-source suite, or a new app/component built from one container image. Trigger phrases: 帮我部署当前项目 / 帮我把这个项目跑起来 / 帮我看看当前项目卡在哪 / 如果还没初始化就先初始化,然后自动继续到应该停止的位置 / 帮我处理一下这个应用 / 在团队下新建应用并使用镜像创建组件. Not for supplied third-party Compose, Helm, image-set descriptors, or an explicit named third-party open-source suite; use rainbond-opensource-app-deploy. Confirmed market templates use rainbond-template-installer."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rainbond App Assistant
|
|
@@ -14,7 +14,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
14
14
|
- 用户实际提供第三方 Docker Compose、Helm、镜像集合描述符,或明确要求部署 Harbor、Dify、n8n 等第三方开源套件:转到 rainbond-opensource-app-deploy。
|
|
15
15
|
- 已确认是 Rainbond 本地/云端市场模板:转到 rainbond-template-installer。
|
|
16
16
|
|
|
17
|
-
开始前读取 [routing](references/routing.md)
|
|
17
|
+
开始前读取 [routing](references/routing.md) 做静态归属判断。初始路由阶段只加载最终所属入口 Skill 的 Runtime Gate,不得提前读取专项 Skill 的 Gate。
|
|
18
18
|
|
|
19
19
|
## 渐进加载
|
|
20
20
|
|
|
@@ -23,11 +23,11 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
23
23
|
| 阶段 | 必须读取 | 禁止提前读取 |
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| 初始部署或首次 Rainbond 操作 | 本根入口、[own runtime gate](references/runtime-gate.md)、[routing](references/routing.md) | 其余全部 |
|
|
26
|
-
|
|
|
26
|
+
| workspace context 已解析,需要编排或执行 app/component | [workflow rules](references/workflow-rules.md);仅在核对路线或复盘时读取 [operational reference](references/operational-reference.md) | 输出与对象细节 |
|
|
27
27
|
| 需要对象边界或跨阶段状态语义 | [product object model](references/product-object-model.md) | 不相关工作流 |
|
|
28
28
|
| 需要生成最终结果或自动化契约 | [output contract](references/output-contract.md) | 不需要结果协议时不得读取 |
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
workspace context 包含 `enterprise_id`、`team_id`、`team_name` 和 `region_name`;app/component 标识只来自用户明确输入或本次实时查询/创建结果。它们都由当前任务携带,不写入 Runtime,也不得生成或传递 CLI 业务 operation ID、运行环境 ID 或 intent JSON。
|
|
31
31
|
|
|
32
32
|
## Runtime Gate
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
35
35
|
|
|
36
36
|
不可弱化的不变量:
|
|
37
37
|
|
|
38
|
-
- 不得绕过 Gate 选择的 transport、context
|
|
38
|
+
- 不得绕过 Gate 选择的 transport、context 或授权边界。进入专项阶段后,按 workflow rules 完整读取对应专项 Skill;其中重复的 Gate 只用于一致性核对,不得触发第二次连接、状态检查或 context 解析,除非 Gate 声明的失效条件已经发生。
|
|
39
39
|
- 401:只读调用仅可按 Gate 允许的恢复流程重试一次;写调用不得自动重放,必须先查询真实状态。403:立即停止,不做未授权重试。
|
|
40
40
|
- 可变调用必须先取得确认 ID,再用完全相同输入附加确认执行;不得绕过确认。
|
|
41
41
|
- JWT、凭据与密钥不得回显、复制到报告或通过替代 transport 绕过保护。
|
|
@@ -49,7 +49,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
49
49
|
- 到达 code_or_build_handoff_needed 后硬停止,不自动改代码、运行本地测试、提交、推送或重试。
|
|
50
50
|
- 密钥只来自当前 profile 允许的输入源,永不回显或写入报告。
|
|
51
51
|
|
|
52
|
-
详细主线、
|
|
52
|
+
详细主线、context 复用、专项手册加载、构建/运行时证据链、依赖、确认边界与尝试预算只在需要执行时读取 [workflow rules](references/workflow-rules.md)。
|
|
53
53
|
|
|
54
54
|
## 简洁结果协议
|
|
55
55
|
|
|
@@ -57,6 +57,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
57
57
|
|
|
58
58
|
- 成功:说明项目、运行环境、工作空间、应用、真实 Rainbond 页面/访问地址及本轮实际完成操作;无法确认的字段省略,禁止猜测。
|
|
59
59
|
- 未完成:输出“部署失败。”、一句直接原因;仅在确有安全可执行方案时增加“解决办法”。
|
|
60
|
+
- 默认不得展示内部对象、状态枚举、Skill/工具名、YAML、JSON 或英文编排标题。
|
|
60
61
|
- 用户明确要求结构化结果或自动化契约时,读取 [output contract](references/output-contract.md),不得自行发明字段、枚举或状态。
|
|
61
62
|
|
|
62
63
|
## 停止条件
|