aws-runtime-bridge 1.0.3 → 1.1.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/dist/adapter/adapter.test.js +4 -4
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +0 -7
- package/dist/adapter/types.test.js +5 -53
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +4 -0
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +36 -0
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +45 -0
- package/dist/routes/sessions.js +1 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +48 -14
- package/dist/routes/terminal.test.js +6 -2
- package/dist/services/agent-process-manager.js +4 -4
- package/dist/services/auto-register.d.ts +9 -0
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +190 -32
- package/dist/services/aws-client-agent-mcp.test.js +3 -0
- package/dist/services/mcp-launch-binding-queue.d.ts +36 -0
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -0
- package/dist/services/mcp-launch-binding-queue.js +92 -0
- package/dist/services/mcp-launch-binding-queue.test.d.ts +2 -0
- package/dist/services/mcp-launch-binding-queue.test.d.ts.map +1 -0
- package/dist/services/mcp-launch-binding-queue.test.js +107 -0
- package/dist/services/orphan-monitor.js +1 -1
- package/dist/services/process-detector.d.ts +1 -1
- package/dist/services/process-detector.d.ts.map +1 -1
- package/dist/services/process-detector.js +2 -11
- package/dist/services/process-registry.d.ts +1 -0
- package/dist/services/process-registry.d.ts.map +1 -1
- package/dist/services/process-registry.js +129 -108
- package/dist/services/terminal-persistence.d.ts.map +1 -1
- package/dist/services/terminal-persistence.js +47 -37
- package/dist/services/terminal-persistence.test.js +47 -1
- package/dist/utils/file-utils.d.ts +3 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +32 -0
- package/package/aws-client-agent-mcp/README.md +288 -288
- package/package.json +76 -76
- package/dist/routes/aws-mcp.d.ts +0 -10
- package/dist/routes/aws-mcp.d.ts.map +0 -1
- package/dist/routes/aws-mcp.js +0 -74
- package/dist/routes/aws-mcp.test.d.ts +0 -2
- package/dist/routes/aws-mcp.test.d.ts.map +0 -1
- package/dist/routes/aws-mcp.test.js +0 -42
- package/dist/routes/memory.d.ts +0 -13
- package/dist/routes/memory.d.ts.map +0 -1
- package/dist/routes/memory.js +0 -429
- package/dist/services/aws-mcp-http.d.ts +0 -11
- package/dist/services/aws-mcp-http.d.ts.map +0 -1
- package/dist/services/aws-mcp-http.js +0 -225
- package/dist/services/aws-mcp-http.test.d.ts +0 -2
- package/dist/services/aws-mcp-http.test.d.ts.map +0 -1
- package/dist/services/aws-mcp-http.test.js +0 -27
- package/dist/services/easytier-manager.d.ts +0 -106
- package/dist/services/easytier-manager.d.ts.map +0 -1
- package/dist/services/easytier-manager.js +0 -331
- package/dist/services/easytier-manager.test.d.ts +0 -5
- package/dist/services/easytier-manager.test.d.ts.map +0 -1
- package/dist/services/easytier-manager.test.js +0 -98
- package/dist/services/memory-service.d.ts +0 -195
- package/dist/services/memory-service.d.ts.map +0 -1
- package/dist/services/memory-service.js +0 -650
- package/dist/services/session-lookup.d.ts +0 -20
- package/dist/services/session-lookup.d.ts.map +0 -1
- package/dist/services/session-lookup.js +0 -43
- package/dist/services/user-api-key-service.d.ts +0 -28
- package/dist/services/user-api-key-service.d.ts.map +0 -1
- package/dist/services/user-api-key-service.js +0 -75
- package/node_modules/@cc-switch/sdk/dist/adapters/common.d.ts +0 -38
- package/node_modules/@cc-switch/sdk/dist/adapters/common.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/common.js +0 -47
- package/node_modules/@cc-switch/sdk/dist/adapters/index.d.ts +0 -5
- package/node_modules/@cc-switch/sdk/dist/adapters/index.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/index.js +0 -28
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claude.js +0 -39
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-claudecode.js +0 -40
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.d.ts +0 -18
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.js +0 -63
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.d.ts +0 -2
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-opencode.test.js +0 -86
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.d.ts +0 -9
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/mcp-placeholder.js +0 -14
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claude.js +0 -51
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-claudecode.js +0 -51
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.d.ts +0 -10
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-opencode.js +0 -51
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.d.ts +0 -9
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/adapters/skill-placeholder.js +0 -14
- package/node_modules/@cc-switch/sdk/dist/services/instance-service.d.ts +0 -78
- package/node_modules/@cc-switch/sdk/dist/services/instance-service.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/instance-service.js +0 -180
- package/package/cc-switch-sdk/dist/adapters/common.d.ts +0 -38
- package/package/cc-switch-sdk/dist/adapters/common.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/common.js +0 -47
- package/package/cc-switch-sdk/dist/adapters/index.d.ts +0 -5
- package/package/cc-switch-sdk/dist/adapters/index.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/index.js +0 -28
- package/package/cc-switch-sdk/dist/adapters/mcp-claude.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/mcp-claude.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-claude.js +0 -39
- package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-claudecode.js +0 -40
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.d.ts +0 -18
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.js +0 -63
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.d.ts +0 -2
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-opencode.test.js +0 -86
- package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.d.ts +0 -9
- package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/mcp-placeholder.js +0 -14
- package/package/cc-switch-sdk/dist/adapters/skill-claude.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/skill-claude.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-claude.js +0 -51
- package/package/cc-switch-sdk/dist/adapters/skill-claudecode.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/skill-claudecode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-claudecode.js +0 -51
- package/package/cc-switch-sdk/dist/adapters/skill-opencode.d.ts +0 -10
- package/package/cc-switch-sdk/dist/adapters/skill-opencode.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-opencode.js +0 -51
- package/package/cc-switch-sdk/dist/adapters/skill-placeholder.d.ts +0 -9
- package/package/cc-switch-sdk/dist/adapters/skill-placeholder.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/adapters/skill-placeholder.js +0 -14
- package/package/cc-switch-sdk/dist/services/instance-service.d.ts +0 -78
- package/package/cc-switch-sdk/dist/services/instance-service.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/services/instance-service.js +0 -180
|
@@ -17,12 +17,70 @@ import { getRuntimeAccessToken, getRuntimeBindingPublicState, saveRuntimeBinding
|
|
|
17
17
|
// 默认配置
|
|
18
18
|
const DEFAULT_CONFIG = {
|
|
19
19
|
enabled: false,
|
|
20
|
+
serverUrl: schedulerBaseUrl,
|
|
21
|
+
preferSameLanIp: true,
|
|
20
22
|
maxRetries: 5,
|
|
21
23
|
retryInterval: 5000,
|
|
22
24
|
};
|
|
23
25
|
let registrationState = {
|
|
24
26
|
registered: false,
|
|
27
|
+
registrations: {},
|
|
25
28
|
};
|
|
29
|
+
let cachedConfiguredConnectionKeys = [];
|
|
30
|
+
export function getConfiguredConnectionKeys() {
|
|
31
|
+
return [...cachedConfiguredConnectionKeys];
|
|
32
|
+
}
|
|
33
|
+
function normalizeOptionalString(value) {
|
|
34
|
+
if (value == null) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const normalized = String(value).trim();
|
|
38
|
+
return normalized || undefined;
|
|
39
|
+
}
|
|
40
|
+
function normalizeOptionalBoolean(value) {
|
|
41
|
+
if (typeof value === "boolean") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
const normalized = value.trim().toLowerCase();
|
|
46
|
+
if (["true", "1", "yes", "y"].includes(normalized)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (["false", "0", "no", "n"].includes(normalized)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
function normalizeOptionalNumber(value) {
|
|
56
|
+
if (value == null || value === "") {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const parsed = Number(value);
|
|
60
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
61
|
+
}
|
|
62
|
+
function normalizeTargetConfig(source) {
|
|
63
|
+
const serverUrl = normalizeOptionalString(source.serverUrl) ||
|
|
64
|
+
normalizeOptionalString(source.schedulerBaseUrl);
|
|
65
|
+
const connectionKey = normalizeOptionalString(source.connectionKey) ||
|
|
66
|
+
normalizeOptionalString(source.password);
|
|
67
|
+
return {
|
|
68
|
+
enabled: normalizeOptionalBoolean(source.enabled),
|
|
69
|
+
serverUrl,
|
|
70
|
+
tenantId: normalizeOptionalString(source.tenantId),
|
|
71
|
+
userKey: normalizeOptionalString(source.userKey),
|
|
72
|
+
connectionKey,
|
|
73
|
+
instanceName: normalizeOptionalString(source.instanceName),
|
|
74
|
+
projectName: normalizeOptionalString(source.projectName),
|
|
75
|
+
roleName: normalizeOptionalString(source.roleName),
|
|
76
|
+
workspacePath: normalizeOptionalString(source.workspacePath),
|
|
77
|
+
registerIp: normalizeOptionalString(source.registerIp),
|
|
78
|
+
virtualIp: normalizeOptionalString(source.virtualIp),
|
|
79
|
+
preferSameLanIp: normalizeOptionalBoolean(source.preferSameLanIp),
|
|
80
|
+
maxRetries: normalizeOptionalNumber(source.maxRetries),
|
|
81
|
+
retryInterval: normalizeOptionalNumber(source.retryInterval),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
26
84
|
/**
|
|
27
85
|
* 配置文件路径
|
|
28
86
|
*/
|
|
@@ -65,6 +123,18 @@ function ensureConfigFile() {
|
|
|
65
123
|
*
|
|
66
124
|
* 配置文件格式:
|
|
67
125
|
* {
|
|
126
|
+
* "connectionKey": "bridge-password", // 可选,server/面板连接该 bridge 的密钥
|
|
127
|
+
* "autoRegisterTargets": [
|
|
128
|
+
* {
|
|
129
|
+
* "serverUrl": "http://scheduler-host:7380", // 必填,要主动注册的 server 地址
|
|
130
|
+
* "userKey": "aws_live_xxx", // 必填,用户 API Key
|
|
131
|
+
* "instanceName": "my-bridge", // 必填/可选,默认主机名
|
|
132
|
+
* "registerIp": "192.168.1.25", // 可选,优先作为注册 IP
|
|
133
|
+
* "connectionKey": "bridge-password" // 可选,不填则使用顶层 connectionKey
|
|
134
|
+
* }
|
|
135
|
+
* ],
|
|
136
|
+
*
|
|
137
|
+
* // 兼容旧版单目标配置:
|
|
68
138
|
* "userKey": "aws_live_xxx", // 必填,用户 API Key
|
|
69
139
|
* "tenantId": "xxx", // 可选,租户 ID(不填则自动推导)
|
|
70
140
|
* "instanceName": "my-bridge", // 可选,默认使用主机名
|
|
@@ -89,18 +159,7 @@ function loadConfigFromFile() {
|
|
|
89
159
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
90
160
|
const config = JSON.parse(content);
|
|
91
161
|
logger.info(`[AutoRegister] 从配置文件加载配置: ${configPath}`);
|
|
92
|
-
return
|
|
93
|
-
tenantId: config.tenantId,
|
|
94
|
-
userKey: config.userKey,
|
|
95
|
-
instanceName: config.instanceName,
|
|
96
|
-
projectName: config.projectName,
|
|
97
|
-
roleName: config.roleName,
|
|
98
|
-
workspacePath: config.workspacePath,
|
|
99
|
-
registerIp: config.registerIp,
|
|
100
|
-
virtualIp: config.virtualIp,
|
|
101
|
-
maxRetries: config.maxRetries,
|
|
102
|
-
retryInterval: config.retryInterval,
|
|
103
|
-
};
|
|
162
|
+
return normalizeTargetConfig(config);
|
|
104
163
|
}
|
|
105
164
|
catch (error) {
|
|
106
165
|
const err = error;
|
|
@@ -108,6 +167,39 @@ function loadConfigFromFile() {
|
|
|
108
167
|
return null;
|
|
109
168
|
}
|
|
110
169
|
}
|
|
170
|
+
function loadTargetsFromFile() {
|
|
171
|
+
ensureConfigFile();
|
|
172
|
+
const configPath = getConfigFilePath();
|
|
173
|
+
try {
|
|
174
|
+
if (!fs.existsSync(configPath)) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
178
|
+
const config = JSON.parse(content);
|
|
179
|
+
const topLevel = normalizeTargetConfig(config);
|
|
180
|
+
const rawTargets = Array.isArray(config.autoRegisterTargets)
|
|
181
|
+
? config.autoRegisterTargets
|
|
182
|
+
: [];
|
|
183
|
+
if (rawTargets.length === 0) {
|
|
184
|
+
return topLevel.userKey ? [topLevel] : [];
|
|
185
|
+
}
|
|
186
|
+
return rawTargets
|
|
187
|
+
.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item)))
|
|
188
|
+
.map((item) => {
|
|
189
|
+
const target = normalizeTargetConfig(item);
|
|
190
|
+
return {
|
|
191
|
+
...topLevel,
|
|
192
|
+
...target,
|
|
193
|
+
connectionKey: target.connectionKey || topLevel.connectionKey,
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const err = error;
|
|
199
|
+
logger.warn(`[AutoRegister] 读取自动注册目标失败: ${configPath}, error: ${err.message}`);
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
111
203
|
/**
|
|
112
204
|
* 从环境变量加载配置
|
|
113
205
|
*/
|
|
@@ -116,12 +208,18 @@ function loadConfigFromEnv() {
|
|
|
116
208
|
if (process.env.AWS_AUTO_REGISTER === "true") {
|
|
117
209
|
envConfig.enabled = true;
|
|
118
210
|
}
|
|
211
|
+
if (process.env.AWS_RUNTIME_SCHEDULER_BASE_URL) {
|
|
212
|
+
envConfig.serverUrl = process.env.AWS_RUNTIME_SCHEDULER_BASE_URL;
|
|
213
|
+
}
|
|
119
214
|
if (process.env.AWS_TENANT_ID) {
|
|
120
215
|
envConfig.tenantId = process.env.AWS_TENANT_ID;
|
|
121
216
|
}
|
|
122
217
|
if (process.env.AWS_USER_KEY) {
|
|
123
218
|
envConfig.userKey = process.env.AWS_USER_KEY;
|
|
124
219
|
}
|
|
220
|
+
if (process.env.AWS_BRIDGE_CONNECTION_KEY) {
|
|
221
|
+
envConfig.connectionKey = process.env.AWS_BRIDGE_CONNECTION_KEY;
|
|
222
|
+
}
|
|
125
223
|
if (process.env.AWS_INSTANCE_NAME) {
|
|
126
224
|
envConfig.instanceName = process.env.AWS_INSTANCE_NAME;
|
|
127
225
|
}
|
|
@@ -140,6 +238,10 @@ function loadConfigFromEnv() {
|
|
|
140
238
|
if (process.env.AWS_VIRTUAL_IP) {
|
|
141
239
|
envConfig.virtualIp = process.env.AWS_VIRTUAL_IP;
|
|
142
240
|
}
|
|
241
|
+
if (process.env.AWS_PREFER_SAME_LAN_IP) {
|
|
242
|
+
envConfig.preferSameLanIp =
|
|
243
|
+
normalizeOptionalBoolean(process.env.AWS_PREFER_SAME_LAN_IP) ?? true;
|
|
244
|
+
}
|
|
143
245
|
if (process.env.AWS_REGISTER_MAX_RETRIES) {
|
|
144
246
|
envConfig.maxRetries = parseInt(process.env.AWS_REGISTER_MAX_RETRIES, 10);
|
|
145
247
|
}
|
|
@@ -180,18 +282,60 @@ export function loadConfig() {
|
|
|
180
282
|
}
|
|
181
283
|
return {
|
|
182
284
|
enabled,
|
|
285
|
+
serverUrl: merged.serverUrl || schedulerBaseUrl,
|
|
183
286
|
tenantId: merged.tenantId || "", // 保持为空字符串,后续会通过userKey自动推导
|
|
184
287
|
userKey: merged.userKey || "",
|
|
288
|
+
connectionKey: merged.connectionKey,
|
|
185
289
|
instanceName,
|
|
186
290
|
projectName: merged.projectName,
|
|
187
291
|
roleName: merged.roleName,
|
|
188
292
|
workspacePath: merged.workspacePath,
|
|
189
293
|
registerIp: merged.registerIp,
|
|
190
294
|
virtualIp: merged.virtualIp,
|
|
295
|
+
preferSameLanIp: merged.preferSameLanIp !== false,
|
|
191
296
|
maxRetries: merged.maxRetries,
|
|
192
297
|
retryInterval: merged.retryInterval,
|
|
193
298
|
};
|
|
194
299
|
}
|
|
300
|
+
export function loadConfigs() {
|
|
301
|
+
const fileTargets = loadTargetsFromFile();
|
|
302
|
+
const envConfig = loadConfigFromEnv();
|
|
303
|
+
const targets = fileTargets.length > 0 ? fileTargets : [loadConfigFromFile() || {}];
|
|
304
|
+
const configs = targets.map((target) => {
|
|
305
|
+
const merged = {
|
|
306
|
+
...DEFAULT_CONFIG,
|
|
307
|
+
...target,
|
|
308
|
+
...envConfig,
|
|
309
|
+
};
|
|
310
|
+
let enabled = false;
|
|
311
|
+
if (process.env.AWS_AUTO_REGISTER !== undefined) {
|
|
312
|
+
enabled = process.env.AWS_AUTO_REGISTER === "true";
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
enabled = fileTargets.length > 0 || Boolean(merged.userKey);
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
enabled,
|
|
319
|
+
serverUrl: merged.serverUrl || schedulerBaseUrl,
|
|
320
|
+
tenantId: merged.tenantId || "",
|
|
321
|
+
userKey: merged.userKey || "",
|
|
322
|
+
connectionKey: merged.connectionKey,
|
|
323
|
+
instanceName: merged.instanceName || os.hostname(),
|
|
324
|
+
projectName: merged.projectName,
|
|
325
|
+
roleName: merged.roleName,
|
|
326
|
+
workspacePath: merged.workspacePath,
|
|
327
|
+
registerIp: merged.registerIp,
|
|
328
|
+
virtualIp: merged.virtualIp,
|
|
329
|
+
preferSameLanIp: merged.preferSameLanIp !== false,
|
|
330
|
+
maxRetries: merged.maxRetries,
|
|
331
|
+
retryInterval: merged.retryInterval,
|
|
332
|
+
};
|
|
333
|
+
});
|
|
334
|
+
cachedConfiguredConnectionKeys = configs
|
|
335
|
+
.map((config) => config.connectionKey)
|
|
336
|
+
.filter((key) => Boolean(key && key.trim()));
|
|
337
|
+
return configs;
|
|
338
|
+
}
|
|
195
339
|
/**
|
|
196
340
|
* 获取本机所有非内部 IPv4 地址
|
|
197
341
|
*/
|
|
@@ -259,8 +403,7 @@ function extractIpFromUrl(url) {
|
|
|
259
403
|
* 策略:
|
|
260
404
|
* 1. 解析调度中心 URL 获取其 IP
|
|
261
405
|
* 2. 遍历本机网卡,找到与调度中心同子网的 IP
|
|
262
|
-
* 3. 如果找不到同子网 IP
|
|
263
|
-
* 4. 如果没有任何非内部 IP,返回 127.0.0.1
|
|
406
|
+
* 3. 如果找不到同子网 IP,返回 127.0.0.1
|
|
264
407
|
*/
|
|
265
408
|
function getPreferredIpAddress(schedulerUrl) {
|
|
266
409
|
const allIps = getAllLocalIpAddresses();
|
|
@@ -278,43 +421,49 @@ function getPreferredIpAddress(schedulerUrl) {
|
|
|
278
421
|
return ipInfo.address;
|
|
279
422
|
}
|
|
280
423
|
}
|
|
281
|
-
logger.warn(`[AutoRegister] 未找到与调度中心 (${schedulerIp})
|
|
424
|
+
logger.warn(`[AutoRegister] 未找到与调度中心 (${schedulerIp}) 同子网的本机 IP,使用 127.0.0.1`);
|
|
282
425
|
}
|
|
283
426
|
else {
|
|
284
|
-
logger.
|
|
427
|
+
logger.warn("[AutoRegister] 无法解析调度中心 IP,使用 127.0.0.1");
|
|
285
428
|
}
|
|
286
|
-
|
|
287
|
-
return allIps[0].address;
|
|
429
|
+
return "127.0.0.1";
|
|
288
430
|
}
|
|
289
431
|
/**
|
|
290
432
|
* 获取本机 IP 地址(兼容旧接口)
|
|
291
433
|
*/
|
|
292
|
-
function getLocalIpAddress() {
|
|
293
|
-
return getPreferredIpAddress(
|
|
434
|
+
function getLocalIpAddress(targetServerUrl = schedulerBaseUrl) {
|
|
435
|
+
return getPreferredIpAddress(targetServerUrl);
|
|
436
|
+
}
|
|
437
|
+
function resolveRuntimeBridgeBaseUrl(config) {
|
|
438
|
+
const port = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
|
|
439
|
+
const localIp = config.preferSameLanIp === false
|
|
440
|
+
? "127.0.0.1"
|
|
441
|
+
: getLocalIpAddress(config.serverUrl || schedulerBaseUrl);
|
|
442
|
+
return `http://${config.registerIp || config.virtualIp || localIp}:${port}`;
|
|
294
443
|
}
|
|
295
444
|
/**
|
|
296
445
|
* 执行注册请求
|
|
297
446
|
*/
|
|
298
447
|
async function doRegister(config) {
|
|
299
448
|
const instanceName = config.instanceName || os.hostname();
|
|
300
|
-
const
|
|
301
|
-
const port = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
|
|
449
|
+
const targetServerUrl = config.serverUrl || schedulerBaseUrl;
|
|
302
450
|
const request = {
|
|
303
451
|
tenantId: config.tenantId,
|
|
304
452
|
userKey: config.userKey,
|
|
305
453
|
instanceName,
|
|
306
454
|
hostname: os.hostname(),
|
|
307
455
|
virtualIp: config.registerIp || config.virtualIp,
|
|
308
|
-
runtimeBridgeBaseUrl:
|
|
456
|
+
runtimeBridgeBaseUrl: resolveRuntimeBridgeBaseUrl(config),
|
|
457
|
+
connectionKey: config.connectionKey,
|
|
309
458
|
workspacePath: config.workspacePath,
|
|
310
459
|
projectName: config.projectName,
|
|
311
460
|
roleName: config.roleName,
|
|
312
461
|
};
|
|
313
462
|
logger.info(`[AutoRegister] 正在注册实例: ${instanceName}`);
|
|
314
|
-
logger.info(`[AutoRegister] 调度中心: ${
|
|
463
|
+
logger.info(`[AutoRegister] 调度中心: ${targetServerUrl}`);
|
|
315
464
|
logger.info(`[AutoRegister] 用户 Key: ${config.userKey ? "****" + config.userKey.slice(-4) : "(未设置)"}`);
|
|
316
465
|
logger.info(`[AutoRegister] 租户 ID: ${config.tenantId || "(自动推导)"}`);
|
|
317
|
-
const response = await axios.post(`${
|
|
466
|
+
const response = await axios.post(`${targetServerUrl}/api/instances/register`, request, {
|
|
318
467
|
headers: {
|
|
319
468
|
"Content-Type": "application/json",
|
|
320
469
|
"X-Runtime-Token": runtimeToken,
|
|
@@ -348,11 +497,16 @@ async function doUnregister(instanceId) {
|
|
|
348
497
|
* 4. 默认值
|
|
349
498
|
*/
|
|
350
499
|
export async function autoRegister(customConfig) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
500
|
+
const configs = customConfig
|
|
501
|
+
? [{ ...loadConfig(), ...customConfig }]
|
|
502
|
+
: loadConfigs();
|
|
503
|
+
if (configs.length > 1) {
|
|
504
|
+
const results = await Promise.all(configs.map((config) => autoRegisterSingle(config)));
|
|
505
|
+
return results.every(Boolean);
|
|
506
|
+
}
|
|
507
|
+
return autoRegisterSingle(configs[0] || loadConfig());
|
|
508
|
+
}
|
|
509
|
+
async function autoRegisterSingle(config) {
|
|
356
510
|
// 检查是否启用
|
|
357
511
|
if (!config.enabled) {
|
|
358
512
|
logger.info("[AutoRegister] 自动注册未启用");
|
|
@@ -375,6 +529,10 @@ export async function autoRegister(customConfig) {
|
|
|
375
529
|
if (response.success) {
|
|
376
530
|
registrationState.registered = true;
|
|
377
531
|
registrationState.instanceId = response.instanceId;
|
|
532
|
+
if (response.instanceId) {
|
|
533
|
+
registrationState.registrations[config.serverUrl || schedulerBaseUrl] =
|
|
534
|
+
response.instanceId;
|
|
535
|
+
}
|
|
378
536
|
registrationState.error = undefined;
|
|
379
537
|
logger.info(`[AutoRegister] ✓ 注册成功!`);
|
|
380
538
|
logger.info(`[AutoRegister] 实例 ID: ${response.instanceId}`);
|
|
@@ -386,7 +544,7 @@ export async function autoRegister(customConfig) {
|
|
|
386
544
|
accessToken: response.runtimeAccessToken,
|
|
387
545
|
instanceId: response.instanceId,
|
|
388
546
|
userId: response.userId,
|
|
389
|
-
schedulerBaseUrl: response.serverUrl || schedulerBaseUrl,
|
|
547
|
+
schedulerBaseUrl: response.serverUrl || config.serverUrl || schedulerBaseUrl,
|
|
390
548
|
});
|
|
391
549
|
logger.info("[AutoRegister] 已保存调度中心下发的 runtime access token");
|
|
392
550
|
}
|
|
@@ -428,7 +586,7 @@ export async function autoRegister(customConfig) {
|
|
|
428
586
|
logger.error(`[AutoRegister] - 用户 Key (userKey): ${config.userKey ? "****" + config.userKey.slice(-4) : "(未设置)"}`);
|
|
429
587
|
logger.error(`[AutoRegister] - 租户 ID (tenantId): ${config.tenantId || "(可选,将自动推导)"}`);
|
|
430
588
|
logger.error(`[AutoRegister] - 实例名 (instanceName): ${config.instanceName || "(使用主机名)"}`);
|
|
431
|
-
logger.error(`[AutoRegister] - 调度中心地址: ${schedulerBaseUrl}`);
|
|
589
|
+
logger.error(`[AutoRegister] - 调度中心地址: ${config.serverUrl || schedulerBaseUrl}`);
|
|
432
590
|
logger.error(`[AutoRegister] - 最后错误: ${registrationState.error || "未知"}`);
|
|
433
591
|
logger.error(`[AutoRegister] ========================================`);
|
|
434
592
|
logger.error(`[AutoRegister] 配置来源:~/.aws-bridge/config.json 或环境变量`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mkdtempSync } from 'node:fs';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -61,6 +62,8 @@ describe('aws-client-agent-mcp service', () => {
|
|
|
61
62
|
process.env.AWS_CLIENT_AGENT_MCP_COMMAND = 'aws-client-agent-mcp';
|
|
62
63
|
process.env.AWS_RUNTIME_CALLBACK_TOKEN = 'secret-runtime-token';
|
|
63
64
|
process.env.CUSTOM_SECRET = 'should-not-leak';
|
|
65
|
+
process.env.AWS_RUNTIME_HOME_DIR = mkdtempSync(path.join(os.tmpdir(), 'aws-mcp-config-'));
|
|
66
|
+
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = '';
|
|
64
67
|
process.env.AWS_SERVER_URL = '';
|
|
65
68
|
process.env.AWS_MCP_HTTP_URL = '';
|
|
66
69
|
const mod = await import('./aws-client-agent-mcp.js');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface McpLaunchBindingInput {
|
|
2
|
+
agentId: string;
|
|
3
|
+
workspacePath: string;
|
|
4
|
+
serverUrl?: string;
|
|
5
|
+
mcpHttpUrl?: string;
|
|
6
|
+
runtimeAccessToken?: string;
|
|
7
|
+
userId?: string;
|
|
8
|
+
schedulerBaseUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface McpLaunchBinding {
|
|
11
|
+
id: string;
|
|
12
|
+
agentId: string;
|
|
13
|
+
workspacePath: string;
|
|
14
|
+
serverUrl: string;
|
|
15
|
+
mcpHttpUrl: string;
|
|
16
|
+
runtimeAccessToken?: string;
|
|
17
|
+
userId?: string;
|
|
18
|
+
schedulerBaseUrl?: string;
|
|
19
|
+
enqueuedAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ClaimedMcpLaunchBinding extends McpLaunchBinding {
|
|
22
|
+
runtimeAccessToken?: string;
|
|
23
|
+
userId?: string;
|
|
24
|
+
schedulerBaseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface McpLaunchBindingClaimRequest {
|
|
27
|
+
agentId: unknown;
|
|
28
|
+
bindingId?: unknown;
|
|
29
|
+
workspacePath: unknown;
|
|
30
|
+
serverUrl?: unknown;
|
|
31
|
+
}
|
|
32
|
+
export declare function enqueueMcpLaunchBinding(input: McpLaunchBindingInput): McpLaunchBinding | null;
|
|
33
|
+
export declare function claimMcpLaunchBinding(request: McpLaunchBindingClaimRequest): ClaimedMcpLaunchBinding | null;
|
|
34
|
+
export declare function getMcpLaunchQueueSize(workspacePath: unknown): number;
|
|
35
|
+
export declare function clearMcpLaunchBindingQueues(): void;
|
|
36
|
+
//# sourceMappingURL=mcp-launch-binding-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"AAGA,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,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAqCD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CAoB7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,uBAAuB,GAAG,IAAI,CAoC3G;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAMpE;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getRuntimeAccessToken, loadRuntimeBinding } from "./runtime-binding.js";
|
|
3
|
+
const launchBindings = [];
|
|
4
|
+
function normalizeWorkspacePath(workspacePath) {
|
|
5
|
+
const raw = String(workspacePath || "").trim();
|
|
6
|
+
if (!raw) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
return path.resolve(raw).toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
function normalizeOptionalUrl(value) {
|
|
12
|
+
return String(value || "").trim();
|
|
13
|
+
}
|
|
14
|
+
function normalizeServerUrl(serverUrl) {
|
|
15
|
+
const raw = String(serverUrl || "").trim();
|
|
16
|
+
if (!raw) {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(raw);
|
|
21
|
+
url.protocol = url.protocol.toLowerCase();
|
|
22
|
+
url.hostname = url.hostname.toLowerCase();
|
|
23
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
24
|
+
return url.toString().replace(/\/+$/, "");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return raw.replace(/\/+$/, "").toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function createBindingId(agentId) {
|
|
31
|
+
return `${agentId}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
32
|
+
}
|
|
33
|
+
export function enqueueMcpLaunchBinding(input) {
|
|
34
|
+
const agentId = String(input.agentId || "").trim();
|
|
35
|
+
const workspaceKey = normalizeWorkspacePath(input.workspacePath);
|
|
36
|
+
if (!agentId || !workspaceKey) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const binding = {
|
|
40
|
+
id: createBindingId(agentId),
|
|
41
|
+
agentId,
|
|
42
|
+
workspacePath: path.resolve(String(input.workspacePath)),
|
|
43
|
+
serverUrl: normalizeOptionalUrl(input.serverUrl || input.schedulerBaseUrl),
|
|
44
|
+
mcpHttpUrl: normalizeOptionalUrl(input.mcpHttpUrl),
|
|
45
|
+
runtimeAccessToken: normalizeOptionalUrl(input.runtimeAccessToken),
|
|
46
|
+
userId: normalizeOptionalUrl(input.userId),
|
|
47
|
+
schedulerBaseUrl: normalizeOptionalUrl(input.schedulerBaseUrl),
|
|
48
|
+
enqueuedAt: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
launchBindings.push(binding);
|
|
51
|
+
return binding;
|
|
52
|
+
}
|
|
53
|
+
export function claimMcpLaunchBinding(request) {
|
|
54
|
+
const agentId = String(request.agentId || "").trim();
|
|
55
|
+
const bindingId = String(request.bindingId || "").trim();
|
|
56
|
+
const workspaceKey = normalizeWorkspacePath(request.workspacePath);
|
|
57
|
+
const serverKey = normalizeServerUrl(request.serverUrl);
|
|
58
|
+
if (!agentId || !bindingId || !workspaceKey || !serverKey) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const bindingIndex = launchBindings.findIndex((binding) => binding.agentId === agentId &&
|
|
62
|
+
binding.id === bindingId &&
|
|
63
|
+
normalizeWorkspacePath(binding.workspacePath) === workspaceKey &&
|
|
64
|
+
normalizeServerUrl(binding.serverUrl || binding.schedulerBaseUrl) === serverKey);
|
|
65
|
+
if (bindingIndex < 0) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const [binding] = launchBindings.splice(bindingIndex, 1);
|
|
69
|
+
if (!binding) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const runtimeState = loadRuntimeBinding();
|
|
73
|
+
const userId = binding.userId || runtimeState.userId;
|
|
74
|
+
const schedulerBaseUrl = binding.schedulerBaseUrl || runtimeState.schedulerBaseUrl;
|
|
75
|
+
const runtimeAccessToken = binding.runtimeAccessToken || getRuntimeAccessToken(userId, schedulerBaseUrl);
|
|
76
|
+
return {
|
|
77
|
+
...binding,
|
|
78
|
+
runtimeAccessToken,
|
|
79
|
+
userId,
|
|
80
|
+
schedulerBaseUrl,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function getMcpLaunchQueueSize(workspacePath) {
|
|
84
|
+
const workspaceKey = normalizeWorkspacePath(workspacePath);
|
|
85
|
+
if (!workspaceKey) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
return launchBindings.filter((binding) => normalizeWorkspacePath(binding.workspacePath) === workspaceKey).length;
|
|
89
|
+
}
|
|
90
|
+
export function clearMcpLaunchBindingQueues() {
|
|
91
|
+
launchBindings.splice(0, launchBindings.length);
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-launch-binding-queue.test.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { claimMcpLaunchBinding, clearMcpLaunchBindingQueues, enqueueMcpLaunchBinding, getMcpLaunchQueueSize, } from './mcp-launch-binding-queue.js';
|
|
3
|
+
describe('mcp launch binding queue', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
clearMcpLaunchBindingQueues();
|
|
6
|
+
});
|
|
7
|
+
it('claims same-workspace bindings only for the requested agent identity', () => {
|
|
8
|
+
const firstBinding = enqueueMcpLaunchBinding({
|
|
9
|
+
agentId: 'agent-a',
|
|
10
|
+
workspacePath: 'C:/repo/demo',
|
|
11
|
+
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
12
|
+
runtimeAccessToken: 'token-a',
|
|
13
|
+
userId: 'user-a',
|
|
14
|
+
schedulerBaseUrl: 'http://server-a:7380',
|
|
15
|
+
});
|
|
16
|
+
const secondBinding = enqueueMcpLaunchBinding({
|
|
17
|
+
agentId: 'agent-b',
|
|
18
|
+
workspacePath: 'C:/repo/demo',
|
|
19
|
+
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
20
|
+
runtimeAccessToken: 'token-b',
|
|
21
|
+
userId: 'user-b',
|
|
22
|
+
schedulerBaseUrl: 'http://server-a:7380',
|
|
23
|
+
});
|
|
24
|
+
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(2);
|
|
25
|
+
const second = claimMcpLaunchBinding({
|
|
26
|
+
agentId: 'agent-b',
|
|
27
|
+
bindingId: secondBinding?.id,
|
|
28
|
+
workspacePath: 'C:/repo/demo',
|
|
29
|
+
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
30
|
+
});
|
|
31
|
+
const first = claimMcpLaunchBinding({
|
|
32
|
+
agentId: 'agent-a',
|
|
33
|
+
bindingId: firstBinding?.id,
|
|
34
|
+
workspacePath: 'C:/repo/demo',
|
|
35
|
+
serverUrl: 'ws://server-a:7380/ws/agent',
|
|
36
|
+
});
|
|
37
|
+
expect(first?.agentId).toBe('agent-a');
|
|
38
|
+
expect(first?.runtimeAccessToken).toBe('token-a');
|
|
39
|
+
expect(second?.agentId).toBe('agent-b');
|
|
40
|
+
expect(second?.runtimeAccessToken).toBe('token-b');
|
|
41
|
+
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
it('does not consume a binding when the requested agent id does not match', () => {
|
|
44
|
+
const binding = enqueueMcpLaunchBinding({
|
|
45
|
+
agentId: 'agent-a',
|
|
46
|
+
workspacePath: 'C:/repo/demo',
|
|
47
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
48
|
+
});
|
|
49
|
+
expect(claimMcpLaunchBinding({
|
|
50
|
+
agentId: 'agent-b',
|
|
51
|
+
bindingId: binding?.id,
|
|
52
|
+
workspacePath: 'C:/repo/demo',
|
|
53
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
54
|
+
})).toBeNull();
|
|
55
|
+
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(1);
|
|
56
|
+
expect(claimMcpLaunchBinding({
|
|
57
|
+
agentId: 'agent-a',
|
|
58
|
+
bindingId: binding?.id,
|
|
59
|
+
workspacePath: 'C:/repo/demo',
|
|
60
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
61
|
+
})?.agentId).toBe('agent-a');
|
|
62
|
+
});
|
|
63
|
+
it('does not consume a binding when the binding id is missing or wrong', () => {
|
|
64
|
+
const binding = enqueueMcpLaunchBinding({
|
|
65
|
+
agentId: 'agent-a',
|
|
66
|
+
workspacePath: 'C:/repo/demo',
|
|
67
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
68
|
+
});
|
|
69
|
+
expect(claimMcpLaunchBinding({
|
|
70
|
+
agentId: 'agent-a',
|
|
71
|
+
workspacePath: 'C:/repo/demo',
|
|
72
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
73
|
+
})).toBeNull();
|
|
74
|
+
expect(claimMcpLaunchBinding({
|
|
75
|
+
agentId: 'agent-a',
|
|
76
|
+
bindingId: 'wrong-binding',
|
|
77
|
+
workspacePath: 'C:/repo/demo',
|
|
78
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
79
|
+
})).toBeNull();
|
|
80
|
+
expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(1);
|
|
81
|
+
expect(claimMcpLaunchBinding({
|
|
82
|
+
agentId: 'agent-a',
|
|
83
|
+
bindingId: binding?.id,
|
|
84
|
+
workspacePath: 'C:/repo/demo',
|
|
85
|
+
serverUrl: 'ws://server:7380/ws/agent',
|
|
86
|
+
})?.agentId).toBe('agent-a');
|
|
87
|
+
});
|
|
88
|
+
it('matches by server URL as well as workspace path', () => {
|
|
89
|
+
const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' });
|
|
90
|
+
const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' });
|
|
91
|
+
expect(claimMcpLaunchBinding({ agentId: 'agent-b', bindingId: secondBinding?.id, workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' })?.agentId).toBe('agent-b');
|
|
92
|
+
expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: firstBinding?.id, workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' })?.agentId).toBe('agent-a');
|
|
93
|
+
});
|
|
94
|
+
it('does not mix different workspace paths', () => {
|
|
95
|
+
const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' });
|
|
96
|
+
const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' });
|
|
97
|
+
expect(claimMcpLaunchBinding({ agentId: 'agent-b', bindingId: secondBinding?.id, workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-b');
|
|
98
|
+
expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: firstBinding?.id, workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-a');
|
|
99
|
+
});
|
|
100
|
+
it('returns null for missing or invalid workspaces', () => {
|
|
101
|
+
expect(enqueueMcpLaunchBinding({ agentId: '', workspacePath: 'C:/repo/demo' })).toBeNull();
|
|
102
|
+
expect(enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: '' })).toBeNull();
|
|
103
|
+
expect(claimMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/missing', serverUrl: 'ws://server:7380/ws/agent' })).toBeNull();
|
|
104
|
+
expect(claimMcpLaunchBinding({ agentId: '', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server:7380/ws/agent' })).toBeNull();
|
|
105
|
+
expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: 'binding-a', workspacePath: 'C:/repo/demo' })).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -180,7 +180,7 @@ export class OrphanMonitor {
|
|
|
180
180
|
// 使用进程树终止
|
|
181
181
|
const result = terminateProcessTree(pid);
|
|
182
182
|
// 等待进程完全终止(最多 5 秒)
|
|
183
|
-
const exited = waitForProcessExit(pid, 5000);
|
|
183
|
+
const exited = await waitForProcessExit(pid, 5000);
|
|
184
184
|
if (result.terminated > 0 && exited) {
|
|
185
185
|
const event = {
|
|
186
186
|
type: 'orphan_cleaned',
|
|
@@ -81,7 +81,7 @@ export declare function terminateProcessTree(pid: number): {
|
|
|
81
81
|
* @param timeoutMs 超时时间(毫秒)
|
|
82
82
|
* @returns 是否成功终止
|
|
83
83
|
*/
|
|
84
|
-
export declare function waitForProcessExit(pid: number, timeoutMs?: number): boolean
|
|
84
|
+
export declare function waitForProcessExit(pid: number, timeoutMs?: number): Promise<boolean>;
|
|
85
85
|
/**
|
|
86
86
|
* 检测并收集所有孤儿进程信息
|
|
87
87
|
* 用于 runtime-bridge 启动时恢复控制
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-detector.d.ts","sourceRoot":"","sources":["../../src/services/process-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiCH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAoBrD;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,WAAW,EAAE,CAkBvD;AA8LD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAkBnF;AAiUD;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAyD3F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmCxF;AA2BD;;;;;;GAMG;AACH,
|
|
1
|
+
{"version":3,"file":"process-detector.d.ts","sourceRoot":"","sources":["../../src/services/process-detector.ts"],"names":[],"mappings":"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"}
|