aws-runtime-bridge 1.1.0 → 1.1.2

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 (113) hide show
  1. package/README.md +31 -9
  2. package/dist/index.js +2 -0
  3. package/dist/routes/instance.d.ts +10 -0
  4. package/dist/routes/instance.d.ts.map +1 -1
  5. package/dist/routes/instance.js +37 -13
  6. package/dist/routes/instance.test.js +38 -1
  7. package/dist/services/auto-register.d.ts +4 -0
  8. package/dist/services/auto-register.d.ts.map +1 -1
  9. package/dist/services/auto-register.js +3 -34
  10. package/dist/services/startup-config-wizard.d.ts +15 -0
  11. package/dist/services/startup-config-wizard.d.ts.map +1 -0
  12. package/dist/services/startup-config-wizard.js +122 -0
  13. package/dist/services/startup-config-wizard.test.d.ts +2 -0
  14. package/dist/services/startup-config-wizard.test.d.ts.map +1 -0
  15. package/dist/services/startup-config-wizard.test.js +103 -0
  16. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +166 -0
  17. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -0
  18. package/package/aws-client-agent-mcp/dist/agent-client.js +602 -0
  19. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -0
  20. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts +2 -0
  21. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts.map +1 -0
  22. package/package/aws-client-agent-mcp/dist/agent-client.test.js +534 -0
  23. package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +1 -0
  24. package/package/aws-client-agent-mcp/dist/config.d.ts +22 -0
  25. package/package/aws-client-agent-mcp/dist/config.d.ts.map +1 -0
  26. package/package/aws-client-agent-mcp/dist/config.js +106 -0
  27. package/package/aws-client-agent-mcp/dist/config.js.map +1 -0
  28. package/package/aws-client-agent-mcp/dist/config.test.d.ts +2 -0
  29. package/package/aws-client-agent-mcp/dist/config.test.d.ts.map +1 -0
  30. package/package/aws-client-agent-mcp/dist/config.test.js +139 -0
  31. package/package/aws-client-agent-mcp/dist/config.test.js.map +1 -0
  32. package/package/aws-client-agent-mcp/dist/constants.d.ts +15 -0
  33. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -0
  34. package/package/aws-client-agent-mcp/dist/constants.js +19 -0
  35. package/package/aws-client-agent-mcp/dist/constants.js.map +1 -0
  36. package/package/aws-client-agent-mcp/dist/http-client.d.ts +28 -0
  37. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -0
  38. package/package/aws-client-agent-mcp/dist/http-client.js +96 -0
  39. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -0
  40. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts +2 -0
  41. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts.map +1 -0
  42. package/package/aws-client-agent-mcp/dist/http-client.test.js +228 -0
  43. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -0
  44. package/package/aws-client-agent-mcp/dist/index.d.ts +14 -0
  45. package/package/aws-client-agent-mcp/dist/index.d.ts.map +1 -0
  46. package/package/aws-client-agent-mcp/dist/index.js +30 -0
  47. package/package/aws-client-agent-mcp/dist/index.js.map +1 -0
  48. package/package/aws-client-agent-mcp/dist/logger.d.ts +7 -0
  49. package/package/aws-client-agent-mcp/dist/logger.d.ts.map +1 -0
  50. package/package/aws-client-agent-mcp/dist/logger.js +19 -0
  51. package/package/aws-client-agent-mcp/dist/logger.js.map +1 -0
  52. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +77 -0
  53. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -0
  54. package/package/aws-client-agent-mcp/dist/mcp-server.js +438 -0
  55. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -0
  56. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts +2 -0
  57. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts.map +1 -0
  58. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +624 -0
  59. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -0
  60. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +78 -0
  61. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -0
  62. package/package/aws-client-agent-mcp/dist/mcp-tools.js +420 -0
  63. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -0
  64. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +61 -0
  65. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -0
  66. package/package/aws-client-agent-mcp/dist/memory-store.js +268 -0
  67. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -0
  68. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts +2 -0
  69. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts.map +1 -0
  70. package/package/aws-client-agent-mcp/dist/memory-store.test.js +164 -0
  71. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -0
  72. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +87 -0
  73. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -0
  74. package/package/aws-client-agent-mcp/dist/message-buffer.js +185 -0
  75. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -0
  76. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts +2 -0
  77. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts.map +1 -0
  78. package/package/aws-client-agent-mcp/dist/message-buffer.test.js +44 -0
  79. package/package/aws-client-agent-mcp/dist/message-buffer.test.js.map +1 -0
  80. package/package/aws-client-agent-mcp/dist/messageContent.d.ts +53 -0
  81. package/package/aws-client-agent-mcp/dist/messageContent.d.ts.map +1 -0
  82. package/package/aws-client-agent-mcp/dist/messageContent.js +125 -0
  83. package/package/aws-client-agent-mcp/dist/messageContent.js.map +1 -0
  84. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts +3 -0
  85. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -0
  86. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +50 -0
  87. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -0
  88. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts +2 -0
  89. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts.map +1 -0
  90. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +90 -0
  91. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -0
  92. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +66 -0
  93. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -0
  94. package/package/aws-client-agent-mcp/dist/status-reporter.js +237 -0
  95. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -0
  96. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts +2 -0
  97. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts.map +1 -0
  98. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +19 -0
  99. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -0
  100. package/package/aws-client-agent-mcp/dist/types.d.ts +309 -0
  101. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -0
  102. package/package/aws-client-agent-mcp/dist/types.js +9 -0
  103. package/package/aws-client-agent-mcp/dist/types.js.map +1 -0
  104. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +94 -0
  105. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -0
  106. package/package/aws-client-agent-mcp/dist/websocket-client.js +316 -0
  107. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -0
  108. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts +2 -0
  109. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts.map +1 -0
  110. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +191 -0
  111. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -0
  112. package/package.json +3 -1
  113. package/scripts/ensure-bin-executable.mjs +31 -0
