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
package/README.md
CHANGED
|
@@ -30,10 +30,9 @@ aws-bridge
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
`aws-runtime-bridge` 命令仍作为兼容别名保留。安装 `aws-runtime-bridge` 后,包内会随附
|
|
33
|
-
`aws-client-agent-mcp` 的编译产物;bridge
|
|
34
|
-
启动 Agent 时默认注入名为 `aws-mcp` 的 MCP Server。
|
|
33
|
+
`aws-client-agent-mcp` 的编译产物;bridge 启动时只负责准备该 MCP 产物,不再在 Agent 启动时默认动态注入 `aws-mcp`。
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
请在面板中为目标运行时安装/配置 MCP;这样 Claude Code、Codex、OpenCode、PTY 等不同启动模式都走一致的持久化 MCP 配置链路。
|
|
37
36
|
|
|
38
37
|
如需使用自定义 MCP 可执行文件,可设置:
|
|
39
38
|
|
package/dist/config.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export declare const AUTO_REGISTER_PROJECT_NAME: string;
|
|
|
67
67
|
export declare const AUTO_REGISTER_ROLE_NAME: string;
|
|
68
68
|
/** 工作空间路径 */
|
|
69
69
|
export declare const AUTO_REGISTER_WORKSPACE_PATH: string;
|
|
70
|
-
/**
|
|
70
|
+
/** 注册 IP(优先),兼容 EasyTier 虚拟 IP */
|
|
71
71
|
export declare const AUTO_REGISTER_VIRTUAL_IP: string;
|
|
72
72
|
/** 注册重试次数 */
|
|
73
73
|
export declare const AUTO_REGISTER_MAX_RETRIES: number;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,gBAAgB,EAAE,MACwC,CAAC;AAExE,oBAAoB;AACpB,eAAO,MAAM,8BAA8B,gCAAgC,CAAC;AAE5E,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,kBAAkB;AAClB,eAAO,MAAM,YAAY,EAAE,MAC+C,CAAC;AAE3E,2BAA2B;AAC3B,eAAO,MAAM,wBAAwB,EAAE,OACiB,CAAC;AAEzD,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,uBAAuB;AACvB,wBAAgB,uBAAuB,IAAI,IAAI,CAc9C;AAED,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,QAC4B,CAAC;AAElE,aAAa;AACb,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,4BAA4B,QAExC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -3,71 +3,73 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 集中管理所有环境变量和配置常量
|
|
5
5
|
*/
|
|
6
|
-
import os from
|
|
7
|
-
import path from
|
|
8
|
-
import { logger } from
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { logger } from "./utils/logger.js";
|
|
9
9
|
/** 服务端口 */
|
|
10
10
|
export const port = Number(process.env.AWS_RUNTIME_BRIDGE_PORT || 18081);
|
|
11
11
|
/** 调度器基础 URL */
|
|
12
|
-
export const schedulerBaseUrl = process.env.AWS_RUNTIME_SCHEDULER_BASE_URL ||
|
|
12
|
+
export const schedulerBaseUrl = process.env.AWS_RUNTIME_SCHEDULER_BASE_URL || "http://localhost:8080";
|
|
13
13
|
/** 默认运行时回调 Token */
|
|
14
|
-
export const DEFAULT_RUNTIME_CALLBACK_TOKEN =
|
|
14
|
+
export const DEFAULT_RUNTIME_CALLBACK_TOKEN = "agentswork-runtime-callback";
|
|
15
15
|
/** Node 环境 */
|
|
16
|
-
export const nodeEnv = process.env.NODE_ENV ||
|
|
16
|
+
export const nodeEnv = process.env.NODE_ENV || "development";
|
|
17
17
|
/** 运行时回调 Token */
|
|
18
18
|
export const runtimeToken = process.env.AWS_RUNTIME_CALLBACK_TOKEN || DEFAULT_RUNTIME_CALLBACK_TOKEN;
|
|
19
19
|
/** 是否显式允许本地开发使用默认 Token */
|
|
20
|
-
export const allowDefaultRuntimeToken = process.env.AWS_ALLOW_DEFAULT_RUNTIME_TOKEN ===
|
|
20
|
+
export const allowDefaultRuntimeToken = process.env.AWS_ALLOW_DEFAULT_RUNTIME_TOKEN === "true";
|
|
21
21
|
/** 是否允许浏览宿主机任意目录(默认关闭,仅建议本地排障启用) */
|
|
22
|
-
export const allowHostFileBrowser = process.env.AWS_ALLOW_HOST_FILE_BROWSER ===
|
|
22
|
+
export const allowHostFileBrowser = process.env.AWS_ALLOW_HOST_FILE_BROWSER === "true";
|
|
23
23
|
/** CORS 允许来源列表,默认仅允许本机开发前端 */
|
|
24
|
-
export const allowedCorsOrigins = String(process.env.AWS_RUNTIME_CORS_ORIGINS ||
|
|
25
|
-
.
|
|
24
|
+
export const allowedCorsOrigins = String(process.env.AWS_RUNTIME_CORS_ORIGINS ||
|
|
25
|
+
"http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173,http://127.0.0.1:5173")
|
|
26
|
+
.split(",")
|
|
26
27
|
.map((origin) => origin.trim())
|
|
27
28
|
.filter((origin) => origin.length > 0);
|
|
28
29
|
/** 验证生产环境必须设置 Token */
|
|
29
30
|
export function validateProductionToken() {
|
|
30
31
|
if (!process.env.AWS_RUNTIME_CALLBACK_TOKEN) {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if (allowDefaultRuntimeToken) {
|
|
33
|
+
logger.warn("[runtime-bridge] AWS_RUNTIME_CALLBACK_TOKEN not set, using default development token");
|
|
34
|
+
return;
|
|
34
35
|
}
|
|
35
|
-
logger.
|
|
36
|
+
logger.info("[runtime-bridge] AWS_RUNTIME_CALLBACK_TOKEN not set; starting in unpaired mode. " +
|
|
37
|
+
"Sensitive APIs require a panel/server issued runtime access token after pairing.");
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
/** 获取运行时主目录 */
|
|
39
41
|
export function getRuntimeHomeDir() {
|
|
40
|
-
return String(process.env.AWS_RUNTIME_HOME_DIR ||
|
|
42
|
+
return String(process.env.AWS_RUNTIME_HOME_DIR || "").trim() || os.homedir();
|
|
41
43
|
}
|
|
42
44
|
/** 获取 Claude 配置文件路径(AI 配置写入 settings.json) */
|
|
43
45
|
export function getClaudeConfigFile(runtimeHome) {
|
|
44
|
-
return String(process.env.AWS_RUNTIME_CLAUDE_CONFIG_FILE ||
|
|
45
|
-
|
|
46
|
+
return (String(process.env.AWS_RUNTIME_CLAUDE_CONFIG_FILE || "").trim() ||
|
|
47
|
+
path.join(runtimeHome, ".claude", "settings.json"));
|
|
46
48
|
}
|
|
47
49
|
/** 获取 Claude MCP 配置文件路径 */
|
|
48
50
|
export function getClaudeMcpConfigFile(runtimeHome) {
|
|
49
|
-
return String(process.env.AWS_RUNTIME_CLAUDE_MCP_CONFIG_FILE ||
|
|
50
|
-
|
|
51
|
+
return (String(process.env.AWS_RUNTIME_CLAUDE_MCP_CONFIG_FILE || "").trim() ||
|
|
52
|
+
path.join(runtimeHome, ".claude.json"));
|
|
51
53
|
}
|
|
52
54
|
/** 获取 Claude Skills 目录 */
|
|
53
55
|
export function getClaudeSkillsDir(runtimeHome) {
|
|
54
|
-
return String(process.env.AWS_RUNTIME_CLAUDE_SKILLS_DIR ||
|
|
55
|
-
|
|
56
|
+
return (String(process.env.AWS_RUNTIME_CLAUDE_SKILLS_DIR || "").trim() ||
|
|
57
|
+
path.join(runtimeHome, ".claude", "skills"));
|
|
56
58
|
}
|
|
57
59
|
/** 获取 OpenCode 配置文件路径 */
|
|
58
60
|
export function getOpencodeConfigFile(runtimeHome) {
|
|
59
|
-
return String(process.env.AWS_RUNTIME_OPENCODE_CONFIG_FILE ||
|
|
60
|
-
|
|
61
|
+
return (String(process.env.AWS_RUNTIME_OPENCODE_CONFIG_FILE || "").trim() ||
|
|
62
|
+
path.join(runtimeHome, ".config", "opencode", "opencode.json"));
|
|
61
63
|
}
|
|
62
64
|
/** 获取 Codex 配置文件路径 */
|
|
63
65
|
export function getCodexConfigFile(runtimeHome) {
|
|
64
|
-
return String(process.env.AWS_RUNTIME_CODEX_CONFIG_FILE ||
|
|
65
|
-
|
|
66
|
+
return (String(process.env.AWS_RUNTIME_CODEX_CONFIG_FILE || "").trim() ||
|
|
67
|
+
path.join(runtimeHome, ".codex", "config.toml"));
|
|
66
68
|
}
|
|
67
69
|
/** 获取 OpenCode Skills 目录 */
|
|
68
70
|
export function getOpencodeSkillsDir(runtimeHome) {
|
|
69
|
-
return String(process.env.AWS_RUNTIME_OPENCODE_SKILLS_DIR ||
|
|
70
|
-
|
|
71
|
+
return (String(process.env.AWS_RUNTIME_OPENCODE_SKILLS_DIR || "").trim() ||
|
|
72
|
+
path.join(runtimeHome, ".opencode", "skills"));
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
73
75
|
* 孤儿进程监控配置
|
|
@@ -75,9 +77,10 @@ export function getOpencodeSkillsDir(runtimeHome) {
|
|
|
75
77
|
/** 定期扫描间隔(毫秒),默认 30 秒 */
|
|
76
78
|
export const ORPHAN_SCAN_INTERVAL = Number(process.env.AWS_ORPHAN_SCAN_INTERVAL || 30000);
|
|
77
79
|
/** 自动清理模式: 'auto' | 'alert' | 'manual' */
|
|
78
|
-
export const ORPHAN_AUTO_CLEAN_MODE = (process.env.AWS_ORPHAN_AUTO_CLEAN_MODE ||
|
|
80
|
+
export const ORPHAN_AUTO_CLEAN_MODE = (process.env.AWS_ORPHAN_AUTO_CLEAN_MODE ||
|
|
81
|
+
"alert");
|
|
79
82
|
/** 是否启用定期扫描 */
|
|
80
|
-
export const ORPHAN_SCAN_ENABLED = process.env.AWS_ORPHAN_SCAN_ENABLED !==
|
|
83
|
+
export const ORPHAN_SCAN_ENABLED = process.env.AWS_ORPHAN_SCAN_ENABLED !== "false";
|
|
81
84
|
/** 健康检测:进程最大无响应时间(毫秒),默认 5 分钟 */
|
|
82
85
|
export const ORPHAN_UNRESPONSIVE_TIMEOUT = Number(process.env.AWS_ORPHAN_UNRESPONSIVE_TIMEOUT || 300000);
|
|
83
86
|
/** 健康检测:CPU 使用率阈值(百分比),超过则认为卡死,默认 100% */
|
|
@@ -88,21 +91,21 @@ export const ORPHAN_MEMORY_THRESHOLD = Number(process.env.AWS_ORPHAN_MEMORY_THRE
|
|
|
88
91
|
* 自动注册配置
|
|
89
92
|
*/
|
|
90
93
|
/** 是否启用自动注册 */
|
|
91
|
-
export const AUTO_REGISTER_ENABLED = process.env.AWS_AUTO_REGISTER ===
|
|
94
|
+
export const AUTO_REGISTER_ENABLED = process.env.AWS_AUTO_REGISTER === "true";
|
|
92
95
|
/** 租户 ID */
|
|
93
|
-
export const AUTO_REGISTER_TENANT_ID = process.env.AWS_TENANT_ID ||
|
|
96
|
+
export const AUTO_REGISTER_TENANT_ID = process.env.AWS_TENANT_ID || "";
|
|
94
97
|
/** 用户 API Key */
|
|
95
|
-
export const AUTO_REGISTER_USER_KEY = process.env.AWS_USER_KEY ||
|
|
98
|
+
export const AUTO_REGISTER_USER_KEY = process.env.AWS_USER_KEY || "";
|
|
96
99
|
/** 实例名称(默认使用主机名) */
|
|
97
|
-
export const AUTO_REGISTER_INSTANCE_NAME = process.env.AWS_INSTANCE_NAME ||
|
|
100
|
+
export const AUTO_REGISTER_INSTANCE_NAME = process.env.AWS_INSTANCE_NAME || "";
|
|
98
101
|
/** 项目名称 */
|
|
99
|
-
export const AUTO_REGISTER_PROJECT_NAME = process.env.AWS_PROJECT_NAME ||
|
|
102
|
+
export const AUTO_REGISTER_PROJECT_NAME = process.env.AWS_PROJECT_NAME || "";
|
|
100
103
|
/** 角色名称 */
|
|
101
|
-
export const AUTO_REGISTER_ROLE_NAME = process.env.AWS_ROLE_NAME ||
|
|
104
|
+
export const AUTO_REGISTER_ROLE_NAME = process.env.AWS_ROLE_NAME || "";
|
|
102
105
|
/** 工作空间路径 */
|
|
103
|
-
export const AUTO_REGISTER_WORKSPACE_PATH = process.env.AWS_WORKSPACE_PATH ||
|
|
104
|
-
/**
|
|
105
|
-
export const AUTO_REGISTER_VIRTUAL_IP = process.env.AWS_VIRTUAL_IP ||
|
|
106
|
+
export const AUTO_REGISTER_WORKSPACE_PATH = process.env.AWS_WORKSPACE_PATH || "";
|
|
107
|
+
/** 注册 IP(优先),兼容 EasyTier 虚拟 IP */
|
|
108
|
+
export const AUTO_REGISTER_VIRTUAL_IP = process.env.AWS_REGISTER_IP || process.env.AWS_VIRTUAL_IP || "";
|
|
106
109
|
/** 注册重试次数 */
|
|
107
110
|
export const AUTO_REGISTER_MAX_RETRIES = Number(process.env.AWS_REGISTER_MAX_RETRIES || 5);
|
|
108
111
|
/** 注册重试间隔(毫秒) */
|
package/dist/index.js
CHANGED
|
@@ -21,18 +21,20 @@ import { gitRouter } from "./routes/git.js";
|
|
|
21
21
|
import { fileBrowserRouter } from "./routes/file-browser.js";
|
|
22
22
|
import { aiSourcesRouter } from "./routes/ai-sources.js";
|
|
23
23
|
import { processesRouter } from "./routes/processes.js";
|
|
24
|
+
import { runtimeBindingRouter, logRuntimeBindingStartupState, } from "./routes/runtime-binding.js";
|
|
24
25
|
import { sessions, flushSessionOutput } from "./services/session-output.js";
|
|
25
|
-
import { loadPersistedSessions, savePersistedSessions } from "./services/terminal-persistence.js";
|
|
26
|
+
import { loadPersistedSessions, savePersistedSessions, } from "./services/terminal-persistence.js";
|
|
26
27
|
import { detectOrphanProcesses } from "./services/process-detector.js";
|
|
27
|
-
import { startOrphanMonitor, stopOrphanMonitor } from "./services/orphan-monitor.js";
|
|
28
|
+
import { startOrphanMonitor, stopOrphanMonitor, } from "./services/orphan-monitor.js";
|
|
28
29
|
import { getAgentProcessManager } from "./services/agent-process-manager.js";
|
|
29
30
|
import { adapterRegistry } from "./adapter/index.js";
|
|
30
31
|
import { logger } from "./utils/logger.js";
|
|
31
|
-
import { autoRegister, unregister, bridgeRestartCleanup } from "./services/auto-register.js";
|
|
32
|
+
import { autoRegister, unregister, bridgeRestartCleanup, } from "./services/auto-register.js";
|
|
32
33
|
import { ensureAwsClientAgentMcpReleased } from "./services/aws-client-agent-mcp.js";
|
|
33
34
|
// 验证生产环境配置
|
|
34
35
|
validateProductionToken();
|
|
35
36
|
ensureAwsClientAgentMcpReleased();
|
|
37
|
+
logRuntimeBindingStartupState();
|
|
36
38
|
// ★ 启动时自动注册
|
|
37
39
|
async function performAutoRegister() {
|
|
38
40
|
try {
|
|
@@ -71,7 +73,7 @@ performAutoRegister()
|
|
|
71
73
|
// 这样可以确保 bridge 重启后,之前启动的 Agent 显示为"离线+停止"状态
|
|
72
74
|
return performBridgeRestartCleanup();
|
|
73
75
|
})
|
|
74
|
-
.catch(err => {
|
|
76
|
+
.catch((err) => {
|
|
75
77
|
logger.warn("[runtime-bridge] 自动注册或清理出错:", err);
|
|
76
78
|
});
|
|
77
79
|
// ★★★ Bridge 重启清理
|
|
@@ -109,13 +111,13 @@ async function rebuildProcessRegistry() {
|
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
113
|
const manager = getAgentProcessManager();
|
|
112
|
-
await manager.rebuildRegistry(persistedSessions.map(s => ({
|
|
114
|
+
await manager.rebuildRegistry(persistedSessions.map((s) => ({
|
|
113
115
|
agentId: s.agentId,
|
|
114
116
|
sessionId: s.sessionId,
|
|
115
117
|
pid: s.pid,
|
|
116
118
|
workspacePath: s.workspacePath,
|
|
117
119
|
command: s.command,
|
|
118
|
-
mode: s.mode ||
|
|
120
|
+
mode: s.mode || "pty",
|
|
119
121
|
})));
|
|
120
122
|
logger.info(`[runtime-bridge] 进程注册表重建完成: ${persistedSessions.length} 条记录`);
|
|
121
123
|
}
|
|
@@ -124,7 +126,7 @@ async function rebuildProcessRegistry() {
|
|
|
124
126
|
logger.warn("[runtime-bridge] 进程注册表重建失败:", err.message);
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
rebuildProcessRegistry().catch(err => {
|
|
129
|
+
rebuildProcessRegistry().catch((err) => {
|
|
128
130
|
logger.warn("[runtime-bridge] 进程注册表重建出错:", err);
|
|
129
131
|
});
|
|
130
132
|
// ★ 启动时检测孤儿进程并自动清理
|
|
@@ -134,13 +136,13 @@ rebuildProcessRegistry().catch(err => {
|
|
|
134
136
|
async function detectOrphansOnStartup() {
|
|
135
137
|
try {
|
|
136
138
|
// ★★★ 检查是否存在保留会话标记文件
|
|
137
|
-
const fs = await import(
|
|
138
|
-
const path = await import(
|
|
139
|
-
const os = await import(
|
|
140
|
-
const restartFlagFile = path.join(os.tmpdir(),
|
|
139
|
+
const fs = await import("fs/promises");
|
|
140
|
+
const path = await import("path");
|
|
141
|
+
const os = await import("os");
|
|
142
|
+
const restartFlagFile = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
|
|
141
143
|
let preserveSessions = false;
|
|
142
144
|
try {
|
|
143
|
-
const flagContent = await fs.readFile(restartFlagFile,
|
|
145
|
+
const flagContent = await fs.readFile(restartFlagFile, "utf-8");
|
|
144
146
|
const flag = JSON.parse(flagContent);
|
|
145
147
|
preserveSessions = flag.preserveSessions === true;
|
|
146
148
|
// 读取后删除标记文件
|
|
@@ -160,20 +162,20 @@ async function detectOrphansOnStartup() {
|
|
|
160
162
|
logger.info(`[runtime-bridge] ★ aws-mcp-server 重启恢复场景,保留 ${persistedSessions.length} 个会话`);
|
|
161
163
|
// 重建进程注册表
|
|
162
164
|
const manager = getAgentProcessManager();
|
|
163
|
-
await manager.rebuildRegistry(persistedSessions.map(s => ({
|
|
165
|
+
await manager.rebuildRegistry(persistedSessions.map((s) => ({
|
|
164
166
|
agentId: s.agentId,
|
|
165
167
|
sessionId: s.sessionId,
|
|
166
168
|
pid: s.pid,
|
|
167
169
|
workspacePath: s.workspacePath,
|
|
168
170
|
command: s.command,
|
|
169
|
-
mode: s.mode ||
|
|
171
|
+
mode: s.mode || "pty",
|
|
170
172
|
})));
|
|
171
173
|
return;
|
|
172
174
|
}
|
|
173
175
|
logger.info(`[runtime-bridge] ★ Bridge 重启,检测 ${persistedSessions.length} 个持久化会话的孤儿进程...`);
|
|
174
176
|
// ★★★ 修复:不再区分 SDK 和 PTY 模式,全部清理
|
|
175
177
|
// bridge 重启 = 整个运行环境重启,所有 agent 都应该被终止
|
|
176
|
-
const orphans = detectOrphanProcesses(persistedSessions.map(s => ({
|
|
178
|
+
const orphans = detectOrphanProcesses(persistedSessions.map((s) => ({
|
|
177
179
|
agentId: s.agentId,
|
|
178
180
|
pid: s.pid,
|
|
179
181
|
workspacePath: s.workspacePath,
|
|
@@ -190,13 +192,16 @@ async function detectOrphansOnStartup() {
|
|
|
190
192
|
try {
|
|
191
193
|
if (orphan.process.pid) {
|
|
192
194
|
// 使用跨平台方式终止进程
|
|
193
|
-
if (process.platform ===
|
|
194
|
-
const { execSync } = await import(
|
|
195
|
-
execSync(`taskkill /PID ${orphan.process.pid} /T /F`, {
|
|
195
|
+
if (process.platform === "win32") {
|
|
196
|
+
const { execSync } = await import("child_process");
|
|
197
|
+
execSync(`taskkill /PID ${orphan.process.pid} /T /F`, {
|
|
198
|
+
encoding: "utf8",
|
|
199
|
+
timeout: 3000,
|
|
200
|
+
});
|
|
196
201
|
logger.info(`[runtime-bridge] 已自动终止孤儿进程: agentId=${orphan.agentId}, PID=${orphan.process.pid}`);
|
|
197
202
|
}
|
|
198
203
|
else {
|
|
199
|
-
process.kill(orphan.process.pid,
|
|
204
|
+
process.kill(orphan.process.pid, "SIGKILL");
|
|
200
205
|
logger.info(`[runtime-bridge] 已自动终止孤儿进程: agentId=${orphan.agentId}, PID=${orphan.process.pid}`);
|
|
201
206
|
}
|
|
202
207
|
}
|
|
@@ -207,7 +212,7 @@ async function detectOrphansOnStartup() {
|
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
214
|
// 等待进程完全终止
|
|
210
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
211
216
|
}
|
|
212
217
|
// ★★★ 修复:bridge 重启后清空所有持久化会话
|
|
213
218
|
// 不再保留任何会话,因为所有 agent 都应该被清理
|
|
@@ -220,11 +225,11 @@ async function detectOrphansOnStartup() {
|
|
|
220
225
|
}
|
|
221
226
|
}
|
|
222
227
|
// 执行孤儿进程检测
|
|
223
|
-
detectOrphansOnStartup().catch(err => {
|
|
228
|
+
detectOrphansOnStartup().catch((err) => {
|
|
224
229
|
logger.warn("[runtime-bridge] 孤儿进程检测出错:", err);
|
|
225
230
|
});
|
|
226
231
|
// ★ 启动孤儿进程定期监控
|
|
227
|
-
startOrphanMonitor().catch(err => {
|
|
232
|
+
startOrphanMonitor().catch((err) => {
|
|
228
233
|
logger.warn("[runtime-bridge] 启动孤儿进程监控失败:", err);
|
|
229
234
|
});
|
|
230
235
|
// 创建 Express 应用
|
|
@@ -241,6 +246,7 @@ app.use(cors({
|
|
|
241
246
|
}));
|
|
242
247
|
app.use(express.json({ limit: "1mb" }));
|
|
243
248
|
// 注册路由
|
|
249
|
+
app.use("/runtime", runtimeBindingRouter);
|
|
244
250
|
app.use("/runtime", propertiesRouter);
|
|
245
251
|
app.use("/runtime", ymlRouter);
|
|
246
252
|
app.use("/runtime", instanceRouter);
|
|
@@ -345,7 +351,7 @@ async function gracefulShutdown(signal, server, preserveSessions = false) {
|
|
|
345
351
|
const providerId = entry.providerId || "claude-code";
|
|
346
352
|
const adapter = adapterRegistry.get(providerId);
|
|
347
353
|
// 尝试获取 PID 用于日志
|
|
348
|
-
let pidInfo =
|
|
354
|
+
let pidInfo = "";
|
|
349
355
|
try {
|
|
350
356
|
const pid = adapter.getSessionPid?.(sessionId);
|
|
351
357
|
if (pid) {
|
|
@@ -371,14 +377,14 @@ async function gracefulShutdown(signal, server, preserveSessions = false) {
|
|
|
371
377
|
stopOrphanMonitor();
|
|
372
378
|
// 4. ★ 验证所有进程已终止(等待一小段时间让进程完全退出)
|
|
373
379
|
logger.info("[runtime-bridge] 等待进程完全终止...");
|
|
374
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
380
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
375
381
|
// 5. 检查是否还有残留进程,如果有则强制终止
|
|
376
382
|
try {
|
|
377
383
|
const { detectOrphanProcesses, terminateProcessTree } = await import("./services/process-detector.js");
|
|
378
384
|
const { loadPersistedSessions } = await import("./services/terminal-persistence.js");
|
|
379
385
|
const persistedSessions = await loadPersistedSessions();
|
|
380
386
|
if (persistedSessions.length > 0) {
|
|
381
|
-
const orphans = detectOrphanProcesses(persistedSessions.map(s => ({
|
|
387
|
+
const orphans = detectOrphanProcesses(persistedSessions.map((s) => ({
|
|
382
388
|
agentId: s.agentId,
|
|
383
389
|
pid: s.pid,
|
|
384
390
|
workspacePath: s.workspacePath,
|
|
@@ -455,11 +461,11 @@ const server = startServer();
|
|
|
455
461
|
*
|
|
456
462
|
* 信号处理器必须使用同步 I/O,因为异步操作在信号上下文中不可靠。
|
|
457
463
|
*/
|
|
458
|
-
const RESTART_FLAG_FILE = path.join(os.tmpdir(),
|
|
464
|
+
const RESTART_FLAG_FILE = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
|
|
459
465
|
function checkPreserveFlagSync() {
|
|
460
466
|
try {
|
|
461
467
|
if (existsSync(RESTART_FLAG_FILE)) {
|
|
462
|
-
const content = readFileSync(RESTART_FLAG_FILE,
|
|
468
|
+
const content = readFileSync(RESTART_FLAG_FILE, "utf-8");
|
|
463
469
|
const flag = JSON.parse(content);
|
|
464
470
|
return flag.preserveSessions === true;
|
|
465
471
|
}
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 验证请求头中的 X-Runtime-Token 是否与配置的 runtimeToken 匹配
|
|
5
5
|
*/
|
|
6
|
-
import type { Request, Response, NextFunction } from
|
|
6
|
+
import type { Request, Response, NextFunction } from "express";
|
|
7
7
|
/**
|
|
8
8
|
* Token 验证中间件
|
|
9
|
-
* 检查请求头中的 X-Runtime-Token
|
|
9
|
+
* 检查请求头中的 X-Runtime-Token 或 Authorization: Bearer 是否有效。
|
|
10
|
+
*
|
|
11
|
+
* 优先支持面板/服务器绑定时写入的动态令牌;仍兼容环境变量配置的静态令牌。
|
|
10
12
|
*/
|
|
11
13
|
export declare function validateToken(req: Request, res: Response, next: NextFunction): void;
|
|
12
14
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA8B/D;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,IAAI,CAqBN"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -3,16 +3,46 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 验证请求头中的 X-Runtime-Token 是否与配置的 runtimeToken 匹配
|
|
5
5
|
*/
|
|
6
|
-
import { runtimeToken } from
|
|
6
|
+
import { allowDefaultRuntimeToken, runtimeToken } from "../config.js";
|
|
7
|
+
import { hasRuntimeBinding, validateRuntimeBindingToken, } from "../services/runtime-binding.js";
|
|
8
|
+
function extractRuntimeToken(req) {
|
|
9
|
+
const headerToken = req.header("X-Runtime-Token");
|
|
10
|
+
if (headerToken) {
|
|
11
|
+
return headerToken.trim();
|
|
12
|
+
}
|
|
13
|
+
const authorization = req.header("Authorization") || "";
|
|
14
|
+
const bearerMatch = authorization.match(/^Bearer\s+(.+)$/i);
|
|
15
|
+
return bearerMatch?.[1]?.trim() || "";
|
|
16
|
+
}
|
|
17
|
+
function validateConfiguredToken(token) {
|
|
18
|
+
if (!token) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (process.env.AWS_RUNTIME_CALLBACK_TOKEN) {
|
|
22
|
+
return token === runtimeToken;
|
|
23
|
+
}
|
|
24
|
+
return allowDefaultRuntimeToken && token === runtimeToken;
|
|
25
|
+
}
|
|
7
26
|
/**
|
|
8
27
|
* Token 验证中间件
|
|
9
|
-
* 检查请求头中的 X-Runtime-Token
|
|
28
|
+
* 检查请求头中的 X-Runtime-Token 或 Authorization: Bearer 是否有效。
|
|
29
|
+
*
|
|
30
|
+
* 优先支持面板/服务器绑定时写入的动态令牌;仍兼容环境变量配置的静态令牌。
|
|
10
31
|
*/
|
|
11
32
|
export function validateToken(req, res, next) {
|
|
12
|
-
const token = req
|
|
13
|
-
if (token
|
|
14
|
-
|
|
33
|
+
const token = extractRuntimeToken(req);
|
|
34
|
+
if (validateRuntimeBindingToken(token) || validateConfiguredToken(token)) {
|
|
35
|
+
next();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!hasRuntimeBinding() &&
|
|
39
|
+
!process.env.AWS_RUNTIME_CALLBACK_TOKEN &&
|
|
40
|
+
!allowDefaultRuntimeToken) {
|
|
41
|
+
res.status(401).json({
|
|
42
|
+
error: "runtime_bridge_unpaired",
|
|
43
|
+
message: "Runtime bridge is not paired. Add this instance from the panel first.",
|
|
44
|
+
});
|
|
15
45
|
return;
|
|
16
46
|
}
|
|
17
|
-
|
|
47
|
+
res.status(401).json({ error: "unauthorized" });
|
|
18
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAwBA,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC"}
|
package/dist/routes/instance.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Router } from
|
|
2
|
-
import axios from
|
|
3
|
-
import { validateToken } from
|
|
4
|
-
import { schedulerBaseUrl, runtimeToken } from
|
|
5
|
-
import { loadInstanceState, saveInstanceState } from
|
|
6
|
-
import { initInstance } from
|
|
7
|
-
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS } from
|
|
8
|
-
import { createLogger } from
|
|
9
|
-
const log = createLogger(
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { validateToken } from "../middleware/auth.js";
|
|
4
|
+
import { schedulerBaseUrl, runtimeToken } from "../config.js";
|
|
5
|
+
import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
|
|
6
|
+
import { initInstance } from "../services/instance-init-service.js";
|
|
7
|
+
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, } from "../services/tool-installer.js";
|
|
8
|
+
import { createLogger } from "../utils/logger.js";
|
|
9
|
+
const log = createLogger("instance");
|
|
10
10
|
// ★★★ 导出 gracefulShutdown 的触发函数
|
|
11
11
|
// 用于 aws-mcp-server 通知 bridge 保留会话状态关闭
|
|
12
12
|
let gracefulShutdownFn = null;
|
|
@@ -14,12 +14,18 @@ export function setGracefulShutdownFn(fn) {
|
|
|
14
14
|
gracefulShutdownFn = fn;
|
|
15
15
|
}
|
|
16
16
|
export const instanceRouter = Router();
|
|
17
|
-
instanceRouter.get(
|
|
17
|
+
instanceRouter.get("/healthz", (_req, res) => {
|
|
18
|
+
res.json({
|
|
19
|
+
ok: true,
|
|
20
|
+
runtimeBridge: "healthy",
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
instanceRouter.get("/ping", validateToken, async (_req, res) => {
|
|
18
24
|
try {
|
|
19
|
-
const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: {
|
|
25
|
+
const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeToken } });
|
|
20
26
|
res.json({
|
|
21
27
|
ok: true,
|
|
22
|
-
runtimeBridge:
|
|
28
|
+
runtimeBridge: "healthy",
|
|
23
29
|
schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
|
|
24
30
|
schedulerBaseUrl,
|
|
25
31
|
});
|
|
@@ -28,15 +34,15 @@ instanceRouter.get('/ping', validateToken, async (_req, res) => {
|
|
|
28
34
|
const err = error;
|
|
29
35
|
res.status(502).json({
|
|
30
36
|
ok: false,
|
|
31
|
-
error: err?.message ||
|
|
37
|
+
error: err?.message || "scheduler ping failed",
|
|
32
38
|
schedulerBaseUrl,
|
|
33
39
|
});
|
|
34
40
|
}
|
|
35
41
|
});
|
|
36
|
-
instanceRouter.post(
|
|
42
|
+
instanceRouter.post("/init-instance", validateToken, async (req, res) => {
|
|
37
43
|
const { agentId, workspacePath, skillEnabled, mcpEnabled, ccSwitchEnabledTools, skillPackage, skillPackages, mcpServers, } = req.body || {};
|
|
38
44
|
if (!agentId || !workspacePath) {
|
|
39
|
-
res.status(400).json({ error:
|
|
45
|
+
res.status(400).json({ error: "agentId and workspacePath are required" });
|
|
40
46
|
return;
|
|
41
47
|
}
|
|
42
48
|
try {
|
|
@@ -53,39 +59,51 @@ instanceRouter.post('/init-instance', validateToken, async (req, res) => {
|
|
|
53
59
|
catch (error) {
|
|
54
60
|
const err = error;
|
|
55
61
|
res.status(400).json({
|
|
56
|
-
error: err?.message ||
|
|
62
|
+
error: err?.message || "initialize instance failed",
|
|
57
63
|
});
|
|
58
64
|
}
|
|
59
65
|
});
|
|
60
|
-
instanceRouter.post(
|
|
66
|
+
instanceRouter.post("/cc-switch/state", validateToken, async (req, res) => {
|
|
61
67
|
const { agentId } = req.body || {};
|
|
62
68
|
if (!agentId) {
|
|
63
|
-
res.status(400).json({ error:
|
|
69
|
+
res.status(400).json({ error: "agentId is required" });
|
|
64
70
|
return;
|
|
65
71
|
}
|
|
66
72
|
try {
|
|
67
73
|
const state = await loadInstanceState(agentId);
|
|
68
74
|
const toolStatus = await detectToolStatuses(state.enabledTools || []);
|
|
69
|
-
res.json({
|
|
75
|
+
res.json({
|
|
76
|
+
ok: true,
|
|
77
|
+
agentId: String(agentId),
|
|
78
|
+
state: { ...state, toolStatus },
|
|
79
|
+
});
|
|
70
80
|
}
|
|
71
81
|
catch (error) {
|
|
72
82
|
const err = error;
|
|
73
|
-
res.status(400).json({ error: err?.message ||
|
|
83
|
+
res.status(400).json({ error: err?.message || "load state failed" });
|
|
74
84
|
}
|
|
75
85
|
});
|
|
76
|
-
instanceRouter.post(
|
|
86
|
+
instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res) => {
|
|
77
87
|
const { agentId, tools } = req.body || {};
|
|
78
88
|
if (!agentId) {
|
|
79
|
-
res.status(400).json({ error:
|
|
89
|
+
res.status(400).json({ error: "agentId is required" });
|
|
80
90
|
return;
|
|
81
91
|
}
|
|
82
92
|
const requestedTools = Array.isArray(tools)
|
|
83
|
-
? tools
|
|
93
|
+
? tools
|
|
94
|
+
.map((tool) => String(tool || "")
|
|
95
|
+
.trim()
|
|
96
|
+
.toLowerCase())
|
|
97
|
+
.filter(Boolean)
|
|
84
98
|
: [];
|
|
85
99
|
const supportedInstallableTools = new Set(SUPPORTED_INSTALLABLE_TOOLS);
|
|
86
100
|
const installableTools = requestedTools.filter((tool) => supportedInstallableTools.has(tool));
|
|
87
101
|
if (installableTools.length === 0) {
|
|
88
|
-
res
|
|
102
|
+
res
|
|
103
|
+
.status(400)
|
|
104
|
+
.json({
|
|
105
|
+
error: `tools must include ${SUPPORTED_INSTALLABLE_TOOLS.join(", ")}`,
|
|
106
|
+
});
|
|
89
107
|
return;
|
|
90
108
|
}
|
|
91
109
|
try {
|
|
@@ -111,7 +129,7 @@ instanceRouter.post('/cc-switch/install-tools', validateToken, async (req, res)
|
|
|
111
129
|
}
|
|
112
130
|
catch (error) {
|
|
113
131
|
const err = error;
|
|
114
|
-
res.status(400).json({ error: err?.message ||
|
|
132
|
+
res.status(400).json({ error: err?.message || "install tools failed" });
|
|
115
133
|
}
|
|
116
134
|
});
|
|
117
135
|
/**
|
|
@@ -126,22 +144,22 @@ instanceRouter.post('/cc-switch/install-tools', validateToken, async (req, res)
|
|
|
126
144
|
* - preserveSessions: true = 保留会话(aws-mcp-server 重启)
|
|
127
145
|
* false = 清理会话(bridge 自己关闭)
|
|
128
146
|
*/
|
|
129
|
-
instanceRouter.post(
|
|
147
|
+
instanceRouter.post("/prepare-restart", validateToken, async (req, res) => {
|
|
130
148
|
const { preserveSessions = true } = req.body || {};
|
|
131
149
|
try {
|
|
132
150
|
log.info(`[prepare-restart] 收到准备重启通知,preserveSessions=${preserveSessions}`);
|
|
133
151
|
// 记录重启模式到文件,供下次启动时判断
|
|
134
|
-
const fs = await import(
|
|
135
|
-
const path = await import(
|
|
136
|
-
const os = await import(
|
|
137
|
-
const restartFlagFile = path.join(os.tmpdir(),
|
|
152
|
+
const fs = await import("fs/promises");
|
|
153
|
+
const path = await import("path");
|
|
154
|
+
const os = await import("os");
|
|
155
|
+
const restartFlagFile = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
|
|
138
156
|
if (preserveSessions) {
|
|
139
157
|
// 创建标记文件,表示 aws-mcp-server 重启,需要保留会话
|
|
140
158
|
await fs.mkdir(path.dirname(restartFlagFile), { recursive: true });
|
|
141
159
|
await fs.writeFile(restartFlagFile, JSON.stringify({
|
|
142
160
|
preserveSessions: true,
|
|
143
161
|
timestamp: new Date().toISOString(),
|
|
144
|
-
}),
|
|
162
|
+
}), "utf-8");
|
|
145
163
|
log.info(`[prepare-restart] 已创建保留会话标记文件: ${restartFlagFile}`);
|
|
146
164
|
}
|
|
147
165
|
else {
|
|
@@ -158,13 +176,13 @@ instanceRouter.post('/prepare-restart', validateToken, async (req, res) => {
|
|
|
158
176
|
ok: true,
|
|
159
177
|
preserveSessions,
|
|
160
178
|
message: preserveSessions
|
|
161
|
-
?
|
|
162
|
-
:
|
|
179
|
+
? "Bridge will preserve sessions for aws-mcp-server restart"
|
|
180
|
+
: "Bridge will clean up sessions on shutdown",
|
|
163
181
|
});
|
|
164
182
|
}
|
|
165
183
|
catch (error) {
|
|
166
184
|
const err = error;
|
|
167
|
-
log.error(
|
|
185
|
+
log.error("[prepare-restart] Error:", err);
|
|
168
186
|
res.status(500).json({ error: err.message });
|
|
169
187
|
}
|
|
170
188
|
});
|