aws-runtime-bridge 1.0.1 → 1.0.3

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;AAkL7C,wBAAgB,6BAA6B,IAAI,IAAI,CAapD"}
@@ -0,0 +1,159 @@
1
+ import { Router } from "express";
2
+ import { clearRuntimeBinding, getRuntimeAccessToken, getRuntimeBindingPublicState, getRuntimePairingCode, hasRuntimeBinding, saveRuntimeBinding, validateRuntimeBindingToken, validateRuntimePairingCode, } from "../services/runtime-binding.js";
3
+ import { validateToken } from "../middleware/auth.js";
4
+ import { runtimeToken } from "../config.js";
5
+ import { requestRuntimeAccessTokenRefresh } from "../services/auto-register.js";
6
+ import { createLogger } from "../utils/logger.js";
7
+ const log = createLogger("runtime-binding-route");
8
+ export const runtimeBindingRouter = Router();
9
+ function extractRuntimeToken(req) {
10
+ const headerToken = req.header("X-Runtime-Token");
11
+ if (headerToken) {
12
+ return headerToken.trim();
13
+ }
14
+ const authorization = req.header("Authorization") || "";
15
+ const bearerMatch = authorization.match(/^Bearer\s+(.+)$/i);
16
+ return bearerMatch?.[1]?.trim() || "";
17
+ }
18
+ runtimeBindingRouter.get("/binding/status", (_req, res) => {
19
+ const state = getRuntimeBindingPublicState();
20
+ res.json({
21
+ ok: true,
22
+ ...state,
23
+ pairingRequired: !state.paired,
24
+ });
25
+ });
26
+ runtimeBindingRouter.post("/binding/pair", (req, res) => {
27
+ const { accessToken, instanceId, userId, schedulerBaseUrl, pairingCode } = req.body || {};
28
+ const existingBinding = hasRuntimeBinding();
29
+ const currentRuntimeToken = extractRuntimeToken(req);
30
+ if (existingBinding) {
31
+ const canRotate = validateRuntimeBindingToken(currentRuntimeToken);
32
+ if (!canRotate) {
33
+ res.status(401).json({
34
+ error: "unauthorized",
35
+ message: "Bridge is already paired. Provide the current runtime token to rotate binding.",
36
+ });
37
+ return;
38
+ }
39
+ }
40
+ else if (!validateRuntimePairingCode(pairingCode)) {
41
+ res.status(401).json({
42
+ error: "invalid_pairing_code",
43
+ message: "Invalid pairing code. Read the current pairing code from the bridge console and try again.",
44
+ });
45
+ return;
46
+ }
47
+ try {
48
+ saveRuntimeBinding({
49
+ accessToken: String(accessToken || ""),
50
+ instanceId: instanceId ? String(instanceId) : undefined,
51
+ userId: userId ? String(userId) : undefined,
52
+ schedulerBaseUrl: schedulerBaseUrl ? String(schedulerBaseUrl) : undefined,
53
+ });
54
+ log.info(`[runtime-bridge] paired with instanceId=${instanceId || "unknown"}`);
55
+ res.json({
56
+ ok: true,
57
+ ...getRuntimeBindingPublicState(),
58
+ });
59
+ }
60
+ catch (error) {
61
+ const err = error;
62
+ res
63
+ .status(400)
64
+ .json({ error: err.message || "pair runtime bridge failed" });
65
+ }
66
+ });
67
+ function isLoopbackRequest(req) {
68
+ const ip = req.ip || req.socket.remoteAddress || "";
69
+ return ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
70
+ }
71
+ runtimeBindingRouter.post("/binding/request-token", async (req, res) => {
72
+ if (!isLoopbackRequest(req)) {
73
+ res.status(403).json({ ok: false, error: "loopback request required" });
74
+ return;
75
+ }
76
+ const { userId, serverUrl, schedulerBaseUrl } = req.body || {};
77
+ const scopedToken = getRuntimeAccessToken(userId, serverUrl || schedulerBaseUrl);
78
+ if (scopedToken) {
79
+ res.json({
80
+ ok: true,
81
+ runtimeAccessToken: scopedToken,
82
+ updated: false,
83
+ });
84
+ return;
85
+ }
86
+ const result = await requestRuntimeAccessTokenRefresh();
87
+ if (!result.success || !result.runtimeAccessToken) {
88
+ res.status(400).json({
89
+ ok: false,
90
+ error: result.error || "request runtime token failed",
91
+ });
92
+ return;
93
+ }
94
+ res.json({
95
+ ok: true,
96
+ runtimeAccessToken: result.runtimeAccessToken,
97
+ updated: result.updated,
98
+ });
99
+ });
100
+ runtimeBindingRouter.post("/binding/issue", (req, res) => {
101
+ const currentRuntimeToken = extractRuntimeToken(req);
102
+ const authorizedByScheduler = currentRuntimeToken === runtimeToken;
103
+ const authorizedByCurrentBinding = validateRuntimeBindingToken(currentRuntimeToken);
104
+ if (!authorizedByScheduler && !authorizedByCurrentBinding) {
105
+ res.status(401).json({ error: "unauthorized" });
106
+ return;
107
+ }
108
+ const { accessToken, instanceId, userId, schedulerBaseUrl } = req.body || {};
109
+ const nextToken = String(accessToken || "").trim();
110
+ if (!nextToken) {
111
+ res.status(400).json({ error: "accessToken is required" });
112
+ return;
113
+ }
114
+ try {
115
+ const previousToken = getRuntimeAccessToken();
116
+ const updated = previousToken !== nextToken;
117
+ if (updated) {
118
+ saveRuntimeBinding({
119
+ accessToken: nextToken,
120
+ instanceId: instanceId ? String(instanceId) : undefined,
121
+ userId: userId ? String(userId) : undefined,
122
+ schedulerBaseUrl: schedulerBaseUrl
123
+ ? String(schedulerBaseUrl)
124
+ : undefined,
125
+ });
126
+ log.info(`[runtime-bridge] runtime token redispatched and updated: instanceId=${instanceId || "unknown"}`);
127
+ }
128
+ else {
129
+ log.info(`[runtime-bridge] runtime token redispatched but unchanged: instanceId=${instanceId || "unknown"}`);
130
+ }
131
+ res.json({
132
+ ok: true,
133
+ updated,
134
+ ...getRuntimeBindingPublicState(),
135
+ });
136
+ }
137
+ catch (error) {
138
+ const err = error;
139
+ res
140
+ .status(400)
141
+ .json({ error: err.message || "issue runtime token failed" });
142
+ }
143
+ });
144
+ runtimeBindingRouter.post("/binding/unpair", validateToken, (_req, res) => {
145
+ clearRuntimeBinding();
146
+ res.json({
147
+ ok: true,
148
+ ...getRuntimeBindingPublicState(),
149
+ });
150
+ });
151
+ export function logRuntimeBindingStartupState() {
152
+ const state = getRuntimeBindingPublicState();
153
+ if (state.paired) {
154
+ log.info(`[runtime-bridge] runtime binding loaded: instanceId=${state.instanceId || "unknown"}`);
155
+ return;
156
+ }
157
+ log.info("[runtime-bridge] bridge is unpaired; sensitive APIs will reject requests until paired");
158
+ log.info(`[runtime-bridge] pairing code: ${getRuntimePairingCode()}`);
159
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAEL,KAAK,oBAAoB,EAM1B,MAAM,qBAAqB,CAAC;AAc7B,eAAO,MAAM,cAAc,4CAAW,CAAC;AAIvC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,eAAO,MAAM,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAa,CAAC;AAEnE,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D"}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAEL,KAAK,oBAAoB,EAM1B,MAAM,qBAAqB,CAAC;AAa7B,eAAO,MAAM,cAAc,4CAAW,CAAC;AAIvC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,eAAO,MAAM,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAa,CAAC;AAEnE,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D"}
@@ -10,7 +10,6 @@ import { v4 as uuidv4 } from 'uuid';
10
10
  import { adapterRegistry, registerSdkProviders, resolveSdkProviderIdByCommand, } from '../adapter/index.js';
