rainskills 0.1.19 → 0.1.21

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.
Files changed (40) hide show
  1. package/README.md +34 -39
  2. package/SKILL.md +12 -10
  3. package/bin/rainskills-tools.js +114 -240
  4. package/bin/rainskills.js +134 -591
  5. package/install.sh +68 -94
  6. package/marketplace/rainskills/.claude-plugin/plugin.json +1 -1
  7. package/marketplace/rainskills/.codex-plugin/plugin.json +1 -1
  8. package/marketplace/rainskills/skills/rainskills/SKILL.md +13 -11
  9. package/package.json +2 -2
  10. package/rainbond-app-assistant/SKILL.md +116 -151
  11. package/rainbond-app-version-assistant/SKILL.md +98 -98
  12. package/rainbond-delivery-verifier/SKILL.md +98 -68
  13. package/rainbond-env-sync/SKILL.md +98 -69
  14. package/rainbond-fullstack-bootstrap/SKILL.md +101 -89
  15. package/rainbond-fullstack-bootstrap/modules/10-context-loading.md +1 -1
  16. package/rainbond-fullstack-troubleshooter/SKILL.md +98 -68
  17. package/rainbond-opensource-app-deploy/SKILL.md +102 -81
  18. package/rainbond-platform-installer/SKILL.md +4 -4
  19. package/rainbond-platform-installer/scripts/auto-update.js +0 -9
  20. package/rainbond-platform-installer/scripts/installed-version.js +1 -1
  21. package/rainbond-platform-installer/scripts/platform-installer.js +3 -89
  22. package/rainbond-platform-installer/scripts/runtime-state.js +14 -251
  23. package/rainbond-platform-installer/scripts/single-runtime.js +114 -0
  24. package/rainbond-platform-installer/scripts/user-message.js +3 -49
  25. package/rainbond-platform-installer/scripts/windows-client-config.js +0 -51
  26. package/rainbond-platform-installer/scripts/windows-onboarding.js +0 -3
  27. package/rainbond-platform-installer/scripts/windows-platform.ps1 +1 -1
  28. package/rainbond-platform-query/SKILL.md +103 -61
  29. package/rainbond-project-init/SKILL.md +102 -79
  30. package/rainbond-template-installer/SKILL.md +102 -80
  31. package/rainbond-platform-installer/scripts/environment-credentials.js +0 -225
  32. package/rainbond-platform-installer/scripts/environment-registry.js +0 -349
  33. package/rainbond-platform-installer/scripts/local-runtime-commands.js +0 -180
  34. package/rainbond-platform-installer/scripts/local-runtime.js +0 -58
  35. package/rainbond-platform-installer/scripts/runtime-context-resolver.js +0 -189
  36. package/rainbond-platform-installer/scripts/runtime-credentials.js +0 -163
  37. package/rainbond-platform-installer/scripts/runtime-intents.js +0 -386
  38. package/rainbond-platform-installer/scripts/runtime-operations.js +0 -597
  39. package/rainbond-platform-installer/scripts/windows-client-config.ps1 +0 -9
  40. package/rainbond-platform-installer/scripts/windows-read-user-environment.ps1 +0 -16
package/bin/rainskills.js CHANGED
@@ -4,7 +4,6 @@ const path = require("node:path");
4
4
  const crypto = require("node:crypto");
5
5
  const os = require("node:os");
6
6
  const { spawn } = require("node:child_process");
7
- const { isDeepStrictEqual } = require("node:util");
8
7
  const {
9
8
  detectControlEnvironment,
10
9
  } = require("../rainbond-platform-installer/scripts/control-environment.js");
@@ -30,32 +29,17 @@ const RUNTIME_CHILD_ENVIRONMENT_KEYS = Object.freeze([
30
29
  "RAINSKILLS_INSTALL_REPORT_URL", "RAINSKILLS_LIFECYCLE_REPORT_URL",
31
30
  ]);
32
31
 
