rainskills 0.1.29 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bin/rainskills-tools.js +52 -2
- package/install.sh +352 -2
- package/marketplace/rainskills/.claude-plugin/plugin.json +1 -1
- package/marketplace/rainskills/.codex-plugin/plugin.json +1 -1
- package/marketplace/rainskills/skills/rainskills/SKILL.md +2 -2
- package/package.json +1 -1
- package/rainbond-app-assistant/references/runtime-gate.md +1 -1
- package/rainbond-app-version-assistant/SKILL.md +1 -1
- package/rainbond-delivery-verifier/SKILL.md +1 -1
- package/rainbond-env-sync/SKILL.md +1 -1
- package/rainbond-fullstack-bootstrap/SKILL.md +1 -1
- package/rainbond-fullstack-troubleshooter/SKILL.md +1 -1
- package/rainbond-opensource-app-deploy/references/runtime-gate.md +1 -1
- package/rainbond-platform-installer/SKILL.md +1 -1
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-installer/scripts/result-telemetry.js +285 -0
- package/rainbond-platform-installer/scripts/telemetry.js +3 -0
- package/rainbond-platform-installer/scripts/windows-client-config.js +0 -1
- package/rainbond-platform-installer/scripts/windows-onboarding.js +98 -14
- package/rainbond-platform-query/SKILL.md +1 -1
- package/rainbond-project-init/SKILL.md +1 -1
- package/rainbond-template-installer/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -239,7 +239,7 @@ node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-
|
|
|
239
239
|
|
|
240
240
|
Rainskills 会在用户下一次发起业务动作时,由本地运行时立即返回环境查询结果,并另行启动后台任务静默检查更新。更新只跟随 npm `latest` 指向的正式版。当前版本是 RC 或其他预发布版本时不会查询、不会自动升级;npm 上的新 RC 版本也不参与正式版自动升级。
|
|
241
241
|
|
|
242
|
-
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.
|
|
242
|
+
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.30`,不会执行浮动的 `@latest` 业务代码。新版本原子刷新已经安装的 Rainskills Skills;当前业务继续使用启动时已经加载的版本,最迟从下一条新任务开始使用新版。npm 超时、版本检查失败、安装位置不安全或文件迁移失败时会保留旧版本,且不会阻塞或改变当前操作。
|
|
243
243
|
|
|
244
244
|
升级只更新 Rainskills 自身,不触发 Rainbond 安装、运行环境选择、登录授权或重新对接,也不会新增 Agent MCP 配置。更新内容仅包括 Skills 和本地 CLI。原始业务操作会继续执行;只有该业务操作本身需要运行环境时,才按既有门禁检查当前连接。可用连接直接复用,401 只重新授权一次,403 立即停止,从未连接过运行环境时才进入环境选择。
|
|
245
245
|
|
package/SKILL.md
CHANGED
|
@@ -50,7 +50,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
50
50
|
|
|
51
51
|
DeepSeek Harness 和 WorkBuddy 也必须保持原命令附着直到退出;如果宿主把长命令转成持久终端或后台任务,只能轮询该原始命令句柄,不得另起 `runtime status` 推测授权结果。WorkBuddy 安装后若当前任务的 Skill 清单尚未刷新,在下一条业务动作前先刷新 Skill 列表或新建任务,不得退回内置 Sites 代替 Rainbond 部署。
|
|
52
52
|
|
|
53
|
-
Rainskills 只保存一个全局运行环境,不维护环境列表、名称、默认值或环境 ID。使用固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.
|
|
53
|
+
Rainskills 只保存一个全局运行环境,不维护环境列表、名称、默认值或环境 ID。使用固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.30`):
|
|
54
54
|
|
|
55
55
|
- 状态:执行 `runtime status --json`。
|
|
56
56
|
- 首次连接:执行 `runtime connect <target> --saas` 或 `runtime connect <target> --rainbond-url <Console origin>`。
|
package/bin/rainskills-tools.js
CHANGED
|
@@ -449,6 +449,56 @@ function skillManifestPath(config) {
|
|
|
449
449
|
return path.join(config.homeDir || os.homedir(), CONFIG_DIRECTORY, "bin", SKILL_MANIFEST_FILENAME);
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
function loadTelemetryPackageVersion(config) {
|
|
453
|
+
try {
|
|
454
|
+
const manifest = JSON.parse(fs.readFileSync(skillManifestPath(config), "utf8"));
|
|
455
|
+
return typeof manifest.package_version === "string" && manifest.package_version
|
|
456
|
+
? manifest.package_version
|
|
457
|
+
: "unknown";
|
|
458
|
+
} catch {
|
|
459
|
+
return "unknown";
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function createBridgeTelemetry(config) {
|
|
464
|
+
try {
|
|
465
|
+
const directory = path.join(config.homeDir || os.homedir(), CONFIG_DIRECTORY, "rainskills", "telemetry");
|
|
466
|
+
const { createResultTelemetry } = requireRuntimeModule("result-telemetry.js");
|
|
467
|
+
return createResultTelemetry({
|
|
468
|
+
directory,
|
|
469
|
+
packageVersion: loadTelemetryPackageVersion(config),
|
|
470
|
+
});
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function executeWithUsageTelemetry(command, config) {
|
|
477
|
+
const tracksUsage = command.command === "read" || command.command === "call";
|
|
478
|
+
const telemetry = tracksUsage ? createBridgeTelemetry(config) : null;
|
|
479
|
+
if (telemetry) await telemetry.flushPending(3).catch(() => {});
|
|
480
|
+
try {
|
|
481
|
+
const output = await execute(command, config);
|
|
482
|
+
if (telemetry) {
|
|
483
|
+
const firstUse = telemetry.recordFirstUse("success");
|
|
484
|
+
const active = telemetry.recordActiveDaily();
|
|
485
|
+
await Promise.allSettled([firstUse.delivery, active.delivery]);
|
|
486
|
+
}
|
|
487
|
+
return output;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (telemetry) {
|
|
490
|
+
const firstUse = telemetry.recordFirstUse("failed", {
|
|
491
|
+
error_stage: "first_use",
|
|
492
|
+
error_code: error instanceof BridgeError && error.exitCode === EXIT.TRANSPORT
|
|
493
|
+
? "transport_failed"
|
|
494
|
+
: "tool_call_failed",
|
|
495
|
+
});
|
|
496
|
+
await firstUse.delivery.catch(() => false);
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
452
502
|
function loadSkillBinding(config, skillId, rootSkillId) {
|
|
453
503
|
const target = skillManifestPath(config);
|
|
454
504
|
let manifest;
|
|
@@ -1370,7 +1420,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1370
1420
|
if (config.isInsecureHttp) {
|
|
1371
1421
|
process.stderr.write('{"warning":"using insecure HTTP transport"}\n');
|
|
1372
1422
|
}
|
|
1373
|
-
const output = await
|
|
1423
|
+
const output = await executeWithUsageTelemetry(command, config);
|
|
1374
1424
|
process.stdout.write(`${JSON.stringify(fitOutput(output))}\n`);
|
|
1375
1425
|
return;
|
|
1376
1426
|
}
|
|
@@ -1380,7 +1430,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1380
1430
|
if (config.isInsecureHttp) {
|
|
1381
1431
|
process.stderr.write('{"warning":"using insecure HTTP transport"}\n');
|
|
1382
1432
|
}
|
|
1383
|
-
const output = await
|
|
1433
|
+
const output = await executeWithUsageTelemetry(command, config);
|
|
1384
1434
|
process.stdout.write(`${JSON.stringify(fitOutput(output))}\n`);
|
|
1385
1435
|
} catch (error) {
|
|
1386
1436
|
const bridgeError = error instanceof BridgeError
|
package/install.sh
CHANGED
|
@@ -176,6 +176,7 @@ VALIDATED_TOKEN=""
|
|
|
176
176
|
OBTAINED_RAINBOND_TOKEN=""
|
|
177
177
|
RAINSKILLS_INSTALL_REPORT_URL="https://log.rainbond.com/api/rainskills/installations"
|
|
178
178
|
RAINSKILLS_LIFECYCLE_REPORT_URL="https://log.rainbond.com/api/rainskills/lifecycle-events"
|
|
179
|
+
RAINSKILLS_TELEMETRY_REPORT_URL="${RAINSKILLS_TELEMETRY_REPORT_URL:-https://log.rainbond.com/api/rainskills/events}"
|
|
179
180
|
RAINSKILLS_INSTALL_ATTEMPT_ID="${RAINSKILLS_INSTALL_ATTEMPT_ID:-}"
|
|
180
181
|
RAINSKILLS_INSTALL_EID=""
|
|
181
182
|
RAINSKILLS_INSTALL_CLIENT="unknown"
|
|
@@ -183,6 +184,8 @@ RAINSKILLS_INSTALL_ACTION="install"
|
|
|
183
184
|
RAINSKILLS_INSTALL_FAILURE_STAGE="bootstrap"
|
|
184
185
|
RAINSKILLS_INSTALL_FAILURE_CATEGORY="invalid_arguments"
|
|
185
186
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=0
|
|
187
|
+
RAINSKILLS_V2_TERMINAL_REPORTED=0
|
|
188
|
+
RAINSKILLS_V2_CURRENT_AGENT=""
|
|
186
189
|
RAINSKILLS_TELEMETRY_SEQUENCE=0
|
|
187
190
|
RAINSKILLS_BROWSER_LOGIN_SERVER_PID=""
|
|
188
191
|
RAINSKILLS_BROWSER_LOGIN_READER_PID=""
|
|
@@ -286,13 +289,282 @@ new_rainskills_install_attempt_id() {
|
|
|
286
289
|
python3 - <<'PY'
|
|
287
290
|
import uuid
|
|
288
291
|
|
|
289
|
-
print(uuid.uuid4()
|
|
292
|
+
print(str(uuid.uuid4()))
|
|
290
293
|
PY
|
|
291
294
|
return 0
|
|
292
295
|
fi
|
|
293
296
|
printf '%s-%s-%s\n' "$(date +%s)" "$$" "${RANDOM:-0}"
|
|
294
297
|
}
|
|
295
298
|
|
|
299
|
+
rainskills_v2_enabled() {
|
|
300
|
+
[[ "${RAINSKILLS_TELEMETRY_DISABLED:-0}" != "1" ]]
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
initialize_rainskills_v2_identity() {
|
|
304
|
+
rainskills_v2_enabled || return 0
|
|
305
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
306
|
+
local telemetry_dir package_file
|
|
307
|
+
telemetry_dir="${RAINSKILLS_TELEMETRY_DIR:-${HOME:-/tmp}/.rainbond/rainskills/telemetry}"
|
|
308
|
+
package_file="$SCRIPT_DIR/package.json"
|
|
309
|
+
RAINSKILLS_TELEMETRY_INSTALLATION_ID="$({
|
|
310
|
+
python3 - "$telemetry_dir" "${RAINSKILLS_TELEMETRY_INSTALLATION_ID:-}" <<'PY'
|
|
311
|
+
import os
|
|
312
|
+
import re
|
|
313
|
+
import sys
|
|
314
|
+
import uuid
|
|
315
|
+
|
|
316
|
+
directory, provided = sys.argv[1:]
|
|
317
|
+
pattern = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", re.I)
|
|
318
|
+
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
319
|
+
os.chmod(directory, 0o700)
|
|
320
|
+
path = os.path.join(directory, "installation-id")
|
|
321
|
+
value = provided.strip()
|
|
322
|
+
if not pattern.match(value):
|
|
323
|
+
try:
|
|
324
|
+
value = open(path, encoding="utf-8").read().strip()
|
|
325
|
+
except Exception:
|
|
326
|
+
value = ""
|
|
327
|
+
if not pattern.match(value):
|
|
328
|
+
value = str(uuid.uuid4())
|
|
329
|
+
temporary = "{}.{}.tmp".format(path, os.getpid())
|
|
330
|
+
with open(temporary, "x", encoding="utf-8") as stream:
|
|
331
|
+
stream.write(value + "\n")
|
|
332
|
+
os.chmod(temporary, 0o600)
|
|
333
|
+
os.replace(temporary, path)
|
|
334
|
+
os.chmod(path, 0o600)
|
|
335
|
+
print(value)
|
|
336
|
+
PY
|
|
337
|
+
} 2>/dev/null || true)"
|
|
338
|
+
if [[ -z "${RAINSKILLS_PACKAGE_VERSION:-}" && -f "$package_file" ]]; then
|
|
339
|
+
RAINSKILLS_PACKAGE_VERSION="$({
|
|
340
|
+
python3 - "$package_file" <<'PY'
|
|
341
|
+
import json
|
|
342
|
+
import sys
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
value = json.load(open(sys.argv[1], encoding="utf-8")).get("version")
|
|
346
|
+
except Exception:
|
|
347
|
+
value = None
|
|
348
|
+
print(value if isinstance(value, str) and value else "unknown")
|
|
349
|
+
PY
|
|
350
|
+
} 2>/dev/null || printf 'unknown')"
|
|
351
|
+
fi
|
|
352
|
+
export RAINSKILLS_TELEMETRY_INSTALLATION_ID RAINSKILLS_PACKAGE_VERSION
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
rainskills_v2_action() {
|
|
356
|
+
case "$RAINSKILLS_INSTALL_ACTION" in
|
|
357
|
+
install|refresh|upgrade|repair) printf '%s\n' "$RAINSKILLS_INSTALL_ACTION" ;;
|
|
358
|
+
connect) printf 'repair\n' ;;
|
|
359
|
+
*) printf 'install\n' ;;
|
|
360
|
+
esac
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
rainskills_v2_agent() {
|
|
364
|
+
case "$1" in
|
|
365
|
+
codex) printf 'codex\n' ;;
|
|
366
|
+
claude|claude_code) printf 'claude_code\n' ;;
|
|
367
|
+
pi) printf 'pi\n' ;;
|
|
368
|
+
dsh|deepseek_harness|deepseek) printf 'deepseek\n' ;;
|
|
369
|
+
workbuddy) printf 'workbuddy\n' ;;
|
|
370
|
+
other) printf 'other\n' ;;
|
|
371
|
+
*) printf 'unknown\n' ;;
|
|
372
|
+
esac
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
rainskills_v2_os_type() {
|
|
376
|
+
case "$(uname -s 2>/dev/null || true)" in
|
|
377
|
+
Darwin) printf 'darwin\n' ;;
|
|
378
|
+
MINGW*|MSYS*|CYGWIN*) printf 'windows\n' ;;
|
|
379
|
+
*) printf 'linux\n' ;;
|
|
380
|
+
esac
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
rainskills_v2_os_arch() {
|
|
384
|
+
case "$(uname -m 2>/dev/null || true)" in
|
|
385
|
+
x86_64|amd64) printf 'amd64\n' ;;
|
|
386
|
+
arm64|aarch64) printf 'arm64\n' ;;
|
|
387
|
+
"") printf 'unknown\n' ;;
|
|
388
|
+
*) printf 'other\n' ;;
|
|
389
|
+
esac
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
rainskills_v2_execution_environment() {
|
|
393
|
+
if [[ -n "${WSL_DISTRO_NAME:-}" || -n "${WSL_INTEROP:-}" ]]; then
|
|
394
|
+
printf 'wsl\n'
|
|
395
|
+
elif [[ -n "${SSH_CONNECTION:-}" || -n "${SSH_CLIENT:-}" ]]; then
|
|
396
|
+
printf 'ssh\n'
|
|
397
|
+
elif [[ -n "${container:-}" ]]; then
|
|
398
|
+
printf 'container\n'
|
|
399
|
+
else
|
|
400
|
+
printf 'native\n'
|
|
401
|
+
fi
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
rainskills_v2_error_stage() {
|
|
405
|
+
case "${RAINSKILLS_INSTALL_FAILURE_STAGE:-install}" in
|
|
406
|
+
bootstrap) printf 'preflight\n' ;;
|
|
407
|
+
download) printf 'download\n' ;;
|
|
408
|
+
skill_installation) printf 'agent_configuration\n' ;;
|
|
409
|
+
authorization) printf 'authorization\n' ;;
|
|
410
|
+
verification) printf 'verification\n' ;;
|
|
411
|
+
configuration) printf 'agent_configuration\n' ;;
|
|
412
|
+
*) printf 'install\n' ;;
|
|
413
|
+
esac
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
rainskills_v2_error_code() {
|
|
417
|
+
case "${RAINSKILLS_INSTALL_FAILURE_CATEGORY:-unknown}" in
|
|
418
|
+
invalid_arguments) printf 'invalid_arguments\n' ;;
|
|
419
|
+
tarball_unavailable|download_failed) printf 'download_failed\n' ;;
|
|
420
|
+
authorization_failed) printf 'authorization_failed\n' ;;
|
|
421
|
+
mcp_verification_failed) printf 'verification_failed\n' ;;
|
|
422
|
+
mcp_configuration_failed|cli_configuration_failed|skill_installation_failed) printf 'agent_config_failed\n' ;;
|
|
423
|
+
network_unreachable) printf 'network_unreachable\n' ;;
|
|
424
|
+
*) printf 'unknown\n' ;;
|
|
425
|
+
esac
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
report_rainskills_v2_event() {
|
|
429
|
+
local event_type="$1"
|
|
430
|
+
local status="${2:-}"
|
|
431
|
+
local agent="${3:-}"
|
|
432
|
+
local error_stage="${4:-}"
|
|
433
|
+
local error_code="${5:-}"
|
|
434
|
+
rainskills_v2_enabled || return 0
|
|
435
|
+
[[ -n "${RAINSKILLS_TELEMETRY_INSTALLATION_ID:-}" ]] || return 0
|
|
436
|
+
command -v curl >/dev/null 2>&1 || return 0
|
|
437
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
438
|
+
|
|
439
|
+
local payload telemetry_dir
|
|
440
|
+
telemetry_dir="${RAINSKILLS_TELEMETRY_DIR:-${HOME:-/tmp}/.rainbond/rainskills/telemetry}"
|
|
441
|
+
payload="$({
|
|
442
|
+
python3 - \
|
|
443
|
+
"$event_type" \
|
|
444
|
+
"$RAINSKILLS_TELEMETRY_INSTALLATION_ID" \
|
|
445
|
+
"$RAINSKILLS_INSTALL_ATTEMPT_ID" \
|
|
446
|
+
"${RAINSKILLS_PACKAGE_VERSION:-unknown}" \
|
|
447
|
+
"$(rainskills_v2_action)" \
|
|
448
|
+
"$(rainskills_v2_agent "$agent")" \
|
|
449
|
+
"$(rainskills_v2_os_type)" \
|
|
450
|
+
"$(rainskills_v2_os_arch)" \
|
|
451
|
+
"$(rainskills_v2_execution_environment)" \
|
|
452
|
+
"$status" \
|
|
453
|
+
"$error_stage" \
|
|
454
|
+
"$error_code" \
|
|
455
|
+
"$telemetry_dir" <<'PY'
|
|
456
|
+
import datetime
|
|
457
|
+
import json
|
|
458
|
+
import os
|
|
459
|
+
import sys
|
|
460
|
+
import time
|
|
461
|
+
import uuid
|
|
462
|
+
|
|
463
|
+
(event_type, installation_id, attempt_id, package_version, action, agent,
|
|
464
|
+
os_type, os_arch, execution_environment, status, error_stage, error_code,
|
|
465
|
+
telemetry_dir) = sys.argv[1:]
|
|
466
|
+
event = {
|
|
467
|
+
"schema": "rainskills.telemetry-event.v2",
|
|
468
|
+
"event_id": str(uuid.uuid4()),
|
|
469
|
+
"event_type": event_type,
|
|
470
|
+
"installation_id": installation_id,
|
|
471
|
+
"package_version": package_version,
|
|
472
|
+
"occurred_at": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z"),
|
|
473
|
+
}
|
|
474
|
+
if event_type in {"install_result", "agent_config_result"}:
|
|
475
|
+
event["install_attempt_id"] = attempt_id
|
|
476
|
+
event["action"] = action
|
|
477
|
+
if event_type == "install_result":
|
|
478
|
+
event.update({
|
|
479
|
+
"os_type": os_type,
|
|
480
|
+
"os_arch": os_arch,
|
|
481
|
+
"execution_environment": execution_environment,
|
|
482
|
+
"status": status,
|
|
483
|
+
})
|
|
484
|
+
elif event_type == "agent_config_result":
|
|
485
|
+
event.update({"agent_type": agent, "status": status})
|
|
486
|
+
if status == "failed":
|
|
487
|
+
event["error_stage"] = error_stage
|
|
488
|
+
event["error_code"] = error_code
|
|
489
|
+
pending_dir = os.path.join(telemetry_dir, "pending-v2")
|
|
490
|
+
try:
|
|
491
|
+
os.makedirs(pending_dir, mode=0o700, exist_ok=True)
|
|
492
|
+
os.chmod(telemetry_dir, 0o700)
|
|
493
|
+
os.chmod(pending_dir, 0o700)
|
|
494
|
+
now = time.time()
|
|
495
|
+
entries = []
|
|
496
|
+
for name in os.listdir(pending_dir):
|
|
497
|
+
if not name.endswith(".json"):
|
|
498
|
+
continue
|
|
499
|
+
file_path = os.path.join(pending_dir, name)
|
|
500
|
+
try:
|
|
501
|
+
entries.append((os.stat(file_path).st_mtime, file_path))
|
|
502
|
+
except OSError:
|
|
503
|
+
pass
|
|
504
|
+
entries.sort()
|
|
505
|
+
expired = [entry for entry in entries if now - entry[0] > 7 * 24 * 60 * 60]
|
|
506
|
+
remaining = [entry for entry in entries if now - entry[0] <= 7 * 24 * 60 * 60]
|
|
507
|
+
overflow = remaining[:max(0, len(remaining) - 99)]
|
|
508
|
+
for _, file_path in expired + overflow:
|
|
509
|
+
try:
|
|
510
|
+
os.remove(file_path)
|
|
511
|
+
except OSError:
|
|
512
|
+
pass
|
|
513
|
+
event_path = os.path.join(pending_dir, event["event_id"] + ".json")
|
|
514
|
+
with open(event_path, "x", encoding="utf-8") as stream:
|
|
515
|
+
stream.write(json.dumps(event, separators=(",", ":")) + "\n")
|
|
516
|
+
os.chmod(event_path, 0o600)
|
|
517
|
+
except Exception:
|
|
518
|
+
pass
|
|
519
|
+
print(json.dumps(event, separators=(",", ":")))
|
|
520
|
+
PY
|
|
521
|
+
} 2>/dev/null || true)"
|
|
522
|
+
[[ -n "$payload" ]] || return 0
|
|
523
|
+
local event_id pending_file
|
|
524
|
+
event_id="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["event_id"])' <<<"$payload" 2>/dev/null || true)"
|
|
525
|
+
pending_file="$telemetry_dir/pending-v2/${event_id}.json"
|
|
526
|
+
(
|
|
527
|
+
if curl --silent --show-error --connect-timeout 2 --max-time 3 \
|
|
528
|
+
-X POST "$RAINSKILLS_TELEMETRY_REPORT_URL" \
|
|
529
|
+
-H 'Content-Type: application/json' \
|
|
530
|
+
--data-binary "$payload" >/dev/null 2>&1; then
|
|
531
|
+
[[ -n "$event_id" ]] && rm -f "$pending_file"
|
|
532
|
+
fi
|
|
533
|
+
) &
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
record_rainskills_v2_configured_agent() {
|
|
537
|
+
local agent
|
|
538
|
+
agent="$(rainskills_v2_agent "$1")"
|
|
539
|
+
[[ "$agent" != "unknown" ]] || return 0
|
|
540
|
+
rainskills_v2_enabled || return 0
|
|
541
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
542
|
+
local telemetry_dir
|
|
543
|
+
telemetry_dir="${RAINSKILLS_TELEMETRY_DIR:-${HOME:-/tmp}/.rainbond/rainskills/telemetry}"
|
|
544
|
+
python3 - "$telemetry_dir" "$agent" <<'PY' >/dev/null 2>&1 || true
|
|
545
|
+
import json
|
|
546
|
+
import os
|
|
547
|
+
import sys
|
|
548
|
+
|
|
549
|
+
directory, agent = sys.argv[1:]
|
|
550
|
+
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
551
|
+
os.chmod(directory, 0o700)
|
|
552
|
+
path = os.path.join(directory, "configured-agents.json")
|
|
553
|
+
try:
|
|
554
|
+
current = json.load(open(path, encoding="utf-8"))
|
|
555
|
+
except Exception:
|
|
556
|
+
current = []
|
|
557
|
+
agents = sorted(set(value for value in current + [agent] if isinstance(value, str)))
|
|
558
|
+
temporary = "{}.{}.tmp".format(path, os.getpid())
|
|
559
|
+
with open(temporary, "x", encoding="utf-8") as stream:
|
|
560
|
+
json.dump(agents, stream, separators=(",", ":"))
|
|
561
|
+
stream.write("\n")
|
|
562
|
+
os.chmod(temporary, 0o600)
|
|
563
|
+
os.replace(temporary, path)
|
|
564
|
+
os.chmod(path, 0o600)
|
|
565
|
+
PY
|
|
566
|
+
}
|
|
567
|
+
|
|
296
568
|
rainskills_install_client_for_target() {
|
|
297
569
|
case "$1" in
|
|
298
570
|
codex)
|
|
@@ -325,6 +597,7 @@ report_rainskills_installation() {
|
|
|
325
597
|
local failure_stage="${3:-}"
|
|
326
598
|
local failure_category="${4:-}"
|
|
327
599
|
|
|
600
|
+
[[ "${RAINSKILLS_LEGACY_TELEMETRY_ENABLED:-0}" == "1" ]] || return 0
|
|
328
601
|
[[ -n "$RAINSKILLS_INSTALL_ATTEMPT_ID" ]] || return 0
|
|
329
602
|
if [[ "$phase" == "authorized" || "$phase" == "configured" ]]; then
|
|
330
603
|
[[ -n "$RAINSKILLS_INSTALL_EID" ]] || return 0
|
|
@@ -423,6 +696,7 @@ report_rainskills_lifecycle_event() {
|
|
|
423
696
|
local blocked_reason="${6:-}"
|
|
424
697
|
local auth_method="${7:-}"
|
|
425
698
|
|
|
699
|
+
[[ "${RAINSKILLS_LEGACY_TELEMETRY_ENABLED:-0}" == "1" ]] || return 0
|
|
426
700
|
[[ -n "$RAINSKILLS_INSTALL_ATTEMPT_ID" ]] || return 0
|
|
427
701
|
command -v python3 >/dev/null 2>&1 || return 0
|
|
428
702
|
RAINSKILLS_TELEMETRY_SEQUENCE=$((RAINSKILLS_TELEMETRY_SEQUENCE + 1))
|
|
@@ -536,6 +810,7 @@ initialize_rainskills_installation_reporting() {
|
|
|
536
810
|
esac
|
|
537
811
|
done
|
|
538
812
|
RAINSKILLS_INSTALL_CLIENT="$(rainskills_install_client_for_target "$target")"
|
|
813
|
+
initialize_rainskills_v2_identity
|
|
539
814
|
report_rainskills_lifecycle_event "bootstrap" "resume" "resume" "started"
|
|
540
815
|
report_rainskills_installation "started" "started"
|
|
541
816
|
}
|
|
@@ -634,6 +909,25 @@ set_rainskills_failure_context() {
|
|
|
634
909
|
|
|
635
910
|
report_unhandled_rainskills_installation_failure() {
|
|
636
911
|
local exit_status="$1"
|
|
912
|
+
if [[ "$exit_status" -ne 0 && \
|
|
913
|
+
"${BASH_SUBSHELL:-0}" -eq 0 && \
|
|
914
|
+
"$RAINSKILLS_V2_TERMINAL_REPORTED" -eq 0 ]]; then
|
|
915
|
+
RAINSKILLS_V2_TERMINAL_REPORTED=1
|
|
916
|
+
if [[ -n "$RAINSKILLS_V2_CURRENT_AGENT" ]]; then
|
|
917
|
+
report_rainskills_v2_event \
|
|
918
|
+
"agent_config_result" \
|
|
919
|
+
"failed" \
|
|
920
|
+
"$RAINSKILLS_V2_CURRENT_AGENT" \
|
|
921
|
+
"agent_configuration" \
|
|
922
|
+
"agent_config_failed"
|
|
923
|
+
fi
|
|
924
|
+
report_rainskills_v2_event \
|
|
925
|
+
"install_result" \
|
|
926
|
+
"failed" \
|
|
927
|
+
"" \
|
|
928
|
+
"$(rainskills_v2_error_stage)" \
|
|
929
|
+
"$(rainskills_v2_error_code)"
|
|
930
|
+
fi
|
|
637
931
|
if [[ "$exit_status" -ne 0 && \
|
|
638
932
|
"${BASH_SUBSHELL:-0}" -eq 0 && \
|
|
639
933
|
"$RAINSKILLS_INSTALL_TERMINAL_REPORTED" -eq 0 ]]; then
|
|
@@ -716,6 +1010,23 @@ handle_installer_exit() {
|
|
|
716
1010
|
}
|
|
717
1011
|
|
|
718
1012
|
die() {
|
|
1013
|
+
if [[ "${BASH_SUBSHELL:-0}" -eq 0 && "$RAINSKILLS_V2_TERMINAL_REPORTED" -eq 0 ]]; then
|
|
1014
|
+
RAINSKILLS_V2_TERMINAL_REPORTED=1
|
|
1015
|
+
if [[ -n "$RAINSKILLS_V2_CURRENT_AGENT" ]]; then
|
|
1016
|
+
report_rainskills_v2_event \
|
|
1017
|
+
"agent_config_result" \
|
|
1018
|
+
"failed" \
|
|
1019
|
+
"$RAINSKILLS_V2_CURRENT_AGENT" \
|
|
1020
|
+
"agent_configuration" \
|
|
1021
|
+
"agent_config_failed"
|
|
1022
|
+
fi
|
|
1023
|
+
report_rainskills_v2_event \
|
|
1024
|
+
"install_result" \
|
|
1025
|
+
"failed" \
|
|
1026
|
+
"" \
|
|
1027
|
+
"$(rainskills_v2_error_stage)" \
|
|
1028
|
+
"$(rainskills_v2_error_code)"
|
|
1029
|
+
fi
|
|
719
1030
|
if [[ "${BASH_SUBSHELL:-0}" -eq 0 && "$RAINSKILLS_INSTALL_TERMINAL_REPORTED" -eq 0 ]]; then
|
|
720
1031
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
721
1032
|
report_rainskills_installation \
|
|
@@ -1104,6 +1415,24 @@ collect_destinations() {
|
|
|
1104
1415
|
printf '%s\n' "${destinations[@]}"
|
|
1105
1416
|
}
|
|
1106
1417
|
|
|
1418
|
+
collect_target_agents() {
|
|
1419
|
+
if [[ -n "$CUSTOM_DEST" ]]; then
|
|
1420
|
+
printf 'unknown\n'
|
|
1421
|
+
return 0
|
|
1422
|
+
fi
|
|
1423
|
+
case "$TARGET" in
|
|
1424
|
+
claude) printf 'claude\n' ;;
|
|
1425
|
+
codex) printf 'codex\n' ;;
|
|
1426
|
+
pi) printf 'pi\n' ;;
|
|
1427
|
+
dsh) printf 'dsh\n' ;;
|
|
1428
|
+
workbuddy) printf 'workbuddy\n' ;;
|
|
1429
|
+
all)
|
|
1430
|
+
printf '%s\n' claude codex pi dsh workbuddy
|
|
1431
|
+
;;
|
|
1432
|
+
*) printf 'unknown\n' ;;
|
|
1433
|
+
esac
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1107
1436
|
normalize_rainbond_url() {
|
|
1108
1437
|
local raw
|
|
1109
1438
|
raw="$(trim "$1")"
|
|
@@ -2549,6 +2878,8 @@ do_refresh() {
|
|
|
2549
2878
|
log "如果想立刻在当前终端使用,请执行:source ${ACTIVE_SHELL_RC}"
|
|
2550
2879
|
fi
|
|
2551
2880
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
2881
|
+
RAINSKILLS_V2_TERMINAL_REPORTED=1
|
|
2882
|
+
report_rainskills_v2_event "install_result" "success"
|
|
2552
2883
|
report_rainskills_lifecycle_event "configure_cli" "install_cli" "configure_cli" "completed"
|
|
2553
2884
|
report_rainskills_installation "configured" "success"
|
|
2554
2885
|
}
|
|
@@ -2629,6 +2960,10 @@ configure_runtime_connection() {
|
|
|
2629
2960
|
report_rainskills_lifecycle_event "configure_cli" "install_cli" "configure_cli" "started"
|
|
2630
2961
|
install_local_cli
|
|
2631
2962
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
2963
|
+
RAINSKILLS_V2_TERMINAL_REPORTED=1
|
|
2964
|
+
report_rainskills_v2_event "agent_config_result" "success" "$TARGET"
|
|
2965
|
+
record_rainskills_v2_configured_agent "$TARGET"
|
|
2966
|
+
report_rainskills_v2_event "install_result" "success"
|
|
2632
2967
|
report_rainskills_lifecycle_event "configure_cli" "install_cli" "configure_cli" "completed"
|
|
2633
2968
|
report_rainskills_installation "configured" "success"
|
|
2634
2969
|
|
|
@@ -2690,16 +3025,28 @@ main() {
|
|
|
2690
3025
|
done
|
|
2691
3026
|
|
|
2692
3027
|
local destinations=()
|
|
3028
|
+
local target_agents=()
|
|
2693
3029
|
local dest
|
|
2694
3030
|
while IFS= read -r dest; do
|
|
2695
3031
|
destinations+=("$dest")
|
|
2696
3032
|
done < <(collect_destinations)
|
|
2697
3033
|
|
|
2698
|
-
|
|
3034
|
+
local target_agent
|
|
3035
|
+
while IFS= read -r target_agent; do
|
|
3036
|
+
target_agents+=("$target_agent")
|
|
3037
|
+
done < <(collect_target_agents)
|
|
3038
|
+
|
|
3039
|
+
local destination_index
|
|
3040
|
+
for destination_index in "${!destinations[@]}"; do
|
|
3041
|
+
dest="${destinations[$destination_index]}"
|
|
3042
|
+
RAINSKILLS_V2_CURRENT_AGENT="${target_agents[$destination_index]:-unknown}"
|
|
2699
3043
|
install_detail_log "安装到:$dest"
|
|
2700
3044
|
for skill_dir in "${skills[@]}"; do
|
|
2701
3045
|
copy_skill "$skill_dir" "$dest"
|
|
2702
3046
|
done
|
|
3047
|
+
report_rainskills_v2_event "agent_config_result" "success" "$RAINSKILLS_V2_CURRENT_AGENT"
|
|
3048
|
+
record_rainskills_v2_configured_agent "$RAINSKILLS_V2_CURRENT_AGENT"
|
|
3049
|
+
RAINSKILLS_V2_CURRENT_AGENT=""
|
|
2703
3050
|
done
|
|
2704
3051
|
|
|
2705
3052
|
install_local_cli
|
|
@@ -2708,6 +3055,9 @@ main() {
|
|
|
2708
3055
|
install_detail_log "安装完成。本次:${INSTALL_COUNT_NEW} 项新装 / ${INSTALL_COUNT_UPDATED} 项已更新 / ${INSTALL_COUNT_UNCHANGED} 项已是最新 / ${INSTALL_COUNT_FORCED} 项强制覆盖"
|
|
2709
3056
|
install_detail_log ""
|
|
2710
3057
|
print_capability_summary
|
|
3058
|
+
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
3059
|
+
RAINSKILLS_V2_TERMINAL_REPORTED=1
|
|
3060
|
+
report_rainskills_v2_event "install_result" "success"
|
|
2711
3061
|
}
|
|
2712
3062
|
|
|
2713
3063
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
@@ -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.30 <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
|
|
|
@@ -50,7 +50,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
50
50
|
|
|
51
51
|
DeepSeek Harness 和 WorkBuddy 也必须保持原命令附着直到退出;如果宿主把长命令转成持久终端或后台任务,只能轮询该原始命令句柄,不得另起 `runtime status` 推测授权结果。WorkBuddy 安装后若当前任务的 Skill 清单尚未刷新,在下一条业务动作前先刷新 Skill 列表或新建任务,不得退回内置 Sites 代替 Rainbond 部署。
|
|
52
52
|
|
|
53
|
-
Rainskills 只保存一个全局运行环境,不维护环境列表、名称、默认值或环境 ID。使用固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.
|
|
53
|
+
Rainskills 只保存一个全局运行环境,不维护环境列表、名称、默认值或环境 ID。使用固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.30`):
|
|
54
54
|
|
|
55
55
|
- 状态:执行 `runtime status --json`。
|
|
56
56
|
- 首次连接:执行 `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.30",
|
|
4
4
|
"description": "Interactive Rainbond skill installer for Codex, Claude Code, Pi Agent, DeepSeek Harness, and WorkBuddy",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/goodrain/rainskills#readme",
|
|
@@ -22,7 +22,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
25
|
-
"package_version": "rainskills@0.1.
|
|
25
|
+
"package_version": "rainskills@0.1.30",
|
|
26
26
|
"runtime_status": [
|
|
27
27
|
"node",
|
|
28
28
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -34,7 +34,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
36
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
37
|
-
"package_version": "rainskills@0.1.
|
|
37
|
+
"package_version": "rainskills@0.1.30",
|
|
38
38
|
"runtime_status": [
|
|
39
39
|
"node",
|
|
40
40
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -57,7 +57,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
57
57
|
```json
|
|
58
58
|
{
|
|
59
59
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
60
|
-
"package_version": "rainskills@0.1.
|
|
60
|
+
"package_version": "rainskills@0.1.30",
|
|
61
61
|
"runtime_status": [
|
|
62
62
|
"node",
|
|
63
63
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -34,7 +34,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
36
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
37
|
-
"package_version": "rainskills@0.1.
|
|
37
|
+
"package_version": "rainskills@0.1.30",
|
|
38
38
|
"runtime_status": [
|
|
39
39
|
"node",
|
|
40
40
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -34,7 +34,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
36
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
37
|
-
"package_version": "rainskills@0.1.
|
|
37
|
+
"package_version": "rainskills@0.1.30",
|
|
38
38
|
"runtime_status": [
|
|
39
39
|
"node",
|
|
40
40
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -57,7 +57,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
57
57
|
```json
|
|
58
58
|
{
|
|
59
59
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
60
|
-
"package_version": "rainskills@0.1.
|
|
60
|
+
"package_version": "rainskills@0.1.30",
|
|
61
61
|
"runtime_status": [
|
|
62
62
|
"node",
|
|
63
63
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -22,7 +22,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
25
|
-
"package_version": "rainskills@0.1.
|
|
25
|
+
"package_version": "rainskills@0.1.30",
|
|
26
26
|
"runtime_status": [
|
|
27
27
|
"node",
|
|
28
28
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -32,7 +32,7 @@ Do not use it to deploy an application to an existing Rainbond. Route those requ
|
|
|
32
32
|
## Workflow
|
|
33
33
|
|
|
34
34
|
1. Read [installation-policy.md](references/installation-policy.md).
|
|
35
|
-
2. Use the installed local launcher `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`; its protected runtime package marker is `rainskills@0.1.
|
|
35
|
+
2. Use the installed local launcher `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`; its protected runtime package marker is `rainskills@0.1.30` 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. 业务 Skill 的四项运行环境菜单会把本机或独立服务器选择写入 `rainskills.next-action.v1` 的显式 `--location`;收到这类 next-action 后直接执行固定 argv,不得再次调用 `private-deployment-location`。只有用户直接要求安装 Rainbond 平台且尚未选择部署位置时,才执行 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`。平台安装 onboarding 只保存安装断点,不保存或恢复业务 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. 主机集群开始前必须获得 explicit confirmation,并使用受限 AI 交接:首次调用在固定安装 argv 后追加 `--agent-handoff`,记录该子进程会话;用户确认后,使用相同固定 argv 追加 `--agent-handoff --yes` 一次。不得向等待进程写入 `y`、不得启动第二个竞争安装、不得 `kill` 安装进程,也不得让用户复制完整安装或恢复命令。用户取消时,仅使用同一 argv 追加 `--agent-handoff --cancel`;它只能清除匹配的待确认状态,不能建立 SSH 连接或修改服务器。
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { randomUUID: defaultRandomUUID } = require("node:crypto");
|
|
7
|
+
|
|
8
|
+
const SCHEMA = "rainskills.telemetry-event.v2";
|
|
9
|
+
const DEFAULT_REPORT_URL = "https://log.rainbond.com/api/rainskills/events";
|
|
10
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11
|
+
const AGENTS = new Set(["codex", "pi", "claude_code", "deepseek", "workbuddy", "other", "unknown"]);
|
|
12
|
+
const MAX_PENDING_EVENTS = 100;
|
|
13
|
+
const MAX_PENDING_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
function defaultDirectory() {
|
|
16
|
+
return path.join(os.homedir(), ".rainbond", "rainskills", "telemetry");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function safeMkdir(directory) {
|
|
20
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
21
|
+
try { fs.chmodSync(directory, 0o700); } catch { /* best effort */ }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readPrivateText(file) {
|
|
25
|
+
try {
|
|
26
|
+
const info = fs.lstatSync(file);
|
|
27
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) return null;
|
|
28
|
+
return fs.readFileSync(file, "utf8");
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") return null;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writePrivateFile(file, content) {
|
|
36
|
+
safeMkdir(path.dirname(file));
|
|
37
|
+
const temporary = path.join(
|
|
38
|
+
path.dirname(file),
|
|
39
|
+
`.${path.basename(file)}.${process.pid}.${defaultRandomUUID()}.tmp`
|
|
40
|
+
);
|
|
41
|
+
try {
|
|
42
|
+
fs.writeFileSync(temporary, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
43
|
+
fs.renameSync(temporary, file);
|
|
44
|
+
fs.chmodSync(file, 0o600);
|
|
45
|
+
} finally {
|
|
46
|
+
try { fs.rmSync(temporary, { force: true }); } catch { /* keep original error */ }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ensureInstallationId(directory = defaultDirectory(), randomUUID = defaultRandomUUID) {
|
|
51
|
+
const file = path.join(directory, "installation-id");
|
|
52
|
+
const existing = (readPrivateText(file) || "").trim();
|
|
53
|
+
if (UUID_PATTERN.test(existing)) return existing;
|
|
54
|
+
const generated = randomUUID();
|
|
55
|
+
if (!UUID_PATTERN.test(generated)) throw new Error("telemetry installation id must be a UUID");
|
|
56
|
+
writePrivateFile(file, `${generated}\n`);
|
|
57
|
+
return generated;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeAgent(value) {
|
|
61
|
+
const aliases = {
|
|
62
|
+
claude: "claude_code",
|
|
63
|
+
dsh: "deepseek",
|
|
64
|
+
deepseek_harness: "deepseek",
|
|
65
|
+
};
|
|
66
|
+
const normalized = aliases[value] || value;
|
|
67
|
+
return AGENTS.has(normalized) ? normalized : "unknown";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function readConfiguredAgent(directory = defaultDirectory()) {
|
|
71
|
+
try {
|
|
72
|
+
const raw = readPrivateText(path.join(directory, "configured-agents.json"));
|
|
73
|
+
const agents = JSON.parse(raw || "[]").map(normalizeAgent);
|
|
74
|
+
const unique = [...new Set(agents.filter((agent) => agent !== "unknown"))];
|
|
75
|
+
return unique.length === 1 ? unique[0] : "unknown";
|
|
76
|
+
} catch {
|
|
77
|
+
return "unknown";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function writeConfiguredAgents(directory, agents) {
|
|
82
|
+
let existing = [];
|
|
83
|
+
try {
|
|
84
|
+
existing = JSON.parse(readPrivateText(path.join(directory, "configured-agents.json")) || "[]");
|
|
85
|
+
} catch { /* replace invalid state with the validated targets */ }
|
|
86
|
+
const normalized = [...new Set([...existing, ...(agents || [])]
|
|
87
|
+
.map(normalizeAgent)
|
|
88
|
+
.filter((agent) => agent !== "unknown"))].sort();
|
|
89
|
+
writePrivateFile(path.join(directory, "configured-agents.json"), `${JSON.stringify(normalized)}\n`);
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function loadState(directory) {
|
|
94
|
+
try {
|
|
95
|
+
const raw = readPrivateText(path.join(directory, "v2-state.json"));
|
|
96
|
+
const parsed = JSON.parse(raw || "{}");
|
|
97
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
98
|
+
} catch {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function saveState(directory, state) {
|
|
104
|
+
writePrivateFile(path.join(directory, "v2-state.json"), `${JSON.stringify(state)}\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function pendingDirectory(directory) {
|
|
108
|
+
return path.join(directory, "pending-v2");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function prunePending(directory, nowMs) {
|
|
112
|
+
const pending = pendingDirectory(directory);
|
|
113
|
+
let entries;
|
|
114
|
+
try {
|
|
115
|
+
entries = fs.readdirSync(pending)
|
|
116
|
+
.filter((name) => name.endsWith(".json"))
|
|
117
|
+
.map((name) => {
|
|
118
|
+
const file = path.join(pending, name);
|
|
119
|
+
return { file, mtimeMs: fs.lstatSync(file).mtimeMs };
|
|
120
|
+
})
|
|
121
|
+
.sort((left, right) => left.mtimeMs - right.mtimeMs);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error.code === "ENOENT") return;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
const expired = entries.filter((entry) => nowMs - entry.mtimeMs > MAX_PENDING_AGE_MS);
|
|
127
|
+
const remaining = entries.filter((entry) => nowMs - entry.mtimeMs <= MAX_PENDING_AGE_MS);
|
|
128
|
+
const overflow = remaining.slice(0, Math.max(0, remaining.length - MAX_PENDING_EVENTS + 1));
|
|
129
|
+
for (const entry of [...expired, ...overflow]) {
|
|
130
|
+
try { fs.rmSync(entry.file, { force: true }); } catch { /* best effort */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildEvent({ installationId, packageVersion, agentType, randomUUID, now }, input) {
|
|
135
|
+
const event = {
|
|
136
|
+
schema: SCHEMA,
|
|
137
|
+
event_id: randomUUID(),
|
|
138
|
+
event_type: input.event_type,
|
|
139
|
+
installation_id: installationId,
|
|
140
|
+
package_version: packageVersion,
|
|
141
|
+
occurred_at: now().toISOString(),
|
|
142
|
+
};
|
|
143
|
+
const fields = [
|
|
144
|
+
"install_attempt_id", "action", "agent_type", "os_type", "os_arch",
|
|
145
|
+
"execution_environment", "status", "error_stage", "error_code",
|
|
146
|
+
];
|
|
147
|
+
for (const field of fields) {
|
|
148
|
+
const usesDefaultAgent = ["first_use_result", "active_daily"].includes(input.event_type);
|
|
149
|
+
const value = field === "agent_type"
|
|
150
|
+
? (input[field] || (usesDefaultAgent ? agentType : ""))
|
|
151
|
+
: input[field];
|
|
152
|
+
if (typeof value === "string" && value.length > 0) event[field] = value;
|
|
153
|
+
}
|
|
154
|
+
if (event.agent_type) event.agent_type = normalizeAgent(event.agent_type);
|
|
155
|
+
return event;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createResultTelemetry({
|
|
159
|
+
directory = defaultDirectory(),
|
|
160
|
+
reportUrl = process.env.RAINSKILLS_TELEMETRY_REPORT_URL || DEFAULT_REPORT_URL,
|
|
161
|
+
fetchImpl = globalThis.fetch,
|
|
162
|
+
randomUUID = defaultRandomUUID,
|
|
163
|
+
now = () => new Date(),
|
|
164
|
+
timeoutMs = 1500,
|
|
165
|
+
installationId,
|
|
166
|
+
packageVersion = "unknown",
|
|
167
|
+
agentType,
|
|
168
|
+
disabled = process.env.RAINSKILLS_TELEMETRY_DISABLED === "1",
|
|
169
|
+
} = {}) {
|
|
170
|
+
const resolvedAgent = normalizeAgent(agentType || readConfiguredAgent(directory));
|
|
171
|
+
const resolvedInstallationId = installationId || (disabled ? null : ensureInstallationId(directory, randomUUID));
|
|
172
|
+
|
|
173
|
+
async function send(event, file) {
|
|
174
|
+
if (typeof fetchImpl !== "function" || !reportUrl) return false;
|
|
175
|
+
const controller = new AbortController();
|
|
176
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetchImpl(reportUrl, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
accept: "application/json",
|
|
182
|
+
"content-type": "application/json",
|
|
183
|
+
"idempotency-key": event.event_id,
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify(event),
|
|
186
|
+
signal: controller.signal,
|
|
187
|
+
});
|
|
188
|
+
const status = Number(response?.status || 0);
|
|
189
|
+
const shouldDiscard = Boolean(response?.ok)
|
|
190
|
+
|| (status >= 400 && status < 500 && status !== 429);
|
|
191
|
+
if (shouldDiscard && file) {
|
|
192
|
+
try { fs.rmSync(file, { force: true }); } catch { /* best effort */ }
|
|
193
|
+
}
|
|
194
|
+
return Boolean(response?.ok);
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
} finally {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function skippedResult() {
|
|
203
|
+
return { event: null, delivery: Promise.resolve(false), recorded: false };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function record(input, onceKey = null) {
|
|
207
|
+
if (disabled) return skippedResult();
|
|
208
|
+
const state = loadState(directory);
|
|
209
|
+
if (onceKey && state[onceKey]) return skippedResult();
|
|
210
|
+
|
|
211
|
+
const event = buildEvent({
|
|
212
|
+
installationId: resolvedInstallationId,
|
|
213
|
+
packageVersion,
|
|
214
|
+
agentType: resolvedAgent,
|
|
215
|
+
randomUUID,
|
|
216
|
+
now,
|
|
217
|
+
}, input);
|
|
218
|
+
const pending = pendingDirectory(directory);
|
|
219
|
+
safeMkdir(pending);
|
|
220
|
+
prunePending(directory, now().getTime());
|
|
221
|
+
const file = path.join(pending, `${event.event_id}.json`);
|
|
222
|
+
writePrivateFile(file, `${JSON.stringify(event)}\n`);
|
|
223
|
+
if (onceKey) saveState(directory, { ...state, [onceKey]: event.event_id });
|
|
224
|
+
return { event, delivery: send(event, file), recorded: true };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function recordFirstUse(status, error = {}) {
|
|
228
|
+
if (!new Set(["success", "failed"]).has(status)) return skippedResult();
|
|
229
|
+
return record({
|
|
230
|
+
event_type: "first_use_result",
|
|
231
|
+
status,
|
|
232
|
+
...(status === "failed" ? {
|
|
233
|
+
error_stage: error.error_stage,
|
|
234
|
+
error_code: error.error_code,
|
|
235
|
+
} : {}),
|
|
236
|
+
}, `first_use:${resolvedAgent}:${status}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function recordActiveDaily() {
|
|
240
|
+
const activeDate = now().toISOString().slice(0, 10);
|
|
241
|
+
return record({ event_type: "active_daily" }, `active:${resolvedAgent}:${activeDate}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function flushPending(limit = MAX_PENDING_EVENTS) {
|
|
245
|
+
if (disabled) return;
|
|
246
|
+
let files;
|
|
247
|
+
try {
|
|
248
|
+
files = fs.readdirSync(pendingDirectory(directory))
|
|
249
|
+
.filter((name) => name.endsWith(".json"))
|
|
250
|
+
.sort()
|
|
251
|
+
.slice(0, Math.max(0, Math.min(limit, MAX_PENDING_EVENTS)))
|
|
252
|
+
.map((name) => path.join(pendingDirectory(directory), name));
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (error.code === "ENOENT") return;
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
try {
|
|
259
|
+
const event = JSON.parse(readPrivateText(file));
|
|
260
|
+
await send(event, file);
|
|
261
|
+
} catch {
|
|
262
|
+
try { fs.rmSync(file, { force: true }); } catch { /* best effort */ }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
installationId: resolvedInstallationId,
|
|
269
|
+
agentType: resolvedAgent,
|
|
270
|
+
record,
|
|
271
|
+
recordFirstUse,
|
|
272
|
+
recordActiveDaily,
|
|
273
|
+
flushPending,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = {
|
|
278
|
+
DEFAULT_REPORT_URL,
|
|
279
|
+
SCHEMA,
|
|
280
|
+
createResultTelemetry,
|
|
281
|
+
ensureInstallationId,
|
|
282
|
+
normalizeAgent,
|
|
283
|
+
readConfiguredAgent,
|
|
284
|
+
writeConfiguredAgents,
|
|
285
|
+
};
|
|
@@ -98,6 +98,7 @@ function createLifecycleTelemetry({
|
|
|
98
98
|
randomUUID = crypto.randomUUID,
|
|
99
99
|
now = () => new Date().toISOString(),
|
|
100
100
|
timeoutMs = 1500,
|
|
101
|
+
enabled = process.env.RAINSKILLS_LEGACY_TELEMETRY_ENABLED === "1",
|
|
101
102
|
} = {}) {
|
|
102
103
|
let sequence = 0;
|
|
103
104
|
const deliveries = new Set();
|
|
@@ -213,6 +214,7 @@ function createLifecycleTelemetry({
|
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
async function send(event) {
|
|
217
|
+
if (!enabled) return false;
|
|
216
218
|
const result = await sendRequest(reportUrl, event);
|
|
217
219
|
if (result.ok) return true;
|
|
218
220
|
if (![400, 404, 415, 422].includes(result.status)) return false;
|
|
@@ -224,6 +226,7 @@ function createLifecycleTelemetry({
|
|
|
224
226
|
|
|
225
227
|
function record(input = {}) {
|
|
226
228
|
const event = buildEvent(input);
|
|
229
|
+
if (!enabled) return { event, delivery: Promise.resolve(false) };
|
|
227
230
|
writeLocalEvent(directory, event);
|
|
228
231
|
const delivery = send(event).catch(() => false);
|
|
229
232
|
deliveries.add(delivery);
|
|
@@ -18,6 +18,11 @@ const {
|
|
|
18
18
|
} = require("./windows-auth.js");
|
|
19
19
|
const { validateMcp } = require("./windows-client-config.js");
|
|
20
20
|
const { createLifecycleTelemetry } = require("./telemetry.js");
|
|
21
|
+
const {
|
|
22
|
+
createResultTelemetry,
|
|
23
|
+
normalizeAgent,
|
|
24
|
+
writeConfiguredAgents,
|
|
25
|
+
} = require("./result-telemetry.js");
|
|
21
26
|
const {
|
|
22
27
|
destinationsForHostTarget,
|
|
23
28
|
isHostTarget,
|
|
@@ -619,21 +624,100 @@ async function main(argv, dependencies = {}) {
|
|
|
619
624
|
const skills = discoverSkills(packageRoot);
|
|
620
625
|
const logger = dependencies.logger || ((message) => stdout.write(`${message}\n`));
|
|
621
626
|
const detailLogger = options.verbose ? logger : () => {};
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
627
|
+
const telemetryDirectory = path.join(home, ".rainbond", "rainskills", "telemetry");
|
|
628
|
+
const packageVersion = (() => {
|
|
629
|
+
try {
|
|
630
|
+
const value = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version;
|
|
631
|
+
return typeof value === "string" && value ? value : "unknown";
|
|
632
|
+
} catch {
|
|
633
|
+
return "unknown";
|
|
634
|
+
}
|
|
635
|
+
})();
|
|
636
|
+
const installAttemptId = UUID_PATTERN.test(environment.RAINSKILLS_INSTALL_ATTEMPT_ID || "")
|
|
637
|
+
? environment.RAINSKILLS_INSTALL_ATTEMPT_ID
|
|
638
|
+
: crypto.randomUUID();
|
|
639
|
+
const osArch = os.arch() === "x64" ? "amd64" : os.arch() === "arm64" ? "arm64" : "other";
|
|
640
|
+
const resultTelemetry = (() => {
|
|
641
|
+
try {
|
|
642
|
+
return (dependencies.resultTelemetryFactory || createResultTelemetry)({
|
|
643
|
+
directory: telemetryDirectory,
|
|
644
|
+
packageVersion,
|
|
645
|
+
agentType: "unknown",
|
|
646
|
+
disabled: environment.RAINSKILLS_TELEMETRY_DISABLED === "1",
|
|
647
|
+
installAttemptId,
|
|
648
|
+
osArch,
|
|
649
|
+
});
|
|
650
|
+
} catch {
|
|
651
|
+
return {
|
|
652
|
+
record: () => ({ recorded: false, delivery: Promise.resolve(false) }),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
})();
|
|
656
|
+
const targets = options.customDest
|
|
657
|
+
? ["unknown"]
|
|
658
|
+
: (target === "all" ? ["claude", "codex", "pi", "dsh", "workbuddy"] : [target]);
|
|
659
|
+
const deliveries = [];
|
|
660
|
+
try {
|
|
661
|
+
const counts = copySkills({
|
|
662
|
+
skills,
|
|
663
|
+
destinations,
|
|
664
|
+
force: options.force,
|
|
665
|
+
logger: detailLogger,
|
|
666
|
+
});
|
|
667
|
+
if (!options.customDest) {
|
|
668
|
+
await (dependencies.installLocalCli || installLocalCli)({ packageRoot, home });
|
|
669
|
+
}
|
|
670
|
+
for (const configuredTarget of targets) {
|
|
671
|
+
const agent = normalizeAgent(configuredTarget);
|
|
672
|
+
const result = resultTelemetry.record({
|
|
673
|
+
event_type: "agent_config_result",
|
|
674
|
+
install_attempt_id: installAttemptId,
|
|
675
|
+
action: "install",
|
|
676
|
+
agent_type: agent,
|
|
677
|
+
status: "success",
|
|
678
|
+
});
|
|
679
|
+
deliveries.push(result.delivery);
|
|
680
|
+
}
|
|
681
|
+
const installResult = resultTelemetry.record({
|
|
682
|
+
event_type: "install_result",
|
|
683
|
+
install_attempt_id: installAttemptId,
|
|
684
|
+
action: "install",
|
|
685
|
+
os_type: "windows",
|
|
686
|
+
os_arch: osArch,
|
|
687
|
+
execution_environment: "native",
|
|
688
|
+
status: "success",
|
|
689
|
+
});
|
|
690
|
+
deliveries.push(installResult.delivery);
|
|
691
|
+
if (environment.RAINSKILLS_TELEMETRY_DISABLED !== "1") {
|
|
692
|
+
try {
|
|
693
|
+
(dependencies.configuredAgentsWriter || writeConfiguredAgents)(
|
|
694
|
+
telemetryDirectory,
|
|
695
|
+
targets.map(normalizeAgent)
|
|
696
|
+
);
|
|
697
|
+
} catch { /* telemetry state must not block installation */ }
|
|
698
|
+
}
|
|
699
|
+
await Promise.allSettled(deliveries);
|
|
700
|
+
detailLogger("");
|
|
701
|
+
detailLogger(`安装完成。本次:${counts.installed} 项新装 / ${counts.updated} 项已更新 / ${counts.unchanged} 项已是最新 / ${counts.forced} 项强制覆盖`);
|
|
702
|
+
detailLogger("");
|
|
703
|
+
logger(CAPABILITY_SUMMARY);
|
|
704
|
+
logger(AGENT_SUMMARY_REQUIREMENT);
|
|
705
|
+
return { status: "skills-installed", counts };
|
|
706
|
+
} catch (error) {
|
|
707
|
+
const failure = resultTelemetry.record({
|
|
708
|
+
event_type: "install_result",
|
|
709
|
+
install_attempt_id: installAttemptId,
|
|
710
|
+
action: "install",
|
|
711
|
+
os_type: "windows",
|
|
712
|
+
os_arch: osArch,
|
|
713
|
+
execution_environment: "native",
|
|
714
|
+
status: "failed",
|
|
715
|
+
error_stage: "agent_configuration",
|
|
716
|
+
error_code: "agent_config_failed",
|
|
717
|
+
});
|
|
718
|
+
await failure.delivery.catch(() => false);
|
|
719
|
+
throw error;
|
|
630
720
|
}
|
|
631
|
-
detailLogger("");
|
|
632
|
-
detailLogger(`安装完成。本次:${counts.installed} 项新装 / ${counts.updated} 项已更新 / ${counts.unchanged} 项已是最新 / ${counts.forced} 项强制覆盖`);
|
|
633
|
-
detailLogger("");
|
|
634
|
-
logger(CAPABILITY_SUMMARY);
|
|
635
|
-
logger(AGENT_SUMMARY_REQUIREMENT);
|
|
636
|
-
return { status: "skills-installed", counts };
|
|
637
721
|
}
|
|
638
722
|
|
|
639
723
|
module.exports = {
|
|
@@ -25,7 +25,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
28
|
-
"package_version": "rainskills@0.1.
|
|
28
|
+
"package_version": "rainskills@0.1.30",
|
|
29
29
|
"runtime_status": [
|
|
30
30
|
"node",
|
|
31
31
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -34,7 +34,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
36
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
37
|
-
"package_version": "rainskills@0.1.
|
|
37
|
+
"package_version": "rainskills@0.1.30",
|
|
38
38
|
"runtime_status": [
|
|
39
39
|
"node",
|
|
40
40
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|
|
@@ -34,7 +34,7 @@ Codex 中命令工具一旦返回 `session_id`,必须立即对该 `session_id`
|
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
36
|
"schema": "rainskills.single-runtime-contract.v1",
|
|
37
|
-
"package_version": "rainskills@0.1.
|
|
37
|
+
"package_version": "rainskills@0.1.30",
|
|
38
38
|
"runtime_status": [
|
|
39
39
|
"node",
|
|
40
40
|
"<home>/.rainbond/lib/rainskills/bin/rainskills.js",
|