11
11
  import { validateToken } from '../middleware/auth.js';
12
12
  import { getAgentProcessManager } from '../services/agent-process-manager.js';
13
- import { AWS_MCP_SERVER_NAME, buildAwsMcpServerConfig } from '../services/aws-client-agent-mcp.js';
14
13
  import { findClaudeCodeProcesses } from '../services/process-detector.js';
15
14
  import { flushSessionOutput, scheduleOutputFlush, sendQuestionRequest, sendStatus, sessions } from '../services/session-output.js';
16
15
  import { loadPersistedSessions, removePersistedSession, removePersistedSessionByAgentId, savePersistedSessions, upsertPersistedSession, } from '../services/terminal-persistence.js';
@@ -196,13 +195,9 @@ async function startSdkSession(req, res) {
196
195
  workingDirectory: workspacePath,
197
196
  autoAccept: Boolean(autoAccept),
198
197
  initialPrompt,
199
- // MCP 配置
198
+ // MCP 配置:不再默认注入 aws-mcp;由用户在面板中安装/配置
200
199
  mcpConfigPath,
201
- extraMcpServers: {
202
- // 默认加载 aws-mcp(用于 Agent 间通信)
203
- [AWS_MCP_SERVER_NAME]: buildAwsMcpServerConfig({ agentId, workspacePath }),
204
- ...(extraMcpServers || {}),
205
- },
200
+ extraMcpServers: extraMcpServers || {},
206
201
  };