package/README.md CHANGED
@@ -6,11 +6,13 @@ AgentsWorkStudio 机器实例运行时桥接服务,用于在实例上管理 Ag
6
6
 
7
7
  从 npm 包安装时:
8
8
 
9
- ```bash
10
- npm install -g aws-runtime-bridge
11
- ```
12
-
13
- 从当前仓库安装时:
9
+ ```bash
10
+ npm install -g aws-runtime-bridge
11
+ ```
12
+
13
+ 安装脚本会在 macOS、Linux 与常见 Unix 平台自动为 CLI 入口补齐可执行权限;Windows 平台无需 chmod,会自动跳过该步骤。
14
+
15
+ 从当前仓库安装时:
14
16
 
15
17
  ```bash
16
18
  cd aws-runtime-bridge
@@ -19,10 +21,30 @@ npm run build
19
21
  npm install -g .
20
22
  ```
21
23
 
22
- ## 启动
23
-
24
- ```bash
25
- AWS_RUNTIME_BRIDGE_PORT=18081 \
24
+ ## 启动
25
+
26
+ 首次运行 `awsb` / `aws-bridge` 时,如果不存在 `~/.aws-bridge/config.json`,CLI 会进入交互式配置引导;也可以在提示中选择跳过。跳过时仍会创建配置文件并自动生成随机 `connectionKey`,终端会输出该密钥,请保存后在 server/面板连接此 Bridge 时使用。非交互环境(如 systemd、CI、Docker 后台启动)不会阻塞等待输入,也会自动生成 `connectionKey` 并跳过引导。
27
+
28
+ 引导会生成类似下面的配置:
29
+
30
+ ```json
31
+ {
32
+ "connectionKey": "gkmzjaznX55..",
33
+ "autoRegisterTargets": [
34
+ {
35
+ "serverUrl": "http://127.0.0.1:8080",
36
+ "instanceName": "至强主机",
37
+ "userKey": "aws_live_crh_g0GGrg5IntODg1sFwnas7qf4yGrcwoisDWPBtd8",
38
+ "registerIp": "127.0.0.1"
39
+ }
40
+ ]
41
+ }
42
+ ```
43
+
44
+ 如需在交互式终端中也强制跳过引导,可设置 `AWS_BRIDGE_SKIP_SETUP=true`;此时仍会生成随机 `connectionKey`。
45
+
46
+ ```bash
47
+ AWS_RUNTIME_BRIDGE_PORT=18081 \
26
48
  AWS_RUNTIME_SCHEDULER_BASE_URL=http://your-server-host:7380 \
27
49
  AWS_RUNTIME_CALLBACK_TOKEN=replace-with-a-long-random-runtime-callback-token \
