aws-runtime-bridge 1.2.0 → 1.3.0
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 +77 -77
- package/dist/adapter/ClaudeSdkAdapter.d.ts +1 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +7 -3
- package/dist/adapter/ClaudeSdkAdapter.test.js +2 -2
- package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/CodexSdkAdapter.js +7 -4
- package/dist/adapter/CodexSdkAdapter.test.js +4 -2
- package/dist/adapter/OpencodeSdkAdapter.d.ts +2 -0
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +15 -1
- package/dist/adapter/OpencodeSdkAdapter.test.js +5 -0
- package/dist/index.js +0 -0
- package/dist/routes/properties.test.js +4 -4
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +8 -13
- package/dist/routes/runtime-mcp-proxy.d.ts +3 -0
- package/dist/routes/runtime-mcp-proxy.d.ts.map +1 -0
- package/dist/routes/runtime-mcp-proxy.js +102 -0
- package/dist/routes/runtime-mcp-proxy.test.d.ts +2 -0
- package/dist/routes/runtime-mcp-proxy.test.d.ts.map +1 -0
- package/dist/routes/runtime-mcp-proxy.test.js +111 -0
- package/dist/routes/terminal.js +2 -5
- package/dist/routes/terminal.test.js +3 -4
- package/dist/services/auto-register.d.ts +6 -0
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +63 -1
- package/dist/services/aws-client-agent-mcp.d.ts.map +1 -1
- package/dist/services/aws-client-agent-mcp.js +73 -7
- package/dist/services/aws-client-agent-mcp.test.js +83 -2
- package/dist/services/mcp-launch-binding-queue.d.ts +0 -2
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
- package/dist/services/mcp-launch-binding-queue.js +44 -16
- package/dist/services/mcp-launch-binding-queue.test.js +42 -37
- package/dist/services/runtime-binding.d.ts +1 -0
- package/dist/services/runtime-binding.d.ts.map +1 -1
- package/dist/services/runtime-binding.js +39 -5
- package/dist/services/runtime-binding.test.d.ts +2 -0
- package/dist/services/runtime-binding.test.d.ts.map +1 -0
- package/dist/services/runtime-binding.test.js +11 -0
- package/dist/utils/yaml-utils.test.js +129 -129
- package/node_modules/@cc-switch/sdk/README.md +540 -540
- package/node_modules/@cc-switch/sdk/dist/sdk-import.test.d.ts +2 -0
- package/node_modules/@cc-switch/sdk/dist/sdk-import.test.d.ts.map +1 -0
- package/node_modules/@cc-switch/sdk/dist/sdk-import.test.js +119 -0
- package/node_modules/@cc-switch/sdk/package.json +31 -31
- package/package/aws-client-agent-mcp/README.md +288 -288
- package/package/aws-client-agent-mcp/dist/config.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/config.js +96 -13
- package/package/aws-client-agent-mcp/dist/config.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/config.test.js +26 -8
- package/package/aws-client-agent-mcp/dist/config.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/constants.js +0 -1
- package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +49 -13
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js +40 -13
- package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/index.js +11 -6
- package/package/aws-client-agent-mcp/dist/index.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/logger.d.ts +11 -1
- package/package/aws-client-agent-mcp/dist/logger.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/logger.js +91 -6
- package/package/aws-client-agent-mcp/dist/logger.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/logger.test.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/logger.test.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/logger.test.js +27 -0
- package/package/aws-client-agent-mcp/dist/logger.test.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +18 -14
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +51 -21
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +3 -2
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +1 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +18 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +53 -2
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/package.json +52 -52
- package/package/cc-switch-sdk/README.md +540 -540
- package/package/cc-switch-sdk/dist/sdk-import.test.d.ts +2 -0
- package/package/cc-switch-sdk/dist/sdk-import.test.d.ts.map +1 -0
- package/package/cc-switch-sdk/dist/sdk-import.test.js +119 -0
- package/package/cc-switch-sdk/package.json +31 -31
- package/package.json +78 -78
package/dist/routes/terminal.js
CHANGED
|
@@ -80,7 +80,7 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
|
|
|
80
80
|
});
|
|
81
81
|
// SDK 模式
|
|
82
82
|
if (mode === 'sdk') {
|
|
83
|
-
await startSdkSession(req, res
|
|
83
|
+
await startSdkSession(req, res);
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
86
|
// PTY 模式(原有逻辑)
|
|
@@ -110,7 +110,6 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
|
|
|
110
110
|
env: {
|
|
111
111
|
...process.env,
|
|
112
112
|
AWS_AGENT_ID: String(agentId),
|
|
113
|
-
AWS_MCP_LAUNCH_BINDING_ID: queuedBinding?.id || '',
|
|
114
113
|
AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
|
|
115
114
|
},
|
|
116
115
|
});
|
|
@@ -176,14 +175,13 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
|
|
|
176
175
|
workspacePath,
|
|
177
176
|
command: actualCommand,
|
|
178
177
|
mode: 'pty',
|
|
179
|
-
mcpLaunchBindingId: queuedBinding?.id,
|
|
180
178
|
});
|
|
181
179
|
});
|
|
182
180
|
/**
|
|
183
181
|
* 启动 SDK 会话
|
|
184
182
|
* 支持 MCP 配置和空闲命令
|
|
185
183
|
*/
|
|
186
|
-
async function startSdkSession(req, res
|
|
184
|
+
async function startSdkSession(req, res) {
|
|
187
185
|
const { agentId, workspacePath, command, autoAccept = true, initialPrompt,
|
|
188
186
|
// MCP 配置 - 加载 aws-client-agent-mcp
|
|
189
187
|
mcpConfigPath, extraMcpServers,
|
|
@@ -213,7 +211,6 @@ async function startSdkSession(req, res, mcpLaunchBindingId) {
|
|
|
213
211
|
envOverrides: {
|
|
214
212
|
AWS_AGENT_ID: String(agentId),
|
|
215
213
|
AWS_WORKSPACE_PATH: String(workspacePath),
|
|
216
|
-
AWS_MCP_LAUNCH_BINDING_ID: mcpLaunchBindingId || '',
|
|
217
214
|
AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
|
|
218
215
|
},
|
|
219
216
|
};
|
|
@@ -56,15 +56,14 @@ describe('terminal configuration', () => {
|
|
|
56
56
|
expect(getShellConfig('linux').shell).toBe('bash');
|
|
57
57
|
});
|
|
58
58
|
it('builds correct terminal environment', () => {
|
|
59
|
-
const buildTerminalEnv = (agentId,
|
|
59
|
+
const buildTerminalEnv = (agentId, baseEnv) => ({
|
|
60
60
|
...baseEnv,
|
|
61
61
|
AWS_AGENT_ID: agentId,
|
|
62
|
-
AWS_MCP_LAUNCH_BINDING_ID: bindingId,
|
|
63
62
|
AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
|
|
64
63
|
});
|
|
65
|
-
const env = buildTerminalEnv('agent-123',
|
|
64
|
+
const env = buildTerminalEnv('agent-123', { PATH: '/usr/bin' });
|
|
66
65
|
expect(env.AWS_AGENT_ID).toBe('agent-123');
|
|
67
|
-
expect(env.AWS_MCP_LAUNCH_BINDING_ID).
|
|
66
|
+
expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBeUndefined();
|
|
68
67
|
expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe('true');
|
|
69
68
|
});
|
|
70
69
|
});
|
|
@@ -75,6 +75,12 @@ export declare function requestRuntimeAccessTokenRefresh(): Promise<{
|
|
|
75
75
|
updated?: boolean;
|
|
76
76
|
error?: string;
|
|
77
77
|
}>;
|
|
78
|
+
export declare function requestRuntimeAccessTokenRefreshForServer(serverBaseUrl: string): Promise<{
|
|
79
|
+
success: boolean;
|
|
80
|
+
runtimeAccessToken?: string;
|
|
81
|
+
updated?: boolean;
|
|
82
|
+
error?: string;
|
|
83
|
+
}>;
|
|
78
84
|
export declare function unregister(): Promise<boolean>;
|
|
79
85
|
/**
|
|
80
86
|
* 获取注册状态
|
|
@@ -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;AAiDH;;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;AAiED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAGtD;AAyJD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CA2C/C;AAED,wBAAgB,WAAW,IAAI,kBAAkB,EAAE,CA0ClD;AA4OD;;;;;;;;;;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,CAqED;AAED,wBAAsB,yCAAyC,CAC7D,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA2ED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBA14BtB,OAAO;iBACN,MAAM;mBACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;kBACvB,IAAI;YACV,MAAM;EAw4Bf;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,CA2ED"}
|
|
@@ -13,7 +13,7 @@ import path from "node:path";
|
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import { logger } from "../utils/logger.js";
|
|
15
15
|
import { schedulerBaseUrl, runtimeToken } from "../config.js";
|
|
16
|
-
import { getRuntimeAccessToken, getRuntimeBindingPublicState, loadRuntimeBinding, saveRuntimeBinding, } from "./runtime-binding.js";
|
|
16
|
+
import { getRuntimeAccessToken, getRuntimeBindingPublicState, loadRuntimeBinding, normalizeSchedulerBaseUrl, saveRuntimeBinding, saveScopedRuntimeAccessToken, } from "./runtime-binding.js";
|
|
17
17
|
// 默认配置
|
|
18
18
|
const DEFAULT_CONFIG = {
|
|
19
19
|
enabled: false,
|
|
@@ -37,6 +37,9 @@ function normalizeOptionalString(value) {
|
|
|
37
37
|
const normalized = String(value).trim();
|
|
38
38
|
return normalized || undefined;
|
|
39
39
|
}
|
|
40
|
+
function normalizeSchedulerHttpBaseUrl(value) {
|
|
41
|
+
return normalizeSchedulerBaseUrl(value);
|
|
42
|
+
}
|
|
40
43
|
function normalizeOptionalBoolean(value) {
|
|
41
44
|
if (typeof value === "boolean") {
|
|
42
45
|
return value;
|
|
@@ -649,6 +652,65 @@ export async function requestRuntimeAccessTokenRefresh() {
|
|
|
649
652
|
return { success: false, error: message };
|
|
650
653
|
}
|
|
651
654
|
}
|
|
655
|
+
export async function requestRuntimeAccessTokenRefreshForServer(serverBaseUrl) {
|
|
656
|
+
const targetServerUrl = normalizeSchedulerHttpBaseUrl(serverBaseUrl);
|
|
657
|
+
if (!targetServerUrl) {
|
|
658
|
+
return { success: false, error: "serverUrl is required for runtime token refresh" };
|
|
659
|
+
}
|
|
660
|
+
const config = loadConfigs().find((item) => normalizeSchedulerHttpBaseUrl(item.serverUrl) === targetServerUrl) || loadPrimaryLifecycleConfig();
|
|
661
|
+
if (!config.userKey) {
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
error: "userKey is required for runtime token refresh",
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
const state = getRuntimeBindingPublicState();
|
|
668
|
+
const localIp = getLocalIpAddress(targetServerUrl);
|
|
669
|
+
const runtimeBridgePort = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
|
|
670
|
+
const runtimeBridgeBaseUrl = `http://${config.registerIp || config.virtualIp || localIp}:${runtimeBridgePort}`;
|
|
671
|
+
try {
|
|
672
|
+
const response = await axios.post(`${targetServerUrl}/api/instances/runtime-tokens/refresh`, {
|
|
673
|
+
tenantId: config.tenantId,
|
|
674
|
+
userKey: config.userKey,
|
|
675
|
+
instanceId: state.instanceId,
|
|
676
|
+
instanceName: config.instanceName,
|
|
677
|
+
runtimeBridgeBaseUrl,
|
|
678
|
+
}, {
|
|
679
|
+
headers: {
|
|
680
|
+
"Content-Type": "application/json",
|
|
681
|
+
"X-Runtime-Token": runtimeToken,
|
|
682
|
+
},
|
|
683
|
+
timeout: 10000,
|
|
684
|
+
});
|
|
685
|
+
if (!response.data.success || !response.data.runtimeAccessToken) {
|
|
686
|
+
return {
|
|
687
|
+
success: false,
|
|
688
|
+
error: response.data.message ||
|
|
689
|
+
"scheduler did not return runtimeAccessToken",
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const userId = response.data.userId || config.userKey;
|
|
693
|
+
const previousToken = getRuntimeAccessToken(userId, targetServerUrl);
|
|
694
|
+
const updated = previousToken !== response.data.runtimeAccessToken;
|
|
695
|
+
saveScopedRuntimeAccessToken({
|
|
696
|
+
userId,
|
|
697
|
+
serverBaseUrl: targetServerUrl,
|
|
698
|
+
accessToken: response.data.runtimeAccessToken,
|
|
699
|
+
});
|
|
700
|
+
return {
|
|
701
|
+
success: true,
|
|
702
|
+
runtimeAccessToken: response.data.runtimeAccessToken,
|
|
703
|
+
updated,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
const err = error;
|
|
708
|
+
const message = err.response?.data
|
|
709
|
+
? JSON.stringify(err.response.data)
|
|
710
|
+
: err.message;
|
|
711
|
+
return { success: false, error: message };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
652
714
|
export async function unregister() {
|
|
653
715
|
if (!registrationState.registered || !registrationState.instanceId) {
|
|
654
716
|
logger.info("[AutoRegister] 实例未注册,无需注销");
|
|
@@ -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":"AAyBA,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAsB7C,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,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAaD,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAuBD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAgLD,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,MAAM,GAAG,IAAI,CAoDf;AA2ED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAEpB;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAsBpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,GAC7B,kBAAkB,CA0BpB;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAgBtD"}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { getRuntimeHomeDir, port, schedulerBaseUrl } from "../config.js";
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
|
-
import { getRuntimeAccessToken, loadRuntimeBinding, } from "./runtime-binding.js";
|
|
7
|
+
import { getRuntimeAccessToken, loadRuntimeBinding, normalizeSchedulerBaseUrl, } from "./runtime-binding.js";
|
|
8
8
|
export const AWS_MCP_SERVER_NAME = "aws-mcp";
|
|
9
9
|
const AWS_MCP_ALLOWED_ENV_KEYS = [
|
|
10
|
-
"AWS_INTERNAL_API_KEY",
|
|
11
10
|
"AWS_PROJECT_NAME",
|
|
12
11
|
"AWS_PROMPT",
|
|
13
12
|
"AWS_ROLE_NAME",
|
|
@@ -32,7 +31,10 @@ function getPackageRoot() {
|
|
|
32
31
|
return path.resolve(path.dirname(currentFile), "..", "..");
|
|
33
32
|
}
|
|
34
33
|
function getBundledEntryPath(packageRoot = getPackageRoot()) {
|
|
35
|
-
return path.join(packageRoot, "
|
|
34
|
+
return path.join(getBundledMcpRoot(packageRoot), "dist", "index.js");
|
|
35
|
+
}
|
|
36
|
+
function getBundledMcpRoot(packageRoot) {
|
|
37
|
+
return path.join(packageRoot, "package", "aws-client-agent-mcp");
|
|
36
38
|
}
|
|
37
39
|
function getReleasedMcpRoot() {
|
|
38
40
|
return path.join(getRuntimeHomeDir(), ".aws-bridge", "mcp");
|
|
@@ -56,7 +58,66 @@ function getBridgeVersion(packageRoot) {
|
|
|
56
58
|
return readPackageVersion(path.join(packageRoot, "package.json"));
|
|
57
59
|
}
|
|
58
60
|
function getBundledMcpVersion(packageRoot) {
|
|
59
|
-
return readPackageVersion(path.join(packageRoot, "package
|
|
61
|
+
return readPackageVersion(path.join(getBundledMcpRoot(packageRoot), "package.json"));
|
|
62
|
+
}
|
|
63
|
+
function copyIfExists(sourcePath, targetPath) {
|
|
64
|
+
if (!existsSync(sourcePath)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
copyFileOrDirectory(sourcePath, targetPath);
|
|
68
|
+
}
|
|
69
|
+
function copyFileOrDirectory(sourcePath, targetPath) {
|
|
70
|
+
const stat = statSync(sourcePath);
|
|
71
|
+
if (stat.isDirectory()) {
|
|
72
|
+
cpSync(sourcePath, targetPath, {
|
|
73
|
+
errorOnExist: false,
|
|
74
|
+
force: true,
|
|
75
|
+
recursive: true,
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
cpSync(sourcePath, targetPath, { errorOnExist: false, force: true });
|
|
80
|
+
}
|
|
81
|
+
function syncReleasedPackageMetadata(packageRoot, releasedRoot) {
|
|
82
|
+
const bundledMcpRoot = getBundledMcpRoot(packageRoot);
|
|
83
|
+
copyIfExists(path.join(bundledMcpRoot, "package.json"), path.join(releasedRoot, "package.json"));
|
|
84
|
+
copyIfExists(path.join(bundledMcpRoot, "README.md"), path.join(releasedRoot, "README.md"));
|
|
85
|
+
copyIfExists(path.join(bundledMcpRoot, "package-lock.json"), path.join(releasedRoot, "package-lock.json"));
|
|
86
|
+
}
|
|
87
|
+
function shouldReplaceNodeModulesLink(targetPath) {
|
|
88
|
+
if (!existsSync(targetPath)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
const stat = lstatSync(targetPath);
|
|
92
|
+
return stat.isSymbolicLink() || stat.isDirectory();
|
|
93
|
+
}
|
|
94
|
+
function isExpectedNodeModulesLink(targetPath, sourcePath) {
|
|
95
|
+
if (!existsSync(targetPath)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return realpathSync(targetPath) === realpathSync(sourcePath);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function ensureReleasedNodeModules(packageRoot, releasedRoot) {
|
|
106
|
+
const sourceNodeModules = path.join(packageRoot, "node_modules");
|
|
107
|
+
const releasedNodeModules = path.join(releasedRoot, "node_modules");
|
|
108
|
+
if (!existsSync(sourceNodeModules)) {
|
|
109
|
+
logger.warn(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 未找到依赖目录,release 产物可能无法解析运行时依赖: ${sourceNodeModules}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (isExpectedNodeModulesLink(releasedNodeModules, sourceNodeModules)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!shouldReplaceNodeModulesLink(releasedNodeModules)) {
|
|
116
|
+
logger.warn(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 依赖路径已存在但不是目录或链接,已保留: ${releasedNodeModules}`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
rmSync(releasedNodeModules, { recursive: true, force: true });
|
|
120
|
+
symlinkSync(sourceNodeModules, releasedNodeModules, process.platform === "win32" ? "junction" : "dir");
|
|
60
121
|
}
|
|
61
122
|
function listDistFiles(dirPath, rootPath = dirPath) {
|
|
62
123
|
return readdirSync(dirPath, { withFileTypes: true }).flatMap((entry) => {
|
|
@@ -128,6 +189,8 @@ export function releaseBundledAwsClientAgentMcp(options = {}) {
|
|
|
128
189
|
if (shouldRefreshReleasedMcp(releasedEntry, currentManifest, nextManifest)) {
|
|
129
190
|
rmSync(releasedDist, { recursive: true, force: true });
|
|
130
191
|
mkdirSync(releasedRoot, { recursive: true });
|
|
192
|
+
syncReleasedPackageMetadata(packageRoot, releasedRoot);
|
|
193
|
+
ensureReleasedNodeModules(packageRoot, releasedRoot);
|
|
131
194
|
cpSync(bundledDist, releasedDist, {
|
|
132
195
|
errorOnExist: false,
|
|
133
196
|
force: true,
|
|
@@ -140,6 +203,8 @@ export function releaseBundledAwsClientAgentMcp(options = {}) {
|
|
|
140
203
|
logger.info(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 已更新到: ${releasedDist}`);
|
|
141
204
|
}
|
|
142
205
|
else {
|
|
206
|
+
syncReleasedPackageMetadata(packageRoot, releasedRoot);
|
|
207
|
+
ensureReleasedNodeModules(packageRoot, releasedRoot);
|
|
143
208
|
logger.info(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 已是最新: ${releasedDist}`);
|
|
144
209
|
}
|
|
145
210
|
return releasedEntry;
|
|
@@ -170,7 +235,7 @@ function parseAwsClientAgentMcpArgs(raw) {
|
|
|
170
235
|
return [];
|
|
171
236
|
}
|
|
172
237
|
function toWebSocketUrl(baseUrl) {
|
|
173
|
-
const url = new URL("/ws/agent", baseUrl);
|
|
238
|
+
const url = new URL("/ws/agent", normalizeSchedulerBaseUrl(baseUrl) || baseUrl);
|
|
174
239
|
if (url.protocol === "https:") {
|
|
175
240
|
url.protocol = "wss:";
|
|
176
241
|
}
|
|
@@ -180,7 +245,7 @@ function toWebSocketUrl(baseUrl) {
|
|
|
180
245
|
return url.toString();
|
|
181
246
|
}
|
|
182
247
|
function toMcpHttpUrl(baseUrl) {
|
|
183
|
-
return new URL("/mcp/call", baseUrl).toString();
|
|
248
|
+
return new URL("/mcp/call", normalizeSchedulerBaseUrl(baseUrl) || baseUrl).toString();
|
|
184
249
|
}
|
|
185
250
|
function resolveSchedulerBaseUrlForMcp() {
|
|
186
251
|
const envSchedulerBaseUrl = String(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL || "").trim();
|
|
@@ -231,6 +296,7 @@ export function buildAwsMcpServerConfig(input) {
|
|
|
231
296
|
AWS_SERVER_URL: env.AWS_SERVER_URL || toWebSocketUrl(effectiveSchedulerBaseUrl),
|
|
232
297
|
AWS_MCP_HTTP_URL: env.AWS_MCP_HTTP_URL || toMcpHttpUrl(effectiveSchedulerBaseUrl),
|
|
233
298
|
AWS_RUNTIME_BRIDGE_BASE_URL: env.AWS_RUNTIME_BRIDGE_BASE_URL || `http://127.0.0.1:${port}`,
|
|
299
|
+
AWS_MCP_CLAIM_LAUNCH_BINDING: env.AWS_MCP_CLAIM_LAUNCH_BINDING || "true",
|
|
234
300
|
...(issuedRuntimeAccessToken
|
|
235
301
|
? { AWS_RUNTIME_ACCESS_TOKEN: issuedRuntimeAccessToken }
|
|
236
302
|
: {}),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -13,7 +14,31 @@ function createBundledMcpPackage(version = '1.0.0') {
|
|
|
13
14
|
const bundledDist = path.join(packageRoot, 'package', 'aws-client-agent-mcp', 'dist');
|
|
14
15
|
mkdirSync(bundledDist, { recursive: true });
|
|
15
16
|
writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: 'aws-runtime-bridge', version: '9.9.9' }), 'utf-8');
|
|
16
|
-
writeFileSync(path.join(packageRoot, 'package', 'aws-client-agent-mcp', 'package.json'), JSON.stringify({
|
|
17
|
+
writeFileSync(path.join(packageRoot, 'package', 'aws-client-agent-mcp', 'package.json'), JSON.stringify({
|
|
18
|
+
name: 'aws-client-agent-mcp',
|
|
19
|
+
version,
|
|
20
|
+
type: 'module',
|
|
21
|
+
main: 'dist/index.js',
|
|
22
|
+
dependencies: {
|
|
23
|
+
'@modelcontextprotocol/sdk': '^1.27.1',
|
|
24
|
+
ws: '^8.14.2',
|
|
25
|
+
yaml: '^2.3.4',
|
|
26
|
+
},
|
|
27
|
+
}), 'utf-8');
|
|
28
|
+
writeFileSync(path.join(packageRoot, 'package', 'aws-client-agent-mcp', 'README.md'), '# aws-client-agent-mcp\n', 'utf-8');
|
|
29
|
+
mkdirSync(path.join(packageRoot, 'node_modules', '@modelcontextprotocol', 'sdk'), {
|
|
30
|
+
recursive: true,
|
|
31
|
+
});
|
|
32
|
+
writeFileSync(path.join(packageRoot, 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json'), JSON.stringify({
|
|
33
|
+
name: '@modelcontextprotocol/sdk',
|
|
34
|
+
version: '1.27.1',
|
|
35
|
+
type: 'module',
|
|
36
|
+
main: 'index.js',
|
|
37
|
+
exports: './index.js',
|
|
38
|
+
}), 'utf-8');
|
|
39
|
+
writeFileSync(path.join(packageRoot, 'node_modules', '@modelcontextprotocol', 'sdk', 'index.js'), 'export const sdkMarker = "resolved-sdk";\n', 'utf-8');
|
|
40
|
+
mkdirSync(path.join(packageRoot, 'node_modules', 'ws'), { recursive: true });
|
|
41
|
+
mkdirSync(path.join(packageRoot, 'node_modules', 'yaml'), { recursive: true });
|
|
17
42
|
writeFileSync(path.join(bundledDist, 'index.js'), 'console.log("mcp v1");\n', 'utf-8');
|
|
18
43
|
return packageRoot;
|
|
19
44
|
}
|
|
@@ -56,8 +81,21 @@ describe('aws-client-agent-mcp service', () => {
|
|
|
56
81
|
});
|
|
57
82
|
const releasedIndex = path.join(releasedRoot, 'dist', 'index.js');
|
|
58
83
|
const manifestPath = path.join(releasedRoot, '.release.json');
|
|
84
|
+
const releasedPackageJson = path.join(releasedRoot, 'package.json');
|
|
85
|
+
const releasedNodeModules = path.join(releasedRoot, 'node_modules');
|
|
59
86
|
expect(firstRelease).toBe(releasedIndex);
|
|
60
87
|
expect(existsSync(releasedIndex)).toBe(true);
|
|
88
|
+
expect(JSON.parse(readFileSync(releasedPackageJson, 'utf-8'))).toMatchObject({
|
|
89
|
+
name: 'aws-client-agent-mcp',
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
dependencies: {
|
|
92
|
+
'@modelcontextprotocol/sdk': '^1.27.1',
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
expect(readFileSync(path.join(releasedRoot, 'README.md'), 'utf-8')).toBe('# aws-client-agent-mcp\n');
|
|
96
|
+
expect(lstatSync(releasedNodeModules).isSymbolicLink()).toBe(true);
|
|
97
|
+
expect(realpathSync(releasedNodeModules)).toBe(realpathSync(path.join(packageRoot, 'node_modules')));
|
|
98
|
+
expect(path.resolve(readlinkSync(releasedNodeModules))).toBe(path.join(packageRoot, 'node_modules'));
|
|
61
99
|
const firstManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
62
100
|
expect(firstManifest).toMatchObject({
|
|
63
101
|
bridgeVersion: '9.9.9',
|
|
@@ -66,12 +104,15 @@ describe('aws-client-agent-mcp service', () => {
|
|
|
66
104
|
});
|
|
67
105
|
expect(firstManifest.distHash).toMatch(/^sha256:/);
|
|
68
106
|
writeFileSync(releasedIndex, 'console.log("local marker");\n', 'utf-8');
|
|
107
|
+
rmSync(releasedNodeModules, { recursive: true, force: true });
|
|
108
|
+
mkdirSync(releasedNodeModules, { recursive: true });
|
|
69
109
|
mod.releaseBundledAwsClientAgentMcp({
|
|
70
110
|
packageRoot,
|
|
71
111
|
releasedRoot,
|
|
72
112
|
now: () => '2026-05-05T00:00:00.000Z',
|
|
73
113
|
});
|
|
74
114
|
expect(readFileSync(releasedIndex, 'utf-8')).toBe('console.log("local marker");\n');
|
|
115
|
+
expect(realpathSync(releasedNodeModules)).toBe(realpathSync(path.join(packageRoot, 'node_modules')));
|
|
75
116
|
expect(JSON.parse(readFileSync(manifestPath, 'utf-8')).releasedAt).toBe('2026-05-04T00:00:00.000Z');
|
|
76
117
|
}
|
|
77
118
|
finally {
|
|
@@ -102,6 +143,32 @@ describe('aws-client-agent-mcp service', () => {
|
|
|
102
143
|
rmSync(releasedRoot, { recursive: true, force: true });
|
|
103
144
|
}
|
|
104
145
|
});
|
|
146
|
+
it('releases a runnable package root that resolves bundled mcp runtime dependencies', async () => {
|
|
147
|
+
process.env.AWS_CLIENT_AGENT_MCP_COMMAND = '';
|
|
148
|
+
const packageRoot = createBundledMcpPackage('1.0.0');
|
|
149
|
+
const releasedRoot = mkdtempSync(path.join(os.tmpdir(), 'aws-mcp-release-'));
|
|
150
|
+
try {
|
|
151
|
+
const bundledIndex = path.join(packageRoot, 'package', 'aws-client-agent-mcp', 'dist', 'index.js');
|
|
152
|
+
writeFileSync(bundledIndex, 'import { sdkMarker } from "@modelcontextprotocol/sdk";\nconsole.log(sdkMarker);\n', 'utf-8');
|
|
153
|
+
const mod = await import('./aws-client-agent-mcp.js');
|
|
154
|
+
const releasedEntry = mod.releaseBundledAwsClientAgentMcp({ packageRoot, releasedRoot });
|
|
155
|
+
expect(releasedEntry).toBe(path.join(releasedRoot, 'dist', 'index.js'));
|
|
156
|
+
if (!releasedEntry) {
|
|
157
|
+
throw new Error('Expected bundled MCP release entry to exist');
|
|
158
|
+
}
|
|
159
|
+
const result = spawnSync(process.execPath, [releasedEntry], {
|
|
160
|
+
cwd: releasedRoot,
|
|
161
|
+
encoding: 'utf-8',
|
|
162
|
+
});
|
|
163
|
+
expect(result.stderr).toBe('');
|
|
164
|
+
expect(result.status).toBe(0);
|
|
165
|
+
expect(result.stdout.trim()).toBe('resolved-sdk');
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
rmSync(packageRoot, { recursive: true, force: true });
|
|
169
|
+
rmSync(releasedRoot, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
105
172
|
it('releases bundled mcp into home directory when runtime dist is missing', async () => {
|
|
106
173
|
process.env.AWS_CLIENT_AGENT_MCP_COMMAND = '';
|
|
107
174
|
const mod = await import('./aws-client-agent-mcp.js');
|
|
@@ -149,6 +216,20 @@ describe('aws-client-agent-mcp service', () => {
|
|
|
149
216
|
expect(config.env.AWS_RUNTIME_CALLBACK_TOKEN).toBeUndefined();
|
|
150
217
|
expect(config.env.CUSTOM_SECRET).toBeUndefined();
|
|
151
218
|
});
|
|
219
|
+
it('repairs duplicated scheduler ports when building MCP URLs', async () => {
|
|
220
|
+
process.env.AWS_CLIENT_AGENT_MCP_COMMAND = 'aws-client-agent-mcp';
|
|
221
|
+
process.env.AWS_RUNTIME_HOME_DIR = mkdtempSync(path.join(os.tmpdir(), 'aws-mcp-config-'));
|
|
222
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://127.0.0.1:8080:8080';
|
|
223
|
+
process.env.AWS_SERVER_URL = '';
|
|
224
|
+
process.env.AWS_MCP_HTTP_URL = '';
|
|
225
|
+
const mod = await import('./aws-client-agent-mcp.js');
|
|
226
|
+
const config = mod.buildAwsMcpServerConfig({
|
|
227
|
+
agentId: 'agent-1',
|
|
228
|
+
workspacePath: '/workspace/demo',
|
|
229
|
+
});
|
|
230
|
+
expect(config.env.AWS_SERVER_URL).toBe('ws://127.0.0.1:8080/ws/agent');
|
|
231
|
+
expect(config.env.AWS_MCP_HTTP_URL).toBe('http://127.0.0.1:8080/mcp/call');
|
|
232
|
+
});
|
|
152
233
|
it('ignores malformed AWS_CLIENT_AGENT_MCP_ARGS JSON', async () => {
|
|
153
234
|
process.env.AWS_CLIENT_AGENT_MCP_COMMAND = 'custom-aws-client-agent-mcp';
|
|
154
235
|
process.env.AWS_CLIENT_AGENT_MCP_ARGS = '{bad json';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAmDD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CA+B7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,uBAAuB,GAAG,IAAI,CA0C3G;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAOpE;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { schedulerBaseUrl as defaultSchedulerBaseUrl } from "../config.js";
|
|
3
|
+
import { getRuntimeAccessToken, loadRuntimeBinding, normalizeSchedulerBaseUrl } from "./runtime-binding.js";
|
|
4
|
+
const LAUNCH_BINDING_TTL_MS = 5 * 60 * 1000;
|
|
3
5
|
const launchBindings = [];
|
|
4
6
|
function normalizeWorkspacePath(workspacePath) {
|
|
5
7
|
const raw = String(workspacePath || "").trim();
|
|
@@ -11,57 +13,82 @@ function normalizeWorkspacePath(workspacePath) {
|
|
|
11
13
|
function normalizeOptionalUrl(value) {
|
|
12
14
|
return String(value || "").trim();
|
|
13
15
|
}
|
|
14
|
-
function
|
|
15
|
-
const raw = String(serverUrl || "").trim();
|
|
16
|
+
function normalizeSchedulerOrigin(serverUrl) {
|
|
17
|
+
const raw = normalizeSchedulerBaseUrl(serverUrl) || String(serverUrl || "").trim();
|
|
16
18
|
if (!raw) {
|
|
17
19
|
return "";
|
|
18
20
|
}
|
|
19
21
|
try {
|
|
20
22
|
const url = new URL(raw);
|
|
21
|
-
url.protocol
|
|
23
|
+
if (url.protocol === "ws:") {
|
|
24
|
+
url.protocol = "http:";
|
|
25
|
+
}
|
|
26
|
+
else if (url.protocol === "wss:") {
|
|
27
|
+
url.protocol = "https:";
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
url.protocol = url.protocol.toLowerCase();
|
|
31
|
+
}
|
|
22
32
|
url.hostname = url.hostname.toLowerCase();
|
|
23
|
-
|
|
24
|
-
return url.toString().replace(/\/+$/, "");
|
|
33
|
+
return url.origin;
|
|
25
34
|
}
|
|
26
35
|
catch {
|
|
27
36
|
return raw.replace(/\/+$/, "").toLowerCase();
|
|
28
37
|
}
|
|
29
38
|
}
|
|
39
|
+
function pruneExpiredBindings(now = Date.now()) {
|
|
40
|
+
for (let index = launchBindings.length - 1; index >= 0; index -= 1) {
|
|
41
|
+
const enqueuedAt = Date.parse(launchBindings[index]?.enqueuedAt || "");
|
|
42
|
+
if (!Number.isFinite(enqueuedAt) || now - enqueuedAt > LAUNCH_BINDING_TTL_MS) {
|
|
43
|
+
launchBindings.splice(index, 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
30
47
|
function createBindingId(agentId) {
|
|
31
48
|
return `${agentId}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
32
49
|
}
|
|
33
50
|
export function enqueueMcpLaunchBinding(input) {
|
|
51
|
+
pruneExpiredBindings();
|
|
34
52
|
const agentId = String(input.agentId || "").trim();
|
|
35
53
|
const workspaceKey = normalizeWorkspacePath(input.workspacePath);
|
|
36
54
|
if (!agentId || !workspaceKey) {
|
|
37
55
|
return null;
|
|
38
56
|
}
|
|
57
|
+
const runtimeState = loadRuntimeBinding();
|
|
58
|
+
const schedulerUrl = normalizeSchedulerBaseUrl(input.serverUrl || input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl) || normalizeOptionalUrl(input.serverUrl || input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl);
|
|
59
|
+
const schedulerBindingBaseUrl = normalizeSchedulerBaseUrl(input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl) || normalizeOptionalUrl(input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl);
|
|
39
60
|
const binding = {
|
|
40
61
|
id: createBindingId(agentId),
|
|
41
62
|
agentId,
|
|
42
63
|
workspacePath: path.resolve(String(input.workspacePath)),
|
|
43
|
-
serverUrl:
|
|
64
|
+
serverUrl: schedulerUrl,
|
|
44
65
|
mcpHttpUrl: normalizeOptionalUrl(input.mcpHttpUrl),
|
|
45
66
|
runtimeAccessToken: normalizeOptionalUrl(input.runtimeAccessToken),
|
|
46
67
|
userId: normalizeOptionalUrl(input.userId),
|
|
47
|
-
schedulerBaseUrl:
|
|
68
|
+
schedulerBaseUrl: schedulerBindingBaseUrl,
|
|
48
69
|
enqueuedAt: new Date().toISOString(),
|
|
49
70
|
};
|
|
50
71
|
launchBindings.push(binding);
|
|
51
72
|
return binding;
|
|
52
73
|
}
|
|
53
74
|
export function claimMcpLaunchBinding(request) {
|
|
54
|
-
|
|
55
|
-
const bindingId = String(request.bindingId || "").trim();
|
|
75
|
+
pruneExpiredBindings();
|
|
56
76
|
const workspaceKey = normalizeWorkspacePath(request.workspacePath);
|
|
57
|
-
const serverKey =
|
|
58
|
-
if (!
|
|
77
|
+
const serverKey = normalizeSchedulerOrigin(request.serverUrl);
|
|
78
|
+
if (!workspaceKey) {
|
|
59
79
|
return null;
|
|
60
80
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
let bindingIndex = launchBindings.findIndex((binding) => normalizeWorkspacePath(binding.workspacePath) === workspaceKey &&
|
|
82
|
+
serverKey &&
|
|
83
|
+
normalizeSchedulerOrigin(binding.serverUrl || binding.schedulerBaseUrl) === serverKey);
|
|
84
|
+
if (bindingIndex < 0) {
|
|
85
|
+
const workspaceMatches = launchBindings
|
|
86
|
+
.map((binding, index) => ({ binding, index }))
|
|
87
|
+
.filter(({ binding }) => normalizeWorkspacePath(binding.workspacePath) === workspaceKey);
|
|
88
|
+
if (workspaceMatches.length === 1) {
|
|
89
|
+
bindingIndex = workspaceMatches[0]?.index ?? -1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
65
92
|
if (bindingIndex < 0) {
|
|
66
93
|
return null;
|
|
67
94
|
}
|
|
@@ -81,6 +108,7 @@ export function claimMcpLaunchBinding(request) {
|
|
|
81
108
|
};
|
|
82
109
|
}
|
|
83
110
|
export function getMcpLaunchQueueSize(workspacePath) {
|
|
111
|
+
pruneExpiredBindings();
|
|
84
112
|
const workspaceKey = normalizeWorkspacePath(workspacePath);
|
|
85
113
|
if (!workspaceKey) {
|
|
86
114
|
return 0;
|