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.
- package/README.md +2 -3
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -38
- package/dist/index.js +33 -27
- package/dist/middleware/auth.d.ts +4 -2
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +36 -6
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +52 -34
- package/dist/routes/runtime-binding.d.ts +3 -0
- package/dist/routes/runtime-binding.d.ts.map +1 -0
- package/dist/routes/runtime-binding.js +159 -0
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +2 -7
- package/dist/routes/terminal.test.js +0 -4
- package/dist/services/auto-register.d.ts +9 -1
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +133 -47
- package/dist/services/aws-client-agent-mcp.d.ts +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts.map +1 -1
- package/dist/services/aws-client-agent-mcp.js +81 -49
- package/dist/services/runtime-binding.d.ts +42 -0
- package/dist/services/runtime-binding.d.ts.map +1 -0
- package/dist/services/runtime-binding.js +257 -0
- package/package.json +1 -1
|
@@ -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;
|
|
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"}
|
package/dist/routes/terminal.js
CHANGED
|
@@ -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
|
-
/**
|
|
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;
|
|
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
|
|
11
|
-
import os from
|
|
12
|
-
import path from
|
|
13
|
-
import fs from
|
|
14
|
-
import { logger } from
|
|
15
|
-
import { schedulerBaseUrl, runtimeToken } from
|
|
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,
|
|
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),
|
|
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
|
-
* "
|
|
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,
|
|
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 ===
|
|
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 ===
|
|
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 ||
|
|
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 ===
|
|
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(
|
|
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 ===
|
|
235
|
-
return
|
|
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(
|
|
262
|
-
return
|
|
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(
|
|
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 ?
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
326
|
-
|
|
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(
|
|
352
|
-
logger.info(
|
|
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(
|
|
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
|
|
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 ?
|
|
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(
|
|
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(
|
|
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
|
-
|
|
490
|
-
|
|
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
|
|
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:
|
|
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":"
|
|
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"}
|