28
50
  AWS_RUNTIME_HOME_DIR=/opt/agentswork/runtime-home \
package/dist/index.js CHANGED
@@ -31,7 +31,9 @@ import { adapterRegistry } from "./adapter/index.js";
31
31
  import { logger } from "./utils/logger.js";
32
32
  import { autoRegister, unregister, bridgeRestartCleanup, } from "./services/auto-register.js";
33
33
  import { ensureAwsClientAgentMcpReleased } from "./services/aws-client-agent-mcp.js";
34
+ import { ensureStartupConfig } from "./services/startup-config-wizard.js";
34
35
  // 验证生产环境配置
36
+ await ensureStartupConfig();
35
37
  validateProductionToken();
36
38
  ensureAwsClientAgentMcpReleased();
37
39
  logRuntimeBindingStartupState();
@@ -1,3 +1,13 @@
1
1
  export declare function setGracefulShutdownFn(fn: (signal: string, preserveSessions: boolean) => Promise<void>): void;
2
2
  export declare const instanceRouter: import("express-serve-static-core").Router;
3
+ export declare function buildConnectionCheckResponse(connectionKeyMd5: string): {
4
+ status: number;
5
+ body: {
6
+ ok: boolean;
7
+ runtimeBridge?: "healthy";
8
+ connectionKeyMatched: boolean;
9
+ connectionKeyRequired: boolean;
10
+ error?: string;
11
+ };
12
+ };
3
13
  //# sourceMappingURL=instance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AA0BA,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AA0BA,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA"}
@@ -34,6 +34,41 @@ function hasMatchingConnectionKeyDigest(connectionKeyMd5) {
34
34
  timingSafeEqual(configuredBuffer, providedBuffer));
35
35
  });
36
36
  }