207
202
  // 存储会话信息
208
203
  sdkSessions.set(sessionId, {
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { SDK_PROVIDER_DEFINITIONS } from '../adapter/SdkProviderSpi.js';
3
- import { AWS_MCP_SERVER_NAME } from '../services/aws-client-agent-mcp.js';
4
3
  import { resolveSdkProviderId } from './terminal.js';
5
4
  describe('terminal route validation', () => {
6
5
  it('requires agentId and workspacePath for start', () => {
@@ -64,9 +63,6 @@ describe('terminal configuration', () => {
64
63
  const env = buildTerminalEnv('agent-123', { PATH: '/usr/bin' });
65
64
  expect(env.AWS_AGENT_ID).toBe('agent-123');
66
65
  });
67
- it('uses aws-mcp as the default injected MCP server name', () => {
68
- expect(AWS_MCP_SERVER_NAME).toBe('aws-mcp');
69
- });
70
66
  });
71
67
  describe('resolveSdkProviderId', () => {
72
68
  it('exposes codex through the SDK provider SPI', () => {
@@ -25,7 +25,9 @@ interface AutoRegisterConfig {
25
25
  roleName?: string;
26
26
  /** 工作空间路径 */
27
27
  workspacePath?: string;
28
- /** 虚拟 IP(EasyTier) */
28
+ /** 注册 IP(优先用于生成 runtimeBridgeBaseUrl) */
29
+ registerIp?: string;
30
+ /** 虚拟 IP(EasyTier,兼容旧配置) */
29
31
  virtualIp?: string;
30
32
  /** 注册重试次数 */
31
33
  maxRetries?: number;
@@ -55,6 +57,12 @@ export declare function autoRegister(customConfig?: Partial<AutoRegisterConfig>)
55
57
  /**
56
58
  * 注销实例
57
59
  */
60
+ export declare function requestRuntimeAccessTokenRefresh(): Promise<{
61
+ success: boolean;
62
+ runtimeAccessToken?: string;
63
+ updated?: boolean;
64
+ error?: string;
65
+ }>;
58
66
  export declare function unregister(): Promise<boolean>;
59
67
  /**
60
68
  * 获取注册状态
@@ -1 +1 @@
1
- {"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoCH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,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,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwJD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CAuC/C;AA2KD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAyE/F;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBA5dtB,OAAO;iBACN,MAAM;kBACL,IAAI;YACV,MAAM;EA2df;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;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2D/G"}
1
+ {"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6CH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,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,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAsKD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CAwC/C;AAuLD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAoHlB;AAED;;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;gBA/mBtB,OAAO;iBACN,MAAM;kBACL,IAAI;YACV,MAAM;EA8mBf;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"}
@@ -7,12 +7,13 @@
7
7
  * 1. 环境变量
8
8
  * 2. 用户目录下的 .aws-bridge/config.json 文件
9
9
  */
10
- import axios from 'axios';
11
- import os from 'node:os';
12
- import path from 'node:path';
13
- import fs from 'node:fs';
14
- import { logger } from '../utils/logger.js';
15
- import { schedulerBaseUrl, runtimeToken } from '../config.js';
10
+ import axios from "axios";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import fs from "node:fs";
14
+ import { logger } from "../utils/logger.js";
15
+ import { schedulerBaseUrl, runtimeToken } from "../config.js";
16
+ import { getRuntimeAccessToken, getRuntimeBindingPublicState, saveRuntimeBinding, } from "./runtime-binding.js";
16
17
  // 默认配置
17
18
  const DEFAULT_CONFIG = {
18
19
  enabled: false,
@@ -27,7 +28,7 @@ let registrationState = {
27
28
  */
28
29
  function getConfigFilePath() {
29
30
  const homeDir = os.homedir();
30
- return path.join(homeDir, '.aws-bridge', 'config.json');
31
+ return path.join(homeDir, ".aws-bridge", "config.json");
31
32
  }
32
33
  /**
33
34
  * 确保配置文件存在,如果不存在则创建空配置文件
@@ -46,7 +47,7 @@ function ensureConfigFile() {
46
47
  }
47
48
  // 创建空配置文件
48
49
  const emptyConfig = {};
49
- fs.writeFileSync(configPath, JSON.stringify(emptyConfig, null, 2), 'utf-8');
50
+ fs.writeFileSync(configPath, JSON.stringify(emptyConfig, null, 2), "utf-8");
50
51
  logger.info(`[AutoRegister] 已创建配置文件: ${configPath}`);
51
52
  logger.info(`[AutoRegister] 要启用自动注册,请编辑配置文件设置 tenantId 和 userKey`);
52
53
  return true;
@@ -70,7 +71,8 @@ function ensureConfigFile() {
70
71
  * "projectName": "default", // 可选
71
72
  * "roleName": "Bridge", // 可选
72
73
  * "workspacePath": "/path/to/workspace", // 可选
73
- * "virtualIp": "10.144.144.1", // 可选
74
+ * "registerIp": "192.168.1.25", // 可选,优先作为注册 IP
75
+ * "virtualIp": "10.144.144.1", // 可选,兼容 EasyTier 虚拟 IP
74
76
  * "maxRetries": 5, // 可选
75
77
  * "retryInterval": 5000 // 可选
76
78
  * }
@@ -84,7 +86,7 @@ function loadConfigFromFile() {
84
86
  logger.debug(`[AutoRegister] 配置文件不存在: ${configPath}`);
85
87
  return null;
86
88
  }
87
- const content = fs.readFileSync(configPath, 'utf-8');
89
+ const content = fs.readFileSync(configPath, "utf-8");
88
90
  const config = JSON.parse(content);
89
91
  logger.info(`[AutoRegister] 从配置文件加载配置: ${configPath}`);
90
92
  return {
@@ -94,6 +96,7 @@ function loadConfigFromFile() {
94
96
  projectName: config.projectName,
95
97
  roleName: config.roleName,
96
98
  workspacePath: config.workspacePath,
99
+ registerIp: config.registerIp,
97
100
  virtualIp: config.virtualIp,
98
101
  maxRetries: config.maxRetries,
99
102
  retryInterval: config.retryInterval,
@@ -110,7 +113,7 @@ function loadConfigFromFile() {
110
113
  */
111
114
  function loadConfigFromEnv() {
112
115
  const envConfig = {};
113
- if (process.env.AWS_AUTO_REGISTER === 'true') {
116
+ if (process.env.AWS_AUTO_REGISTER === "true") {
114
117
  envConfig.enabled = true;
115
118
  }
116
119
  if (process.env.AWS_TENANT_ID) {
@@ -131,6 +134,9 @@ function loadConfigFromEnv() {
131
134
  if (process.env.AWS_WORKSPACE_PATH) {
132
135
  envConfig.workspacePath = process.env.AWS_WORKSPACE_PATH;
133
136
  }
137
+ if (process.env.AWS_REGISTER_IP) {
138
+ envConfig.registerIp = process.env.AWS_REGISTER_IP;
139
+ }
134
140
  if (process.env.AWS_VIRTUAL_IP) {
135
141
  envConfig.virtualIp = process.env.AWS_VIRTUAL_IP;
136
142
  }
@@ -167,19 +173,20 @@ export function loadConfig() {
167
173
  // 否则,如果配置文件存在(无论是否有内容),则启用自动注册
168
174
  let enabled = false;
169
175
  if (process.env.AWS_AUTO_REGISTER !== undefined) {
170
- enabled = process.env.AWS_AUTO_REGISTER === 'true';
176
+ enabled = process.env.AWS_AUTO_REGISTER === "true";
171
177
  }
172
178
  else if (fileConfig) {
173
179
  enabled = true;
174
180
  }
175
181
  return {
176
182
  enabled,
177
- tenantId: merged.tenantId || '', // 保持为空字符串,后续会通过userKey自动推导
178
- userKey: merged.userKey || '',
183
+ tenantId: merged.tenantId || "", // 保持为空字符串,后续会通过userKey自动推导
184
+ userKey: merged.userKey || "",
179
185
  instanceName,
180
186
  projectName: merged.projectName,
181
187
  roleName: merged.roleName,
182
188
  workspacePath: merged.workspacePath,
189
+ registerIp: merged.registerIp,
183
190
  virtualIp: merged.virtualIp,
184
191
  maxRetries: merged.maxRetries,
185
192
  retryInterval: merged.retryInterval,
@@ -197,7 +204,7 @@ function getAllLocalIpAddresses() {
197
204
  continue;
198
205
  for (const net of nets) {
199
206
  // 跳过内部和非 IPv4 地址
200
- if (net.family === 'IPv4' && !net.internal) {
207
+ if (net.family === "IPv4" && !net.internal) {
201
208
  addresses.push({
202
209
  address: net.address,
203
210
  netmask: net.netmask,
@@ -212,7 +219,7 @@ function getAllLocalIpAddresses() {
212
219
  * 将 IP 地址转换为整数
213
220
  */
214
221
  function ipToInt(ip) {
215
- const parts = ip.split('.').map(Number);
222
+ const parts = ip.split(".").map(Number);
216
223
  return (parts[0] << 24) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
217
224
  }
218
225
  /**
@@ -231,8 +238,8 @@ function extractIpFromUrl(url) {
231
238
  try {
232
239
  const urlObj = new URL(url);
233
240
  // 如果是主机名,尝试解析
234
- if (urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1') {
235
- return '127.0.0.1';
241
+ if (urlObj.hostname === "localhost" || urlObj.hostname === "127.0.0.1") {
242
+ return "127.0.0.1";
236
243
  }
237
244
  // 检查是否是有效的 IP 地址
238
245
  const ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/;
@@ -258,8 +265,8 @@ function extractIpFromUrl(url) {
258
265
  function getPreferredIpAddress(schedulerUrl) {
259
266
  const allIps = getAllLocalIpAddresses();
260
267
  if (allIps.length === 0) {
261
- logger.warn('[AutoRegister] 未找到任何非内部 IP 地址,使用 127.0.0.1');
262
- return '127.0.0.1';
268
+ logger.warn("[AutoRegister] 未找到任何非内部 IP 地址,使用 127.0.0.1");
269
+ return "127.0.0.1";
263
270
  }
264
271
  // 尝试从调度中心 URL 提取 IP
265
272
  const schedulerIp = extractIpFromUrl(schedulerUrl);
@@ -274,7 +281,7 @@ function getPreferredIpAddress(schedulerUrl) {
274
281
  logger.warn(`[AutoRegister] 未找到与调度中心 (${schedulerIp}) 同子网的 IP,使用第一个可用 IP`);
275
282
  }
276
283
  else {
277
- logger.info('[AutoRegister] 无法解析调度中心 IP,使用第一个可用 IP');
284
+ logger.info("[AutoRegister] 无法解析调度中心 IP,使用第一个可用 IP");
278
285
  }
279
286
  // 回退:返回第一个非内部 IP
280
287
  return allIps[0].address;
@@ -297,20 +304,20 @@ async function doRegister(config) {
297
304
  userKey: config.userKey,
298
305
  instanceName,
299
306
  hostname: os.hostname(),
300
- virtualIp: config.virtualIp,
301
- runtimeBridgeBaseUrl: `http://${config.virtualIp || localIp}:${port}`,
307
+ virtualIp: config.registerIp || config.virtualIp,
308
+ runtimeBridgeBaseUrl: `http://${config.registerIp || config.virtualIp || localIp}:${port}`,
302
309
  workspacePath: config.workspacePath,
303
310
  projectName: config.projectName,
304
311
  roleName: config.roleName,
305
312
  };
306
313
  logger.info(`[AutoRegister] 正在注册实例: ${instanceName}`);
307
314
  logger.info(`[AutoRegister] 调度中心: ${schedulerBaseUrl}`);
308
- logger.info(`[AutoRegister] 用户 Key: ${config.userKey ? '****' + config.userKey.slice(-4) : '(未设置)'}`);
309
- logger.info(`[AutoRegister] 租户 ID: ${config.tenantId || '(自动推导)'}`);
315
+ logger.info(`[AutoRegister] 用户 Key: ${config.userKey ? "****" + config.userKey.slice(-4) : "(未设置)"}`);
316
+ logger.info(`[AutoRegister] 租户 ID: ${config.tenantId || "(自动推导)"}`);
310
317
  const response = await axios.post(`${schedulerBaseUrl}/api/instances/register`, request, {
311
318
  headers: {
312
- 'Content-Type': 'application/json',
313
- 'X-Runtime-Token': runtimeToken,
319
+ "Content-Type": "application/json",
320
+ "X-Runtime-Token": runtimeToken,
314
321
  },
315
322
  timeout: 10000,
316
323
  });
@@ -322,8 +329,8 @@ async function doRegister(config) {
322
329
  async function doUnregister(instanceId) {
323
330
  const response = await axios.post(`${schedulerBaseUrl}/api/instances/unregister`, { instanceId }, {
324
331
  headers: {
325
- 'Content-Type': 'application/json',
326
- 'X-Runtime-Token': runtimeToken,
332
+ "Content-Type": "application/json",
333
+ "X-Runtime-Token": runtimeToken,
327
334
  },
328
335
  timeout: 10000,
329
336
  });
@@ -348,13 +355,13 @@ export async function autoRegister(customConfig) {
348
355
  };
349
356
  // 检查是否启用
350
357
  if (!config.enabled) {
351
- logger.info('[AutoRegister] 自动注册未启用');
352
- logger.info('[AutoRegister] 要启用自动注册,请创建配置文件 ~/.aws-bridge/config.json 或设置环境变量 AWS_AUTO_REGISTER=true');
358
+ logger.info("[AutoRegister] 自动注册未启用");
359
+ logger.info("[AutoRegister] 要启用自动注册,请创建配置文件 ~/.aws-bridge/config.json 或设置环境变量 AWS_AUTO_REGISTER=true");
353
360
  return false;
354
361
  }
355
362
  // 验证必填字段(userKey 为必填,tenantId 现在可选)
356
363
  if (!config.userKey) {
357
- logger.error('[AutoRegister] 缺少用户 Key(在配置文件中设置 userKey 或设置环境变量 AWS_USER_KEY)');
364
+ logger.error("[AutoRegister] 缺少用户 Key(在配置文件中设置 userKey 或设置环境变量 AWS_USER_KEY)");
358
365
  return false;
359
366
  }
360
367
  // tenantId 现在是可选的,如果没有提供,后端会根据 userKey 自动推导
@@ -372,7 +379,27 @@ export async function autoRegister(customConfig) {
372
379
  logger.info(`[AutoRegister] ✓ 注册成功!`);
373
380
  logger.info(`[AutoRegister] 实例 ID: ${response.instanceId}`);
374
381
  logger.info(`[AutoRegister] 消息: ${response.message}`);
375
- logger.info(`[AutoRegister] 更新: ${response.isUpdate ? '' : ''}`);
382
+ logger.info(`[AutoRegister] 更新: ${response.isUpdate ? "" : ""}`);
383
+ if (response.runtimeAccessToken) {
384
+ try {
385
+ saveRuntimeBinding({
386
+ accessToken: response.runtimeAccessToken,
387
+ instanceId: response.instanceId,
388
+ userId: response.userId,
389
+ schedulerBaseUrl: response.serverUrl || schedulerBaseUrl,
390
+ });
391
+ logger.info("[AutoRegister] 已保存调度中心下发的 runtime access token");
392
+ }
393
+ catch (error) {
394
+ const err = error;
395
+ logger.error(`[AutoRegister] 保存 runtime access token 失败: ${err.message}`);
396
+ registrationState.error = err.message;
397
+ return false;
398
+ }
399
+ }
400
+ else {
401
+ logger.warn("[AutoRegister] 注册响应未包含 runtime access token,MCP 将只能使用兼容的静态内部密钥访问调度中心");
402
+ }
376
403
  return true;
377
404
  }
378
405
  else {
@@ -382,25 +409,27 @@ export async function autoRegister(customConfig) {
382
409
  }
383
410
  catch (error) {
384
411
  const err = error;
385
- const message = err.response?.data ? JSON.stringify(err.response.data) : err.message;
412
+ const message = err.response?.data
413
+ ? JSON.stringify(err.response.data)
414
+ : err.message;
386
415
  logger.error(`[AutoRegister] 注册请求失败 (尝试 ${attempt}/${maxRetries}): ${message}`);
387
416
  registrationState.error = message;
388
417
  }
389
418
  // 如果不是最后一次尝试,等待后重试
390
419
  if (attempt < maxRetries) {
391
420
  logger.info(`[AutoRegister] ${retryInterval / 1000} 秒后重试...`);
392
- await new Promise(resolve => setTimeout(resolve, retryInterval));
421
+ await new Promise((resolve) => setTimeout(resolve, retryInterval));
393
422
  }
394
423
  }
395
424
  logger.error(`[AutoRegister] ✗ 注册失败,已达最大重试次数`);
396
425
  logger.error(`[AutoRegister] ========================================`);
397
426
  logger.error(`[AutoRegister] 自动注册已启用但注册失败,实例无法正常工作`);
398
427
  logger.error(`[AutoRegister] 请检查以下配置:`);
399
- logger.error(`[AutoRegister] - 用户 Key (userKey): ${config.userKey ? '****' + config.userKey.slice(-4) : '(未设置)'}`);
400
- logger.error(`[AutoRegister] - 租户 ID (tenantId): ${config.tenantId || '(可选,将自动推导)'}`);
401
- logger.error(`[AutoRegister] - 实例名 (instanceName): ${config.instanceName || '(使用主机名)'}`);
428
+ logger.error(`[AutoRegister] - 用户 Key (userKey): ${config.userKey ? "****" + config.userKey.slice(-4) : "(未设置)"}`);
429
+ logger.error(`[AutoRegister] - 租户 ID (tenantId): ${config.tenantId || "(可选,将自动推导)"}`);
430
+ logger.error(`[AutoRegister] - 实例名 (instanceName): ${config.instanceName || "(使用主机名)"}`);
402
431
  logger.error(`[AutoRegister] - 调度中心地址: ${schedulerBaseUrl}`);
403
- logger.error(`[AutoRegister] - 最后错误: ${registrationState.error || '未知'}`);
432
+ logger.error(`[AutoRegister] - 最后错误: ${registrationState.error || "未知"}`);
404
433
  logger.error(`[AutoRegister] ========================================`);
405
434
  logger.error(`[AutoRegister] 配置来源:~/.aws-bridge/config.json 或环境变量`);
406
435
  return false;
@@ -408,9 +437,64 @@ export async function autoRegister(customConfig) {
408
437
  /**
409
438
  * 注销实例
410
439
  */
440
+ export async function requestRuntimeAccessTokenRefresh() {
441
+ const config = loadConfig();
442
+ if (!config.userKey) {
443
+ return {
444
+ success: false,
445
+ error: "userKey is required for runtime token refresh",
446
+ };
447
+ }
448
+ const state = getRuntimeBindingPublicState();
449
+ const localIp = getLocalIpAddress();
450
+ const runtimeBridgePort = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
451
+ const runtimeBridgeBaseUrl = `http://${config.registerIp || config.virtualIp || localIp}:${runtimeBridgePort}`;
452
+ try {
453
+ const response = await axios.post(`${schedulerBaseUrl}/api/instances/runtime-tokens/refresh`, {
454
+ tenantId: config.tenantId,
455
+ userKey: config.userKey,
456
+ instanceId: state.instanceId,
457
+ instanceName: config.instanceName,
458
+ runtimeBridgeBaseUrl,
459
+ }, {
460
+ headers: {
461
+ "Content-Type": "application/json",
462
+ "X-Runtime-Token": runtimeToken,
463
+ },
464
+ timeout: 10000,
465
+ });
466
+ if (!response.data.success || !response.data.runtimeAccessToken) {
467
+ return {
468
+ success: false,
469
+ error: response.data.message ||
470
+ "scheduler did not return runtimeAccessToken",
471
+ };
472
+ }
473
+ const previousToken = getRuntimeAccessToken();
474
+ const updated = previousToken !== response.data.runtimeAccessToken;
475
+ saveRuntimeBinding({
476
+ accessToken: response.data.runtimeAccessToken,
477
+ instanceId: state.instanceId,
478
+ userId: response.data.userId || config.userKey,
479
+ schedulerBaseUrl,
480
+ });
481
+ return {
482
+ success: true,
483
+ runtimeAccessToken: response.data.runtimeAccessToken,
484
+ updated,
485
+ };
486
+ }
487
+ catch (error) {
488
+ const err = error;
489
+ const message = err.response?.data
490
+ ? JSON.stringify(err.response.data)
491
+ : err.message;
492
+ return { success: false, error: message };
493
+ }
494
+ }
411
495
  export async function unregister() {
412
496
  if (!registrationState.registered || !registrationState.instanceId) {
413
- logger.info('[AutoRegister] 实例未注册,无需注销');
497
+ logger.info("[AutoRegister] 实例未注册,无需注销");
414
498
  return true;
415
499
  }
416
500
  try {
@@ -474,20 +558,20 @@ export async function bridgeRestartCleanup() {
474
558
  if (config.enabled) {
475
559
  const localIp = getLocalIpAddress();
476
560
  const port = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
477
- requestBody.runtimeBridgeBaseUrl = `http://${config.virtualIp || localIp}:${port}`;
561
+ requestBody.runtimeBridgeBaseUrl = `http://${config.registerIp || config.virtualIp || localIp}:${port}`;
478
562
  }
479
563
  if (!requestBody.instanceId && !requestBody.runtimeBridgeBaseUrl) {
480
- logger.info('[AutoRegister] Bridge 重启清理:没有 instanceId 或 runtimeBridgeBaseUrl,跳过清理');
564
+ logger.info("[AutoRegister] Bridge 重启清理:没有 instanceId 或 runtimeBridgeBaseUrl,跳过清理");
481
565
  return { success: true, agentCount: 0 };
482
566
  }
483
567
  try {
484
568
  logger.info(`[AutoRegister] Bridge 重启清理:正在通知调度中心...`);
485
- logger.info(`[AutoRegister] instanceId: ${requestBody.instanceId || '(无)'}`);
486
- logger.info(`[AutoRegister] runtimeBridgeBaseUrl: ${requestBody.runtimeBridgeBaseUrl || '(无)'}`);
569
+ logger.info(`[AutoRegister] instanceId: ${requestBody.instanceId || "(无)"}`);
570
+ logger.info(`[AutoRegister] runtimeBridgeBaseUrl: ${requestBody.runtimeBridgeBaseUrl || "(无)"}`);
487
571
  const response = await axios.post(`${schedulerBaseUrl}/api/instances/bridge-restart-cleanup`, requestBody, {
488
572
  headers: {
489
- 'Content-Type': 'application/json',
490
- 'X-Runtime-Token': runtimeToken,
573
+ "Content-Type": "application/json",
574
+ "X-Runtime-Token": runtimeToken,
491
575
  },
492
576
  timeout: 10000,
493
577
  });
@@ -503,7 +587,9 @@ export async function bridgeRestartCleanup() {
503
587
  }
504
588
  catch (error) {
505
589
  const err = error;
506
- const message = err.response?.data ? JSON.stringify(err.response.data) : err.message;
590
+ const message = err.response?.data
591
+ ? JSON.stringify(err.response.data)
592
+ : err.message;
507
593
  logger.warn(`[AutoRegister] Bridge 重启清理请求失败:${message}`);
508
594
  return { success: false, error: message };
509
595
  }
@@ -15,7 +15,7 @@ export interface ResolveAwsClientAgentMcpCommandOptions {
15
15
  release?: () => string | null;
16
16
  }
17
17
  export interface AwsMcpPreparedInfo extends AwsMcpCommandSpec {
18
- source: 'override' | 'bundled' | 'global';
18
+ source: "override" | "bundled" | "global";
19
19
  }
20
20
  export declare function getReleasedMcpDistPath(): string;
21
21
  export declare function releaseBundledAwsClientAgentMcp(): string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-agent-mcp.d.ts","sourceRoot":"","sources":["../../src/services/aws-client-agent-mcp.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAqB7C,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,sCAAsC;IACrD,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAeD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAMD,wBAAgB,+BAA+B,IAAI,MAAM,GAAG,IAAI,CAoB/D;AA6CD,wBAAgB,+BAA+B,CAAC,OAAO,GAAE,sCAA2C,GAAG,kBAAkB,CAExH;AAED,wBAAgB,gCAAgC,CAAC,OAAO,GAAE,sCAA2C,GAAG,kBAAkB,CAuBzH;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB,GAAG,kBAAkB,CAe1F;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAUtD"}
1
+ {"version":3,"file":"aws-client-agent-mcp.d.ts","sourceRoot":"","sources":["../../src/services/aws-client-agent-mcp.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAuB7C,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,sCAAsC;IACrD,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAqBD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAMD,wBAAgB,+BAA+B,IAAI,MAAM,GAAG,IAAI,CAsB/D;AA2ED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAEpB;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CA+BpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,GAC7B,kBAAkB,CAwBpB;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAgBtD"}