33
- function runtimeChildEnvironment(source = process.env, extra = {}, expectedOrigin = "") {
32
+ function runtimeChildEnvironment(source = process.env, extra = {}) {
34
33
  const environment = {};
35
34
  for (const key of RUNTIME_CHILD_ENVIRONMENT_KEYS) {
36
35
  if (typeof source[key] === "string") environment[key] = source[key];
37
36
  }
38
37
  for (const key of Object.keys(extra)) {
39
- if (!["RAINSKILLS_RUNTIME_CONNECT_COMPLETION", "RAINSKILLS_RUNTIME_OPERATION_ID", "RAINSKILLS_RUNTIME_STATE_SCOPE"].includes(key)) {
38
+ if (!["RAINSKILLS_RUNTIME_CONNECT_COMPLETION", "RAINSKILLS_RUNTIME_OPERATION_ID"].includes(key)) {
40
39
  throw new Error("runtime child environment 包含未知字段");
41
40
  }
42
41
  environment[key] = extra[key];
43
42
  }
44
- if (
45
- expectedOrigin
46
- && typeof source.RAINBOND_JWT === "string"
47
- && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(source.RAINBOND_JWT)
48
- && typeof source.RAINBOND_URL === "string"
49
- ) {
50
- try {
51
- if (normalizeConsoleOrigin(source.RAINBOND_URL) === normalizeConsoleOrigin(expectedOrigin)) {
52
- environment.RAINBOND_JWT = source.RAINBOND_JWT;
53
- environment.RAINBOND_URL = normalizeConsoleOrigin(expectedOrigin);
54
- }
55
- } catch {
56
- // An inherited credential is optional. Invalid or origin-drifted pairs are discarded.
57
- }
58
- }
59
43
  return environment;
60
44
  }
61
45
 
@@ -103,20 +87,6 @@ function assertConnectingState(current, expected) {
103
87
  }
104
88
  }
105
89
 
106
- function assertExactConnectedState(current, expected) {
107
- if (
108
- !current
109
- || current.state !== "connected"
110
- || current.operation_id !== expected.operation_id
111
- || current.target_client !== expected.target_client
112
- || current.environment_kind !== expected.environment_kind
113
- || current.console_origin !== expected.console_origin
114
- || !isDeepStrictEqual(current.intent, expected.intent)
115
- ) {
116
- throw new Error("runtime reconnect 完成状态与 protected connection 不匹配");
117
- }
118
- }
119
-
120
90
  function parseRuntimeConnectArgs(args) {
121
91
  if (args[0] !== "runtime" || args[1] !== "connect") {
122
92
  throw new Error("不是 runtime connect 命令");
@@ -128,8 +98,6 @@ function parseRuntimeConnectArgs(args) {
128
98
  let environmentChoice = "";
129
99
  let rainbondUrl = "";
130
100
  let allowInsecureHttp = false;
131
- let onboardingId = "";
132
- let intentInput = "";
133
101
  let privateLocation = "";
134
102
  for (let index = 3; index < args.length; index += 1) {
135
103
  const argument = args[index];
@@ -149,31 +117,13 @@ function parseRuntimeConnectArgs(args) {
149
117
  throw new Error("runtime connect 私有部署位置只支持 local 或 server");
150
118
  }
151
119
  index += 1;
152
- } else if (argument === "--onboarding-id") {
153
- onboardingId = requireFixedValue(args, index, argument);
154
- index += 1;
155
- } else if (argument === "--intent-json") {
156
- intentInput = requireFixedValue(args, index, argument);
157
- index += 1;
120
+ } else if (["--intent-json", "--onboarding-id"].includes(argument)) {
121
+ throw new Error(`runtime connect 不再支持 ${argument}`);
158
122
  } else {
159
123
  throw new Error("runtime connect 包含未知参数");
160
124
  }
161
125
  }
162
126
  if (!environmentChoice) throw new Error("runtime connect 必须选择一个应用运行环境");
163
- if (!intentInput || intentInput.length > 16384) throw new Error("runtime connect 需要受限的 intent JSON");
164
- let rawIntent;
165
- try {
166
- rawIntent = JSON.parse(intentInput);
167
- } catch {
168
- throw new Error("intent JSON 无效");
169
- }
170
- const { assertIntentCanInstallNewPlatform, validateIntent } = require(
171
- "../rainbond-platform-installer/scripts/runtime-intents.js"
172
- );
173
- const intent = environmentChoice === "install-private"
174
- ? assertIntentCanInstallNewPlatform(rawIntent)
175
- : validateIntent(rawIntent);
176
- if (onboardingId && !UUID_PATTERN.test(onboardingId)) throw new Error("onboarding id 无效");
177
127
  if (allowInsecureHttp && environmentChoice !== "private-existing") {
178
128
  throw new Error("--allow-insecure-http 只适用于明确的私有 Console 地址");
179
129
  }
@@ -185,9 +135,7 @@ function parseRuntimeConnectArgs(args) {
185
135
  environmentChoice,
186
136
  rainbondUrl,
187
137
  allowInsecureHttp,
188
- onboardingId,
189
- intent,
190
- ...(privateLocation ? { privateLocation } : {}),
138
+ privateLocation: privateLocation || undefined,
191
139
  };
192
140
  }
193
141
 
@@ -197,141 +145,25 @@ function runtimeConnectionInvocation(options, origin) {
197
145
  if (options.environmentChoice === "saas") args.push("--saas");
198
146
  else args.push("--self-hosted", "--rainbond-url", origin);
199
147
  if (options.allowInsecureHttp) args.push("--allow-insecure-http");
148
+ args.push("--no-cached-token");
200
149
  return {
201
150
  executable: "bash",
202
151
  args,
203
152
  };
204
153
  }
205
154
 
206
- function runtimeConnectRetryAction(options, origin, operationId) {
155
+ function runtimeConnectRetryAction(options, origin) {
207
156
  const argv = ["runtime", "connect", options.targetClient];
208
157
  if (options.environmentChoice === "saas") argv.push("--saas");
209
158
  else argv.push("--rainbond-url", origin);
210
159
  if (options.allowInsecureHttp) argv.push("--allow-insecure-http");
211
- argv.push("--onboarding-id", operationId, "--intent-json", JSON.stringify(options.intent));
212
160
  return {
213
161
  schema: "rainskills.next-action.v1",
214
162
  action: "retry-runtime-connect",
215
- onboarding_id: operationId,
216
163
  argv,
217
164
  };
218
165
  }
219
166
 
220
- function runtimeReconnectRetryAction(operationId) {
221
- return {
222
- schema: "rainskills.next-action.v1",
223
- action: "retry-runtime-reconnect",
224
- onboarding_id: operationId,
225
- argv: ["runtime", "reconnect", "--onboarding-id", operationId],
226
- };
227
- }
228
-
229
- function parseRuntimeFailureArgs(args) {
230
- if (
231
- args.length !== 8
232
- || args[0] !== "runtime"
233
- || args[1] !== "record-failure"
234
- || args[2] !== "--onboarding-id"
235
- || args[4] !== "--step"
236
- || args[6] !== "--reason"
237
- ) {
238
- throw new Error("runtime record-failure 参数无效");
239
- }
240
- if (!UUID_PATTERN.test(args[3] || "")) throw new Error("runtime failure onboarding operation 无效");
241
- if (!args[5] || args[5].startsWith("--")) throw new Error("runtime failure step 无效");
242
- if (!["credential-expired", "permission-denied"].includes(args[7])) {
243
- throw new Error("runtime failure reason 无效");
244
- }
245
- return { operationId: args[3], step: args[5], reason: args[7] };
246
- }
247
-
248
- function parseRuntimeReconnectArgs(args) {
249
- if (
250
- args.length !== 4
251
- || args[0] !== "runtime"
252
- || args[1] !== "reconnect"
253
- || args[2] !== "--onboarding-id"
254
- || !UUID_PATTERN.test(args[3] || "")
255
- ) {
256
- throw new Error("runtime reconnect 参数无效");
257
- }
258
- return { operationId: args[3] };
259
- }
260
-
261
- function createEnvironmentRuntimeServices() {
262
- const { createEnvironmentRegistry } = require(
263
- "../rainbond-platform-installer/scripts/environment-registry.js"
264
- );
265
- const { createRuntimeOperationStore } = require(
266
- "../rainbond-platform-installer/scripts/runtime-operations.js"
267
- );
268
- const { createEnvironmentCredentialStore } = require(
269
- "../rainbond-platform-installer/scripts/environment-credentials.js"
270
- );
271
- let operations;
272
- const registry = createEnvironmentRegistry({
273
- activeOperationIds: (environmentId) => operations?.activeOperationIds(environmentId) || [],
274
- });
275
- operations = createRuntimeOperationStore({ registry });
276
- return {
277
- environmentCredentialStore: createEnvironmentCredentialStore(),
278
- environmentRegistry: registry,
279
- operationStore: operations,
280
- };
281
- }
282
-
283
- function parseEnvironmentMutationArgs(args) {
284
- const action = args[1];
285
- if (!new Set(["rename", "set-default", "remove"]).has(action)) {
286
- throw new Error("environment action 无效");
287
- }
288
- if (args[2] !== "--environment-id" || !UUID_PATTERN.test(args[3] || "")) {
289
- throw new Error("环境 ID 无效");
290
- }
291
- if (action === "rename") {
292
- if (args.length !== 6 || args[4] !== "--name" || !args[5] || args[5].startsWith("--")) {
293
- throw new Error("environment rename 参数无效");
294
- }
295
- return { action, environmentId: args[3], name: args[5] };
296
- }
297
- if (args.length !== 4) throw new Error(`environment ${action} 参数无效`);
298
- return { action, environmentId: args[3] };
299
- }
300
-
301
- function parseOperationBeginArgs(args) {
302
- if (args[0] !== "operation" || args[1] !== "begin") {
303
- throw new Error("operation begin 参数无效");
304
- }
305
- let operationId = "";
306
- let environmentId = "";
307
- let intentInput = "";
308
- for (let index = 2; index < args.length; index += 1) {
309
- const argument = args[index];
310
- if (argument === "--operation-id") {
311
- operationId = requireFixedValue(args, index, argument);
312
- index += 1;
313
- } else if (argument === "--environment-id") {
314
- environmentId = requireFixedValue(args, index, argument);
315
- index += 1;
316
- } else if (argument === "--intent-json") {
317
- intentInput = requireFixedValue(args, index, argument);
318
- index += 1;
319
- } else {
320
- throw new Error("operation begin 包含未知参数");
321
- }
322
- }
323
- if (!UUID_PATTERN.test(operationId)) throw new Error("operation ID 无效");
324
- if (environmentId && !UUID_PATTERN.test(environmentId)) throw new Error("环境 ID 无效");
325
- if (!intentInput || intentInput.length > 16384) throw new Error("operation begin 缺少 intent JSON");
326
- let intent;
327
- try {
328
- intent = JSON.parse(intentInput);
329
- } catch {
330
- throw new Error("operation begin intent JSON 无效");
331
- }
332
- return { operationId, environmentId: environmentId || undefined, intent };
333
- }
334
-
335
167
  function runAttached(executable, args, { env = process.env } = {}) {
336
168
  return new Promise((resolve, reject) => {
337
169
  const child = spawn(executable, args, { env, stdio: "inherit" });
@@ -362,16 +194,13 @@ async function defaultConnectionRunner(invocation, {
362
194
  RAINSKILLS_RUNTIME_CONNECT_COMPLETION: "1",
363
195
  RAINSKILLS_RUNTIME_OPERATION_ID: operationId,
364
196
  };
365
- if (options.intent?.type === "environment-add") {
366
- childEnvironment.RAINSKILLS_RUNTIME_STATE_SCOPE = "operation";
367
- }
368
197
  const result = await runAttached(invocation.executable, invocation.args, {
369
198
  env: runtimeChildEnvironment(process.env, childEnvironment, origin),
370
199
  });
371
200
  return { ...result, completesRuntimeState: true };
372
201
  }
373
202
 
374
- function defaultPrivateInstallerScheduler({ control, intent, operationId, target, privateLocation }) {
203
+ function defaultPrivateInstallerScheduler({ control, operationId, target, privateLocation }) {
375
204
  const {
376
205
  createNextAction,
377
206
  createOnboardingCheckpoint,
@@ -383,16 +212,13 @@ function defaultPrivateInstallerScheduler({ control, intent, operationId, target
383
212
  packageVersion,
384
213
  control,
385
214
  operationId,
386
- intent,
387
215
  });
388
216
  return createNextAction(operationId, privateLocation);
389
217
  }
390
218
 
391
219
  async function runBuiltin(args, {
392
220
  runtimeStateManager,
393
- environmentCredentialStore,
394
- environmentRegistry,
395
- operationStore,
221
+ singleRuntimeStore,
396
222
  write = (value) => process.stdout.write(value),
397
223
  control = detectControlEnvironment(),
398
224
  originInspector,
@@ -402,120 +228,16 @@ async function runBuiltin(args, {
402
228
  credentialPersister,
403
229
  connectedCredentialReader,
404
230
  } = {}) {
405
- let environmentServices;
406
- const getEnvironmentServices = () => {
407
- environmentServices ||= createEnvironmentRuntimeServices();
408
- return environmentServices;
409
- };
410
- const getEnvironmentRegistry = () => environmentRegistry
411
- || getEnvironmentServices().environmentRegistry;
412
- const getOperationStore = () => operationStore || getEnvironmentServices().operationStore;
413
- const getEnvironmentCredentialStore = () => environmentCredentialStore
414
- || getEnvironmentServices().environmentCredentialStore;
415
- const getRuntimeStateManager = (operationId, {
416
- scoped = false,
417
- protectedOperation = null,
418
- } = {}) => {
231
+ const getSingleRuntimeStore = () => singleRuntimeStore || require(
232
+ "../rainbond-platform-installer/scripts/single-runtime.js"
233
+ ).createSingleRuntimeStore();
234
+ const getRuntimeStateManager = () => {
419
235
  if (runtimeStateManager) return runtimeStateManager;
420
- const operationScoped = scoped || (
421
- credentialEnvironment.RAINSKILLS_RUNTIME_STATE_SCOPE === "operation"
422
- && credentialEnvironment.RAINSKILLS_RUNTIME_OPERATION_ID === operationId
423
- );
424
- const managerOptions = operationScoped ? { operationId } : {};
425
- if (protectedOperation?.environment_id) {
426
- const environment = getEnvironmentRegistry().get(protectedOperation.environment_id);
427
- if (!environment || environment.connection_state !== "connected") {
428
- throw new Error("runtime operation 绑定的运行环境当前不可用");
429
- }
430
- const credentialStore = getEnvironmentCredentialStore();
431
- const credential = credentialStore.read({
432
- environmentId: environment.id,
433
- expectedOrigin: environment.console_origin,
434
- });
435
- managerOptions.env = {
436
- RAINBOND_JWT: credential.token,
437
- RAINBOND_URL: credential.origin,
438
- };
439
- managerOptions.credentialWriter = ({ token, baseUrl }) => {
440
- if (baseUrl !== environment.console_origin) {
441
- throw new Error("runtime renewed credential origin 不匹配");
442
- }
443
- credentialStore.write({
444
- environmentId: environment.id,
445
- origin: baseUrl,
446
- token,
447
- });
448
- };
449
- }
450
236
  return require(
451
237
  "../rainbond-platform-installer/scripts/runtime-state.js"
452
- ).createRuntimeStateManager(managerOptions);
238
+ ).createRuntimeStateManager();
453
239
  };
454
240
 
455
- if (args[0] === "environment" && args[1] === "list") {
456
- if (args.length !== 3 || args[2] !== "--json") {
457
- throw new Error("environment list 只支持固定参数 --json");
458
- }
459
- const current = getEnvironmentRegistry().read();
460
- write(`${JSON.stringify({
461
- schema: "rainskills.environment-list.v1",
462
- default_environment_id: current.default_environment_id,
463
- environments: current.environments,
464
- })}\n`);
465
- return true;
466
- }
467
- if (args[0] === "environment" && ["rename", "set-default", "remove"].includes(args[1])) {
468
- const input = parseEnvironmentMutationArgs(args);
469
- const registry = getEnvironmentRegistry();
470
- let environment;
471
- let action;
472
- if (input.action === "rename") {
473
- environment = registry.rename(input.environmentId, input.name);
474
- action = "renamed";
475
- } else if (input.action === "set-default") {
476
- environment = registry.setDefault(input.environmentId);
477
- action = "default-changed";
478
- } else {
479
- environment = registry.remove(input.environmentId);
480
- getEnvironmentCredentialStore().remove(input.environmentId);
481
- action = "removed";
482
- }
483
- write(`${JSON.stringify({
484
- schema: "rainskills.environment-result.v1",
485
- action,
486
- environment,
487
- })}\n`);
488
- return true;
489
- }
490
- if (args[0] === "operation" && args[1] === "begin") {
491
- const input = parseOperationBeginArgs(args);
492
- const operation = getOperationStore().begin(input);
493
- write(`${JSON.stringify({
494
- schema: "rainskills.operation-begin-result.v1",
495
- operation_id: operation.operation_id,
496
- environment_id: operation.environment_id,
497
- intent: operation.intent,
498
- stage: operation.stage,
499
- })}\n`);
500
- return true;
501
- }
502
- if (args[0] === "operation" && args[1] === "complete") {
503
- if (
504
- args.length !== 4
505
- || args[2] !== "--operation-id"
506
- || !UUID_PATTERN.test(args[3] || "")
507
- ) {
508
- throw new Error("operation complete 参数无效");
509
- }
510
- const operation = getOperationStore().complete(args[3]);
511
- write(`${JSON.stringify({
512
- schema: "rainskills.operation-complete-result.v1",
513
- operation_id: operation.operation_id,
514
- environment_id: operation.environment_id,
515
- stage: operation.stage,
516
- })}\n`);
517
- return true;
518
- }
519
241
  if (args[0] === "runtime" && args[1] === "message") {
520
242
  if (args.length !== 4 || args[2] !== "--id") {
521
243
  throw new Error("runtime message 只支持固定参数 --id");
@@ -529,165 +251,104 @@ async function runBuiltin(args, {
529
251
  if (args.length !== 3 || args[2] !== "--json") {
530
252
  throw new Error("runtime status 只支持固定参数 --json");
531
253
  }
532
- const manager = runtimeStateManager || require(
533
- "../rainbond-platform-installer/scripts/runtime-state.js"
534
- ).createRuntimeStateManager();
535
- write(`${JSON.stringify(await manager.status())}\n`);
536
- return true;
537
- }
538
- if (args[0] === "runtime" && args[1] === "record-failure") {
539
- const failure = parseRuntimeFailureArgs(args);
540
- const manager = runtimeStateManager || require(
541
- "../rainbond-platform-installer/scripts/runtime-state.js"
542
- ).createRuntimeStateManager();
543
- const state = manager.recordFailure(failure);
544
- write(`${JSON.stringify({
545
- schema: "rainskills.runtime-failure-record.v1",
546
- onboarding_id: failure.operationId,
547
- failure_category: state.last_failure_category,
548
- retry_available: state.retry_budget === 1,
549
- })}\n`);
550
- return true;
551
- }
552
- if (args[0] === "runtime" && args[1] === "reconnect") {
553
- const { operationId } = parseRuntimeReconnectArgs(args);
554
- const multiEnvironmentEnabled = Boolean(
555
- environmentRegistry
556
- || operationStore
557
- || environmentCredentialStore
558
- || !runtimeStateManager
559
- );
560
- let reconnectedEnvironment = null;
561
- const manager = runtimeStateManager || require(
562
- "../rainbond-platform-installer/scripts/runtime-state.js"
563
- ).createRuntimeStateManager();
564
- const reconnectResult = await manager.withReconnectLease(operationId, async (connection) => {
565
- try {
566
- const inspect = originInspector || require(
567
- "../rainbond-platform-installer/scripts/console-origin.js"
568
- ).inspectConsoleOrigin;
569
- const inspection = await inspect(connection.console_origin);
570
- if (inspection.pendingRedirectOrigin || inspection.origin !== connection.console_origin) {
571
- throw new Error("protected Console origin 在重连检查中发生变化");
572
- }
573
- const options = {
574
- targetClient: connection.target_client,
575
- environmentChoice: connection.environment_kind === "saas" ? "saas" : "private-existing",
576
- rainbondUrl: connection.console_origin,
577
- allowInsecureHttp: connection.console_origin.startsWith("http://"),
578
- onboardingId: operationId,
579
- intent: connection.intent,
580
- };
581
- const invocation = runtimeConnectionInvocation(options, connection.console_origin);
582
- let completedWithCredential = false;
583
- const completeWithCredential = async (credential) => {
584
- const priorToken = process.env.RAINBOND_JWT;
585
- try {
586
- process.env.RAINBOND_JWT = credential;
587
- await manager.markConnected(connection);
588
- if (multiEnvironmentEnabled) {
589
- const protectedOperation = getOperationStore().read(operationId);
590
- if (!protectedOperation?.environment_id) {
591
- throw new Error("runtime reconnect 缺少已锁定的运行环境");
592
- }
593
- const registration = require(
594
- "../rainbond-platform-installer/scripts/environment-credentials.js"
595
- ).registerConnectedEnvironment({
596
- registry: getEnvironmentRegistry(),
597
- credentialStore: getEnvironmentCredentialStore(),
598
- origin: connection.console_origin,
599
- token: credential,
600
- kind: connection.environment_kind,
601
- });
602
- if (registration.environment.id !== protectedOperation.environment_id) {
603
- throw new Error("runtime reconnect 不能切换已锁定的运行环境");
604
- }
605
- reconnectedEnvironment = registration.environment;
606
- }
607
- completedWithCredential = true;
608
- } finally {
609
- if (priorToken === undefined) delete process.env.RAINBOND_JWT;
610
- else process.env.RAINBOND_JWT = priorToken;
611
- }
612
- };
613
- const result = await connectionRunner(invocation, {
614
- completeWithCredential,
615
- control,
616
- options,
617
- origin: connection.console_origin,
618
- operationId,
254
+ if (runtimeStateManager) {
255
+ write(`${JSON.stringify(await runtimeStateManager.status())}\n`);
256
+ return true;
257
+ }
258
+ const runtime = getSingleRuntimeStore().read();
259
+ if (!runtime) {
260
+ write(`${JSON.stringify({
261
+ schema: "rainskills.runtime-status.v1",
262
+ state: "not_started",
263
+ usable: false,
264
+ })}\n`);
265
+ return true;
266
+ }
267
+ try {
268
+ const validation = await require(
269
+ "../rainbond-platform-installer/scripts/windows-client-config.js"
270
+ ).validateMcp({
271
+ url: `${runtime.console_origin}/console/mcp/rainskills/api/query`,
272
+ token: runtime.token,
273
+ });
274
+ if (validation.token !== runtime.token) {
275
+ getSingleRuntimeStore().write({
276
+ consoleOrigin: runtime.console_origin,
277
+ kind: runtime.kind,
278
+ token: validation.token,
279
+ allowInsecureHttp: runtime.allow_insecure_http,
619
280
  });
620
- if (result.signal || result.code !== 0) {
621
- throw new Error("RainSkills 运行环境重新授权未完成");
622
- }
623
- if (result.completesRuntimeState) {
624
- assertExactConnectedState(manager.read(), connection);
625
- } else {
626
- if (completedWithCredential) throw new Error("运行环境重连器返回了矛盾状态");
627
- await manager.markConnected(connection);
628
- assertExactConnectedState(manager.read(), connection);
629
- }
630
- return {
631
- environmentKind: connection.environment_kind,
632
- consoleOrigin: connection.console_origin,
633
- };
634
- } catch {
635
- write(`${JSON.stringify(runtimeReconnectRetryAction(operationId))}\n`);
636
- throw new Error("RainSkills 运行环境重新授权失败");
637
- }
638
- });
639
- if (multiEnvironmentEnabled) {
640
- const operations = getOperationStore();
641
- const protectedOperation = operations.read(operationId);
642
- if (!protectedOperation || !protectedOperation.environment_id) {
643
- throw new Error("runtime reconnect 缺少已锁定的运行环境");
644
- }
645
- if (!reconnectedEnvironment) {
646
- if (connectedCredentialReader) {
647
- const credential = await connectedCredentialReader(reconnectResult.consoleOrigin);
648
- reconnectedEnvironment = require(
649
- "../rainbond-platform-installer/scripts/environment-credentials.js"
650
- ).registerConnectedEnvironment({
651
- registry: getEnvironmentRegistry(),
652
- credentialStore: getEnvironmentCredentialStore(),
653
- origin: reconnectResult.consoleOrigin,
654
- token: credential.token,
655
- kind: reconnectResult.environmentKind,
656
- }).environment;
657
- } else {
658
- const existing = getEnvironmentRegistry().findByOrigin(reconnectResult.consoleOrigin);
659
- if (!existing || !getEnvironmentCredentialStore().has(existing.id)) {
660
- throw new Error("runtime reconnect 未写入目标环境凭据");
661
- }
662
- getEnvironmentCredentialStore().read({
663
- environmentId: existing.id,
664
- expectedOrigin: reconnectResult.consoleOrigin,
665
- });
666
- reconnectedEnvironment = existing;
667
- }
668
- }
669
- if (reconnectedEnvironment.id !== protectedOperation.environment_id) {
670
- throw new Error("runtime reconnect 不能切换已锁定的运行环境");
671
281
  }
282
+ write(`${JSON.stringify({
283
+ schema: "rainskills.runtime-status.v1",
284
+ state: "connected",
285
+ usable: true,
286
+ console_origin: runtime.console_origin,
287
+ environment_kind: runtime.kind,
288
+ })}\n`);
289
+ } catch {
290
+ write(`${JSON.stringify({
291
+ schema: "rainskills.runtime-status.v1",
292
+ state: "needs_reconnect",
293
+ usable: false,
294
+ console_origin: runtime.console_origin,
295
+ environment_kind: runtime.kind,
296
+ })}\n`);
672
297
  }
673
- write(`${JSON.stringify({
674
- schema: "rainskills.runtime-reconnect-result.v1",
675
- state: "connected",
676
- onboarding_id: operationId,
677
- environment_kind: reconnectResult.environmentKind,
678
- ...(reconnectedEnvironment ? {
679
- environment_id: reconnectedEnvironment.id,
680
- environment_name: reconnectedEnvironment.name,
681
- } : {}),
682
- })}\n`);
683
298
  return true;
684
299
  }
300
+ if (args[0] === "runtime" && args[1] === "reconnect") {
301
+ if (args.length !== 3 || !["codex", "claude", "pi", "all"].includes(args[2])) {
302
+ throw new Error("runtime reconnect 参数无效");
303
+ }
304
+ const current = getSingleRuntimeStore().read();
305
+ if (!current) throw new Error("目前还没有已连接的 Rainbond 运行环境");
306
+ const reconnectArgs = ["runtime", "connect", args[2]];
307
+ if (current.kind === "saas") reconnectArgs.push("--saas");
308
+ else reconnectArgs.push("--rainbond-url", current.console_origin);
309
+ if (current.allow_insecure_http) reconnectArgs.push("--allow-insecure-http");
310
+ return runBuiltin(reconnectArgs, {
311
+ runtimeStateManager,
312
+ singleRuntimeStore: getSingleRuntimeStore(),
313
+ write,
314
+ control,
315
+ originInspector,
316
+ connectionRunner,
317
+ privateInstallerScheduler,
318
+ credentialEnvironment,
319
+ credentialPersister,
320
+ connectedCredentialReader,
321
+ });
322
+ }
685
323
  if (args[0] === "runtime" && args[1] === "assert-connect") {
686
324
  const expected = parseRuntimeAssertConnectArgs(args);
687
325
  const manager = getRuntimeStateManager(expected.operationId);
688
326
  assertConnectingState(manager.read(), expected);
689
327
  return true;
690
328
  }
329
+ if (args[0] === "runtime" && args[1] === "store-credential") {
330
+ if (
331
+ args.length < 6
332
+ || args[2] !== "--console-origin"
333
+ || args[4] !== "--kind"
334
+ || !["saas", "private"].includes(args[5])
335
+ || (args.length === 7 && args[6] !== "--allow-insecure-http")
336
+ || args.length > 7
337
+ ) {
338
+ throw new Error("runtime store-credential 参数无效");
339
+ }
340
+ const token = credentialEnvironment.RAINBOND_JWT;
341
+ if (typeof token !== "string" || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
342
+ throw new Error("runtime store-credential 缺少有效凭据");
343
+ }
344
+ getSingleRuntimeStore().write({
345
+ consoleOrigin: args[3],
346
+ kind: args[5],
347
+ token,
348
+ allowInsecureHttp: args[6] === "--allow-insecure-http",
349
+ });
350
+ return true;
351
+ }
691
352
  if (args[0] === "runtime" && args[1] === "persist-connect-credential") {
692
353
  if (
693
354
  args.length !== 4
@@ -707,63 +368,22 @@ async function runBuiltin(args, {
707
368
  }
708
369
  if (credentialPersister) {
709
370
  await credentialPersister({ token, baseUrl: current.console_origin });
710
- } else if (
711
- environmentRegistry
712
- || operationStore
713
- || environmentCredentialStore
714
- || !runtimeStateManager
715
- ) {
716
- const registration = require(
717
- "../rainbond-platform-installer/scripts/environment-credentials.js"
718
- ).registerConnectedEnvironment({
719
- registry: getEnvironmentRegistry(),
720
- credentialStore: getEnvironmentCredentialStore(),
721
- origin: current.console_origin,
722
- token,
371
+ } else {
372
+ getSingleRuntimeStore().write({
373
+ consoleOrigin: current.console_origin,
723
374
  kind: current.environment_kind,
375
+ token,
376
+ allowInsecureHttp: current.console_origin.startsWith("http://"),
724
377
  });
725
- const operations = getOperationStore();
726
- const operation = operations.read(args[3]);
727
- if (!operation) throw new Error("runtime connect operation 不存在");
728
- if (operation.environment_id === null) {
729
- operations.bindEnvironment(args[3], registration.environment.id);
730
- } else if (operation.environment_id !== registration.environment.id) {
731
- throw new Error("runtime connect 不能改变已锁定的运行环境");
732
- }
733
- } else {
734
- if (typeof manager.persistConnectingCredential !== "function") {
735
- throw new Error("runtime connect credential writer 不可用");
736
- }
737
- await manager.persistConnectingCredential({ operationId: args[3], token });
738
378
  }
739
379
  return true;
740
380
  }
741
381
  if (args[0] === "runtime" && args[1] === "connect") {
742
382
  const options = parseRuntimeConnectArgs(args);
743
- const operationId = options.onboardingId || crypto.randomUUID();
744
- const multiEnvironmentEnabled = Boolean(
745
- environmentRegistry
746
- || operationStore
747
- || environmentCredentialStore
748
- || !runtimeStateManager
749
- );
750
- let operations;
751
- if (multiEnvironmentEnabled) {
752
- operations = getOperationStore();
753
- const existingOperation = operations.read(operationId);
754
- if (!existingOperation) {
755
- operations.createPending({ operationId, intent: options.intent });
756
- } else if (
757
- existingOperation.stage !== "awaiting-environment"
758
- || !isDeepStrictEqual(existingOperation.intent, options.intent)
759
- ) {
760
- throw new Error("runtime connect operation 与受保护的原始 intent 不匹配");
761
- }
762
- }
383
+ const operationId = crypto.randomUUID();
763
384
  if (options.environmentChoice === "install-private") {
764
385
  const nextAction = privateInstallerScheduler({
765
386
  control,
766
- intent: options.intent,
767
387
  operationId,
768
388
  target: options.targetClient,
769
389
  privateLocation: options.privateLocation,
@@ -785,17 +405,14 @@ async function runBuiltin(args, {
785
405
  if (inspection.httpConfirmationRequired && !options.allowInsecureHttp) {
786
406
  throw new Error("明文 HTTP 需要单独显式确认;确认可信内网后使用 --allow-insecure-http");
787
407
  }
788
- const manager = getRuntimeStateManager(operationId, {
789
- scoped: options.intent.type === "environment-add",
790
- });
408
+ const manager = getRuntimeStateManager(operationId);
791
409
  const connection = {
792
410
  target_client: options.targetClient,
793
411
  environment_kind: options.environmentChoice === "saas" ? "saas" : "private",
794
412
  console_origin: inspection.origin,
795
- intent: options.intent,
413
+ intent: null,
796
414
  operation_id: operationId,
797
415
  };
798
- let registeredEnvironment = null;
799
416
  manager.startConnecting(connection);
800
417
  try {
801
418
  const invocation = runtimeConnectionInvocation(options, inspection.origin);
@@ -805,23 +422,12 @@ async function runBuiltin(args, {
805
422
  try {
806
423
  process.env.RAINBOND_JWT = credential;
807
424
  await manager.markConnected(connection);
808
- if (multiEnvironmentEnabled) {
809
- registeredEnvironment = require(
810
- "../rainbond-platform-installer/scripts/environment-credentials.js"
811
- ).registerConnectedEnvironment({
812
- registry: getEnvironmentRegistry(),
813
- credentialStore: getEnvironmentCredentialStore(),
814
- origin: inspection.origin,
815
- token: credential,
816
- kind: connection.environment_kind,
817
- }).environment;
818
- const pending = operations.read(operationId);
819
- if (pending.environment_id === null) {
820
- operations.bindEnvironment(operationId, registeredEnvironment.id);
821
- } else if (pending.environment_id !== registeredEnvironment.id) {
822
- throw new Error("runtime connect 不能改变已锁定的运行环境");
823
- }
824
- }
425
+ getSingleRuntimeStore().write({
426
+ consoleOrigin: inspection.origin,
427
+ kind: connection.environment_kind,
428
+ token: credential,
429
+ allowInsecureHttp: inspection.origin.startsWith("http://"),
430
+ });
825
431
  completedWithCredential = true;
826
432
  } finally {
827
433
  if (priorToken === undefined) delete process.env.RAINBOND_JWT;
@@ -848,67 +454,34 @@ async function runBuiltin(args, {
848
454
  await manager.markConnected(connection);
849
455
  }
850
456
  } catch (error) {
851
- write(`${JSON.stringify(runtimeConnectRetryAction(options, inspection.origin, operationId))}\n`);
457
+ write(`${JSON.stringify(runtimeConnectRetryAction(options, inspection.origin))}\n`);
852
458
  throw error;
853
459
  }
854
- if (multiEnvironmentEnabled) {
855
- if (!registeredEnvironment) {
856
- if (connectedCredentialReader) {
857
- const credential = await connectedCredentialReader(inspection.origin);
858
- if (!credential || credential.origin !== inspection.origin) {
859
- throw new Error("运行环境凭据与已验证 Console origin 不匹配");
860
- }
861
- registeredEnvironment = require(
862
- "../rainbond-platform-installer/scripts/environment-credentials.js"
863
- ).registerConnectedEnvironment({
864
- registry: getEnvironmentRegistry(),
865
- credentialStore: getEnvironmentCredentialStore(),
866
- origin: inspection.origin,
867
- token: credential.token,
868
- kind: connection.environment_kind,
869
- }).environment;
870
- } else {
871
- const existing = getEnvironmentRegistry().findByOrigin(inspection.origin);
872
- if (!existing || !getEnvironmentCredentialStore().has(existing.id)) {
873
- throw new Error("runtime connect 未写入目标环境凭据");
874
- }
875
- getEnvironmentCredentialStore().read({
876
- environmentId: existing.id,
877
- expectedOrigin: inspection.origin,
878
- });
879
- registeredEnvironment = existing;
880
- }
460
+ let storedRuntime = getSingleRuntimeStore().read();
461
+ if (!storedRuntime || storedRuntime.console_origin !== inspection.origin) {
462
+ if (!connectedCredentialReader) {
463
+ throw new Error("runtime connect 未写入唯一运行环境凭据");
881
464
  }
882
- const protectedOperation = operations.read(operationId);
883
- if (protectedOperation.environment_id === null) {
884
- operations.bindEnvironment(operationId, registeredEnvironment.id);
885
- } else if (protectedOperation.environment_id !== registeredEnvironment.id) {
886
- throw new Error("runtime connect 不能改变已锁定的运行环境");
465
+ const credential = await connectedCredentialReader(inspection.origin);
466
+ if (!credential || credential.origin !== inspection.origin) {
467
+ throw new Error("运行环境凭据与已验证 Console origin 不匹配");
887
468
  }
469
+ getSingleRuntimeStore().write({
470
+ consoleOrigin: inspection.origin,
471
+ kind: connection.environment_kind,
472
+ token: credential.token,
473
+ allowInsecureHttp: inspection.origin.startsWith("http://"),
474
+ });
475
+ storedRuntime = getSingleRuntimeStore().read();
476
+ }
477
+ if (!storedRuntime || storedRuntime.console_origin !== inspection.origin) {
478
+ throw new Error("唯一运行环境凭据与已连接 Console origin 不匹配");
888
479
  }
889
- const environmentSnapshot = registeredEnvironment && options.intent.type === "environment-add"
890
- ? getEnvironmentRegistry().read()
891
- : null;
892
480
  write(`${JSON.stringify({
893
481
  schema: "rainskills.runtime-connect-result.v1",
894
482
  state: "connected",
895
- onboarding_id: operationId,
483
+ console_origin: inspection.origin,
896
484
  environment_kind: connection.environment_kind,
897
- ...(registeredEnvironment ? {
898
- environment_id: registeredEnvironment.id,
899
- environment_name: registeredEnvironment.name,
900
- } : {}),
901
- ...(environmentSnapshot ? {
902
- default_environment_id: environmentSnapshot.default_environment_id,
903
- environments: environmentSnapshot.environments,
904
- user_message: require(
905
- "../rainbond-platform-installer/scripts/user-message.js"
906
- ).renderEnvironmentConnectedList({
907
- environments: environmentSnapshot.environments,
908
- defaultEnvironmentId: environmentSnapshot.default_environment_id,
909
- addedEnvironmentId: registeredEnvironment.id,
910
- }),
911
- } : {}),
912
485
  })}\n`);
913
486
  return true;
914
487
  }
@@ -930,33 +503,6 @@ async function runBuiltin(args, {
930
503
  });
931
504
  return true;
932
505
  }
933
- if (args[0] === "intent" && args[1] === "resume") {
934
- if (args.length !== 4 || args[2] !== "--onboarding-id" || !args[3]) {
935
- throw new Error("intent resume 需要固定参数 --onboarding-id <uuid>");
936
- }
937
- const protectedOperation = runtimeStateManager ? null : getOperationStore().read(args[3]);
938
- const manager = getRuntimeStateManager(args[3], {
939
- scoped: protectedOperation?.intent?.type === "environment-add",
940
- protectedOperation,
941
- });
942
- const eligible = manager.assertContinuationEligible(args[3]);
943
- if (protectedOperation?.environment_id) {
944
- const environment = getEnvironmentRegistry().get(protectedOperation.environment_id);
945
- if (!environment || eligible.console_origin !== environment.console_origin) {
946
- throw new Error("runtime operation 与已锁定运行环境不匹配");
947
- }
948
- }
949
- const status = await manager.status();
950
- if (status.state !== "connected" || status.usable !== true) {
951
- throw new Error("runtime 尚未 connected 且通过 live probe,不能恢复 intent");
952
- }
953
- const runtime = manager.prepareContinuation(args[3]);
954
- const { createIntentContinuation } = require(
955
- "../rainbond-platform-installer/scripts/runtime-intents.js"
956
- );
957
- write(`${JSON.stringify(createIntentContinuation(runtime.intent, runtime.failed_step || undefined))}\n`);
958
- return true;
959
- }
960
506
  return false;
961
507
  }
962
508
 
@@ -1220,8 +766,6 @@ async function run() {
1220
766
  module.exports = {
1221
767
  AUTO_UPDATE_FALLBACK_EXIT_CODE,
1222
768
  classifyNodeMajor,
1223
- parseRuntimeFailureArgs,
1224
- parseRuntimeReconnectArgs,
1225
769
  parseRuntimeConnectArgs,
1226
770
  resolveInvocation,
1227
771
  runAutoUpdatePhase,
@@ -1229,7 +773,6 @@ module.exports = {
1229
773
  runtimeChildEnvironment,
1230
774
  runtimeConnectRetryAction,
1231
775
  runtimeConnectionInvocation,
1232
- runtimeReconnectRetryAction,
1233
776
  };
1234
777
 
1235
778
  if (require.main === module) {