37
+ export function buildConnectionCheckResponse(connectionKeyMd5) {
38
+ const configuredConnectionKeys = getConfiguredConnectionKeys();
39
+ const connectionKeyRequired = configuredConnectionKeys.length > 0;
40
+ if (!connectionKeyRequired) {
41
+ return {
42
+ status: 200,
43
+ body: {
44
+ ok: true,
45
+ runtimeBridge: "healthy",
46
+ connectionKeyMatched: true,
47
+ connectionKeyRequired: false,
48
+ },
49
+ };
50
+ }
51
+ if (hasMatchingConnectionKeyDigest(connectionKeyMd5)) {
52
+ return {
53
+ status: 200,
54
+ body: {
55
+ ok: true,
56
+ runtimeBridge: "healthy",
57
+ connectionKeyMatched: true,
58
+ connectionKeyRequired: true,
59
+ },
60
+ };
61
+ }
62
+ return {
63
+ status: 401,
64
+ body: {
65
+ ok: false,
66
+ error: "connection_key_mismatch",
67
+ connectionKeyMatched: false,
68
+ connectionKeyRequired: true,
69
+ },
70
+ };
71
+ }
37
72
  instanceRouter.get("/healthz", (_req, res) => {
38
73
  res.json({
39
74
  ok: true,
@@ -42,19 +77,8 @@ instanceRouter.get("/healthz", (_req, res) => {
42
77
  });
43
78
  instanceRouter.get("/connection-check", (req, res) => {
44
79
  const connectionKeyMd5 = String(req.header("X-Bridge-Connection-Key-MD5") || "");
45
- if (hasMatchingConnectionKeyDigest(connectionKeyMd5)) {
46
- res.json({
47
- ok: true,
48
- runtimeBridge: "healthy",
49
- connectionKeyMatched: true,
50
- });
51
- return;
52
- }
53
- res.status(401).json({
54
- ok: false,
55
- error: "connection_key_mismatch",
56
- connectionKeyMatched: false,
57
- });
80
+ const result = buildConnectionCheckResponse(connectionKeyMd5);
81
+ res.status(result.status).json(result.body);
58
82
  });
59
83
  instanceRouter.get("/ping", validateToken, async (_req, res) => {
60
84
  try {
@@ -1,7 +1,13 @@
1
1
  /**
2
2
  * Instance 路由单元测试
3
3
  */
4
- import { describe, it, expect } from 'vitest';
4
+ import { describe, it, expect, vi, afterEach } from 'vitest';
5
+ vi.mock('../services/auto-register.js', () => ({
6
+ getConfiguredConnectionKeys: vi.fn(() => []),
7
+ }));
8
+ afterEach(() => {
9
+ vi.clearAllMocks();
10
+ });
5
11
  describe('instance route validation', () => {
6
12
  it('builds correct ping response when healthy', () => {
7
13
  const buildPingResponse = (schedulerStatus, schedulerBaseUrl) => ({
@@ -61,4 +67,35 @@ describe('instance route validation', () => {
61
67
  expect(normalizeInstallableTools(['Codex'])).toEqual(['codex']);
62
68
  expect(SUPPORTED_INSTALLABLE_TOOLS).toContain('codex');
63
69
  });
70
+ it('allows connection check when no connection key is configured', async () => {
71
+ const { getConfiguredConnectionKeys } = await import('../services/auto-register.js');
72
+ vi.mocked(getConfiguredConnectionKeys).mockReturnValue([]);
73
+ const { buildConnectionCheckResponse } = await import('./instance.js');
74
+ expect(buildConnectionCheckResponse('')).toEqual({
75
+ status: 200,
76
+ body: {
77
+ ok: true,
78
+ runtimeBridge: 'healthy',
79
+ connectionKeyMatched: true,
80
+ connectionKeyRequired: false,
81
+ },
82
+ });
83
+ });
84
+ it('requires matching connection key digest when a connection key is configured', async () => {
85
+ const { createHash } = await import('node:crypto');
86
+ const { getConfiguredConnectionKeys } = await import('../services/auto-register.js');
87
+ vi.mocked(getConfiguredConnectionKeys).mockReturnValue(['secret-key']);
88
+ const { buildConnectionCheckResponse } = await import('./instance.js');
89
+ const digest = createHash('md5').update('secret-key', 'utf8').digest('hex');
90
+ expect(buildConnectionCheckResponse(digest).status).toBe(200);
91
+ expect(buildConnectionCheckResponse('00000000000000000000000000000000')).toEqual({
92
+ status: 401,
93
+ body: {
94
+ ok: false,
95
+ error: 'connection_key_mismatch',
96
+ connectionKeyMatched: false,
97
+ connectionKeyRequired: true,
98
+ },
99
+ });
100
+ });
64
101
  });
@@ -41,6 +41,10 @@ interface AutoRegisterConfig {
41
41
  retryInterval?: number;
42
42
  }
43
43
  export declare function getConfiguredConnectionKeys(): string[];
44
+ /**
45
+ * 配置文件路径
46
+ */
47
+ export declare function getAutoRegisterConfigFilePath(): string;
44
48
  /**
45
49
  * 加载自动注册配置
46
50
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwBD,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAEtD;AAsQD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CA2C/C;AAED,wBAAgB,WAAW,IAAI,kBAAkB,EAAE,CA0ClD;AA8LD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAalB;AAyHD;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAoED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBA7yBtB,OAAO;iBACN,MAAM;mBACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;kBACvB,IAAI;YACV,MAAM;EA2yBf;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA0ED"}
1
+ {"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwBD,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAEtD;AA6DD;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAGtD;AAyJD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CA2C/C;AAED,wBAAgB,WAAW,IAAI,kBAAkB,EAAE,CA0ClD;AA8LD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAalB;AAyHD;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAoED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBAnwBtB,OAAO;iBACN,MAAM;mBACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;kBACvB,IAAI;YACV,MAAM;EAiwBf;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA0ED"}
@@ -84,38 +84,10 @@ function normalizeTargetConfig(source) {
84
84
  /**
85
85
  * 配置文件路径
86
86
  */
87
- function getConfigFilePath() {
87
+ export function getAutoRegisterConfigFilePath() {
88
88
  const homeDir = os.homedir();
89
89
  return path.join(homeDir, ".aws-bridge", "config.json");
90
90
  }
91
- /**
92
- * 确保配置文件存在,如果不存在则创建空配置文件
93
- */
94
- function ensureConfigFile() {
95
- const configPath = getConfigFilePath();
96
- const configDir = path.dirname(configPath);
97
- try {
98
- // 检查配置文件是否已存在
99
- if (fs.existsSync(configPath)) {
100
- return true;
101
- }
102
- // 创建配置目录
103
- if (!fs.existsSync(configDir)) {
104
- fs.mkdirSync(configDir, { recursive: true });
105
- }
106
- // 创建空配置文件
107
- const emptyConfig = {};
108
- fs.writeFileSync(configPath, JSON.stringify(emptyConfig, null, 2), "utf-8");
109
- logger.info(`[AutoRegister] 已创建配置文件: ${configPath}`);
110
- logger.info(`[AutoRegister] 要启用自动注册,请编辑配置文件设置 tenantId 和 userKey`);
111
- return true;
112
- }
113
- catch (error) {
114
- const err = error;
115
- logger.warn(`[AutoRegister] 创建配置文件失败: ${configPath}, error: ${err.message}`);
116
- return false;
117
- }
118
- }
119
91
  /**
120
92
  * 从配置文件加载配置
121
93
  *
@@ -148,9 +120,7 @@ function ensureConfigFile() {
148
120
  * }
149
121
  */
150
122
  function loadConfigFromFile() {
151
- // 确保配置文件存在(如果不存在则创建模板)
152
- ensureConfigFile();
153
- const configPath = getConfigFilePath();
123
+ const configPath = getAutoRegisterConfigFilePath();
154
124
  try {
155
125
  if (!fs.existsSync(configPath)) {
156
126
  logger.debug(`[AutoRegister] 配置文件不存在: ${configPath}`);
@@ -168,8 +138,7 @@ function loadConfigFromFile() {
168
138
  }
169
139
  }
170
140
  function loadTargetsFromFile() {
171
- ensureConfigFile();
172
- const configPath = getConfigFilePath();
141
+ const configPath = getAutoRegisterConfigFilePath();
173
142
  try {
174
143
  if (!fs.existsSync(configPath)) {
175
144
  return [];
@@ -0,0 +1,15 @@
1
+ interface PromptIO {
2
+ question(prompt: string): Promise<string>;
3
+ close(): void;
4
+ }
5
+ interface EnsureStartupConfigOptions {
6
+ configPath?: string;
7
+ interactive?: boolean;
8
+ prompt?: PromptIO;
9
+ env?: NodeJS.ProcessEnv;
10
+ generateConnectionKey?: () => string;
11
+ }
12
+ export type StartupConfigWizardResult = "exists" | "created" | "skipped" | "non-interactive" | "failed";
13
+ export declare function ensureStartupConfig(options?: EnsureStartupConfigOptions): Promise<StartupConfigWizardResult>;
14
+ export {};
15
+ //# sourceMappingURL=startup-config-wizard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-config-wizard.d.ts","sourceRoot":"","sources":["../../src/services/startup-config-wizard.ts"],"names":[],"mappings":"AAqBA,UAAU,QAAQ;IAChB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,KAAK,IAAI,IAAI,CAAC;CACf;AAED,UAAU,0BAA0B;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,MAAM,yBAAyB,GACjC,QAAQ,GACR,SAAS,GACT,SAAS,GACT,iBAAiB,GACjB,QAAQ,CAAC;AAiGb,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,yBAAyB,CAAC,CAmDpC"}
@@ -0,0 +1,122 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { stdin as input, stdout as output } from "node:process";
7
+ import { getAutoRegisterConfigFilePath } from "./auto-register.js";
8
+ import { logger } from "../utils/logger.js";
9
+ function normalizeAnswer(answer) {
10
+ return answer.trim().toLowerCase();
11
+ }
12
+ function isYes(answer) {
13
+ return ["", "y", "yes", "是", "好", "配置"].includes(normalizeAnswer(answer));
14
+ }
15
+ function isNo(answer) {
16
+ return ["n", "no", "否", "不", "跳过", "skip", "s"].includes(normalizeAnswer(answer));
17
+ }
18
+ function writeConfigFile(configPath, config) {
19
+ mkdirSync(path.dirname(configPath), { recursive: true });
20
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
21
+ }
22
+ function generateDefaultConnectionKey() {
23
+ return `awsb_${randomBytes(24).toString("base64url")}`;
24
+ }
25
+ function writeSkippedConfig(configPath, generateConnectionKey) {
26
+ const connectionKey = generateConnectionKey();
27
+ writeConfigFile(configPath, { connectionKey });
28
+ const message = `[runtime-bridge] 已创建配置文件: ${configPath}\n` +
29
+ `[runtime-bridge] 自动生成 connectionKey: ${connectionKey}\n` +
30
+ "[runtime-bridge] 请保存该 connectionKey;server/面板连接此 Bridge 时需要使用它。";
31
+ logger.info(message);
32
+ output.write(`${message}\n`);
33
+ }
34
+ async function askOptional(prompt, label, defaultValue) {
35
+ const answer = await prompt.question(`${label} (${defaultValue}): `);
36
+ return answer.trim() || defaultValue;
37
+ }
38
+ async function askRequired(prompt, label) {
39
+ for (;;) {
40
+ const answer = (await prompt.question(`${label}: `)).trim();
41
+ if (answer) {
42
+ return answer;
43
+ }
44
+ output.write(`${label} 不能为空,请重新输入。\n`);
45
+ }
46
+ }
47
+ async function askShouldConfigure(prompt) {
48
+ for (;;) {
49
+ const answer = await prompt.question("未检测到 ~/.aws-bridge/config.json,是否现在进入自动注册配置引导?(Y/n): ");
50
+ if (isYes(answer)) {
51
+ return true;
52
+ }
53
+ if (isNo(answer)) {
54
+ return false;
55
+ }
56
+ output.write("请输入 Y 继续配置,或输入 n 跳过。\n");
57
+ }
58
+ }
59
+ async function collectStartupConfig(prompt) {
60
+ const defaultServerUrl = "http://127.0.0.1:8080";
61
+ const defaultInstanceName = os.hostname() || "AgentsWork Runtime Bridge";
62
+ const defaultRegisterIp = "127.0.0.1";
63
+ const connectionKey = await askRequired(prompt, "connectionKey(面板连接 Bridge 的密钥)");
64
+ const serverUrl = await askOptional(prompt, "serverUrl(调度中心地址)", defaultServerUrl);
65
+ const instanceName = await askOptional(prompt, "instanceName(实例名称)", defaultInstanceName);
66
+ const userKey = await askRequired(prompt, "userKey(用户 API Key)");
67
+ const registerIp = await askOptional(prompt, "registerIp(Bridge 对外访问 IP)", defaultRegisterIp);
68
+ return {
69
+ connectionKey,
70
+ autoRegisterTargets: [
71
+ {
72
+ serverUrl,
73
+ instanceName,
74
+ userKey,
75
+ registerIp,
76
+ },
77
+ ],
78
+ };
79
+ }
80
+ export async function ensureStartupConfig(options = {}) {
81
+ const configPath = options.configPath || getAutoRegisterConfigFilePath();
82
+ const generateConnectionKey = options.generateConnectionKey || generateDefaultConnectionKey;
83
+ if (existsSync(configPath)) {
84
+ return "exists";
85
+ }
86
+ const env = options.env || process.env;
87
+ if (env.AWS_BRIDGE_SKIP_SETUP === "true") {
88
+ writeSkippedConfig(configPath, generateConnectionKey);
89
+ logger.info("[runtime-bridge] AWS_BRIDGE_SKIP_SETUP=true,跳过首次配置引导");
90
+ return "skipped";
91
+ }
92
+ const interactive = options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
93
+ if (!interactive) {
94
+ writeSkippedConfig(configPath, generateConnectionKey);
95
+ logger.info(`[runtime-bridge] 未检测到配置文件: ${configPath};当前为非交互环境,跳过首次配置引导`);
96
+ return "non-interactive";
97
+ }
98
+ const prompt = options.prompt || createInterface({ input, output, terminal: true });
99
+ const shouldClosePrompt = !options.prompt;
100
+ try {
101
+ const shouldConfigure = await askShouldConfigure(prompt);
102
+ if (!shouldConfigure) {
103
+ writeSkippedConfig(configPath, generateConnectionKey);
104
+ logger.info("[runtime-bridge] 已跳过首次配置引导");
105
+ return "skipped";
106
+ }
107
+ const config = await collectStartupConfig(prompt);
108
+ writeConfigFile(configPath, config);
109
+ logger.info(`[runtime-bridge] 已写入配置文件: ${configPath}`);
110
+ return "created";
111
+ }
112
+ catch (error) {
113
+ const err = error;
114
+ logger.warn(`[runtime-bridge] 首次配置引导失败: ${err.message}`);
115
+ return "failed";
116
+ }
117
+ finally {
118
+ if (shouldClosePrompt) {
119
+ prompt.close();
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=startup-config-wizard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-config-wizard.test.d.ts","sourceRoot":"","sources":["../../src/services/startup-config-wizard.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { ensureStartupConfig } from "./startup-config-wizard.js";
6
+ class FakePrompt {
7
+ constructor(answers) {
8
+ this.answers = answers;
9
+ this.index = 0;
10
+ this.closed = false;
11
+ }
12
+ async question() {
13
+ return this.answers[this.index++] ?? "";
14
+ }
15
+ close() {
16
+ this.closed = true;
17
+ }
18
+ }
19
+ const tempRoots = [];
20
+ function createConfigPath() {
21
+ const root = mkdtempSync(path.join(os.tmpdir(), "aws-bridge-wizard-"));
22
+ tempRoots.push(root);
23
+ return path.join(root, ".aws-bridge", "config.json");
24
+ }
25
+ afterEach(() => {
26
+ for (const root of tempRoots.splice(0)) {
27
+ rmSync(root, { recursive: true, force: true });
28
+ }
29
+ });
30
+ describe("startup config wizard", () => {
31
+ it("does not prompt when config file already exists", async () => {
32
+ const configPath = createConfigPath();
33
+ mkdirSync(path.dirname(configPath), { recursive: true });
34
+ writeFileSync(configPath, "{}", "utf-8");
35
+ const prompt = new FakePrompt([]);
36
+ const result = await ensureStartupConfig({ configPath, interactive: true, prompt });
37
+ expect(result).toBe("exists");
38
+ expect(prompt.closed).toBe(false);
39
+ });
40
+ it("writes generated connection key when user chooses no", async () => {
41
+ const configPath = createConfigPath();
42
+ const prompt = new FakePrompt(["n"]);
43
+ const result = await ensureStartupConfig({
44
+ configPath,
45
+ interactive: true,
46
+ prompt,
47
+ generateConnectionKey: () => "awsb_test_generated_key",
48
+ });
49
+ expect(result).toBe("skipped");
50
+ expect(JSON.parse(readFileSync(configPath, "utf-8"))).toEqual({
51
+ connectionKey: "awsb_test_generated_key",
52
+ });
53
+ });
54
+ it("writes connection key and auto register target from interactive answers", async () => {
55
+ const configPath = createConfigPath();
56
+ const prompt = new FakePrompt([
57
+ "y",
58
+ "gkmzjaznX55..",
59
+ "http://127.0.0.1:8080",
60
+ "至强主机",
61
+ "aws_live_crh_g0GGrg5IntODg1sFwnas7qf4yGrcwoisDWPBtd8",
62
+ "127.0.0.1",
63
+ ]);
64
+ const result = await ensureStartupConfig({ configPath, interactive: true, prompt });
65
+ expect(result).toBe("created");
66
+ expect(JSON.parse(readFileSync(configPath, "utf-8"))).toEqual({
67
+ connectionKey: "gkmzjaznX55..",
68
+ autoRegisterTargets: [
69
+ {
70
+ serverUrl: "http://127.0.0.1:8080",
71
+ instanceName: "至强主机",
72
+ userKey: "aws_live_crh_g0GGrg5IntODg1sFwnas7qf4yGrcwoisDWPBtd8",
73
+ registerIp: "127.0.0.1",
74
+ },
75
+ ],
76
+ });
77
+ });
78
+ it("writes generated connection key and does not block startup in non-interactive environments", async () => {
79
+ const configPath = createConfigPath();
80
+ const result = await ensureStartupConfig({
81
+ configPath,
82
+ interactive: false,
83
+ generateConnectionKey: () => "awsb_non_interactive_key",
84
+ });
85
+ expect(result).toBe("non-interactive");
86
+ expect(JSON.parse(readFileSync(configPath, "utf-8"))).toEqual({
87
+ connectionKey: "awsb_non_interactive_key",
88
+ });
89
+ });
90
+ it("supports environment opt-out for unattended startup", async () => {
91
+ const configPath = createConfigPath();
92
+ const result = await ensureStartupConfig({
93
+ configPath,
94
+ interactive: true,
95
+ env: { AWS_BRIDGE_SKIP_SETUP: "true" },
96
+ generateConnectionKey: () => "awsb_env_skip_key",
97
+ });
98
+ expect(result).toBe("skipped");
99
+ expect(JSON.parse(readFileSync(configPath, "utf-8"))).toEqual({
100
+ connectionKey: "awsb_env_skip_key",
101
+ });
102
+ });
103
+ });