Scope channel settings to request profile

This commit is contained in:
ekko
2026-05-24 08:37:50 +08:00
committed by ekko
parent 4f8bda9836
commit be2089e423
4 changed files with 162 additions and 99 deletions
@@ -1,8 +1,9 @@
import { readFile } from 'fs/promises'
import { getActiveConfigPath, getActiveEnvPath, getActiveProfileName } from '../../services/hermes/hermes-profile'
import { join } from 'path'
import { getActiveProfileName, getProfileDir } from '../../services/hermes/hermes-profile'
import { AgentBridgeClient } from '../../services/hermes/agent-bridge'
import { restartGateway } from '../../services/hermes/hermes-cli'
import { saveEnvValue } from '../../services/config-helpers'
import { restartGatewayForProfile } from '../../services/hermes/gateway-autostart'
import { saveEnvValueForProfile } from '../../services/config-helpers'
import { logger } from '../../services/logger'
import { safeFileStore } from '../../services/safe-file-store'
@@ -12,8 +13,12 @@ const PLATFORM_SECTIONS = new Set([
'approvals',
])
const configPath = () => getActiveConfigPath()
const envPath = () => getActiveEnvPath()
function requestedProfile(ctx: any): string {
return ctx.state?.profile?.name || getActiveProfileName() || 'default'
}
const configPath = (profile: string) => join(getProfileDir(profile), 'config.yaml')
const envPath = (profile: string) => join(getProfileDir(profile), '.env')
const envPlatformMap: Record<string, [string, string]> = {
TELEGRAM_BOT_TOKEN: ['telegram', 'token'],
@@ -88,9 +93,9 @@ async function destroyBridgeProfile(profile: string): Promise<void> {
}
}
async function readEnvPlatforms(): Promise<Record<string, any>> {
async function readEnvPlatforms(profile: string): Promise<Record<string, any>> {
try {
const raw = await readFile(envPath(), 'utf-8')
const raw = await readFile(envPath(profile), 'utf-8')
const env = parseEnv(raw)
const platforms: Record<string, any> = {}
for (const [envKey, [platform, cfgPath]] of Object.entries(envPlatformMap)) {
@@ -105,14 +110,15 @@ async function readEnvPlatforms(): Promise<Record<string, any>> {
} catch { return {} }
}
async function readConfig(): Promise<Record<string, any>> {
return safeFileStore.readYaml(configPath())
async function readConfig(profile: string): Promise<Record<string, any>> {
return safeFileStore.readYaml(configPath(profile))
}
export async function getConfig(ctx: any) {
try {
const config = await readConfig()
const envPlatforms = await readEnvPlatforms()
const profile = requestedProfile(ctx)
const config = await readConfig(profile)
const envPlatforms = await readEnvPlatforms(profile)
if (Object.keys(envPlatforms).length > 0) {
const existing = config.platforms || {}
for (const [platform, vals] of Object.entries(envPlatforms)) {
@@ -142,7 +148,8 @@ export async function updateConfig(ctx: any) {
ctx.status = 400; ctx.body = { error: 'Missing section or values' }; return
}
try {
await safeFileStore.updateYaml(configPath(), (config) => {
const profile = requestedProfile(ctx)
await safeFileStore.updateYaml(configPath(profile), (config) => {
config[section] = deepMerge(config[section] || {}, values)
return config
}, {
@@ -155,11 +162,10 @@ export async function updateConfig(ctx: any) {
// Platform adapters still run through Hermes gateway; restart it so channel
// config changes (Feishu/Weixin/etc.) are applied, then refresh bridge sessions.
if (restart !== false && PLATFORM_SECTIONS.has(section)) {
const activeProfile = getActiveProfileName()
try {
const restartResult = await restartGateway()
logger.info('[config] gateway restarted after config update section=%s profile=%s result=%s', section, activeProfile, restartResult)
await destroyBridgeProfile(activeProfile)
const restartResult = await restartGatewayForProfile(profile)
logger.info('[config] gateway restarted after config update section=%s profile=%s result=%j', section, profile, restartResult)
await destroyBridgeProfile(profile)
} catch (err) {
logger.error(err, 'Gateway restart failed')
ctx.status = 500
@@ -180,6 +186,7 @@ export async function updateCredentials(ctx: any) {
ctx.status = 400; ctx.body = { error: 'Missing platform or values' }; return
}
try {
const profile = requestedProfile(ctx)
const envMap = platformEnvMap[platform]
if (!envMap) {
ctx.status = 400; ctx.body = { error: `Unknown platform: ${platform}` }; return
@@ -190,12 +197,12 @@ export async function updateCredentials(ctx: any) {
for (const [subKey, subVal] of Object.entries(val as Record<string, any>)) { flatValues[`extra.${subKey}`] = subVal }
} else { flatValues[key] = val }
}
await safeFileStore.updateYaml(configPath(), async (config) => {
await safeFileStore.updateYaml(configPath(profile), async (config) => {
for (const [cfgPath, val] of Object.entries(flatValues)) {
const envVar = envMap[cfgPath]
if (!envVar) continue
if (val === undefined || val === null || val === '') {
await saveEnvValue(envVar, '')
await saveEnvValueForProfile(profile, envVar, '')
const parts = cfgPath.split('.')
let obj: any = config.platforms?.[platform]
if (obj) {
@@ -209,7 +216,7 @@ export async function updateCredentials(ctx: any) {
if (Object.keys(obj).length === 0) { if (!config.platforms) config.platforms = {}; delete config.platforms[platform] }
}
} else {
await saveEnvValue(envVar, String(val))
await saveEnvValueForProfile(profile, envVar, String(val))
}
}
return config
@@ -222,11 +229,10 @@ export async function updateCredentials(ctx: any) {
// Platform adapters still run through Hermes gateway; restart it so channel
// credentials are applied, then refresh bridge sessions.
const activeProfile = getActiveProfileName()
try {
const restartResult = await restartGateway()
logger.info('[config] gateway restarted after credentials update platform=%s profile=%s result=%s', platform, activeProfile, restartResult)
await destroyBridgeProfile(activeProfile)
const restartResult = await restartGatewayForProfile(profile)
logger.info('[config] gateway restarted after credentials update platform=%s profile=%s result=%j', platform, profile, restartResult)
await destroyBridgeProfile(profile)
} catch (err) {
logger.error(err, 'Gateway restart failed')
ctx.status = 500
@@ -1,11 +1,13 @@
import axios from 'axios'
import { chmod } from 'fs/promises'
import { getActiveEnvPath } from '../../services/hermes/hermes-profile'
import { restartGateway } from '../../services/hermes/hermes-cli'
import { safeFileStore } from '../../services/safe-file-store'
import { getActiveProfileName } from '../../services/hermes/hermes-profile'
import { restartGatewayForProfile } from '../../services/hermes/gateway-autostart'
import { saveEnvValueForProfile } from '../../services/config-helpers'
const ILINK_BASE = 'https://ilinkai.weixin.qq.com'
const envPath = () => getActiveEnvPath()
function requestedProfile(ctx: any): string {
return ctx.state?.profile?.name || getActiveProfileName() || 'default'
}
export async function getQrcode(ctx: any) {
try {
@@ -39,28 +41,13 @@ export async function save(ctx: any) {
const { account_id, token, base_url } = ctx.request.body as { account_id: string; token: string; base_url?: string }
if (!account_id || !token) { ctx.status = 400; ctx.body = { error: 'Missing account_id or token' }; return }
try {
const profile = requestedProfile(ctx)
const entries: Record<string, string> = { WEIXIN_ACCOUNT_ID: account_id, WEIXIN_TOKEN: token }
if (base_url) entries.WEIXIN_BASE_URL = base_url
const ep = envPath()
await safeFileStore.updateText(ep, (raw) => {
const lines = raw.split('\n')
const existingKeys = new Set<string>()
const result: string[] = []
for (const line of lines) {
const trimmed = line.trim()
if (trimmed.startsWith('#')) { result.push(line); continue }
const eqIdx = trimmed.indexOf('=')
if (eqIdx !== -1) {
const key = trimmed.slice(0, eqIdx).trim()
if (key in entries) { result.push(`${key}=${entries[key]}`); existingKeys.add(key); continue }
}
result.push(line)
}
for (const [key, val] of Object.entries(entries)) { if (!existingKeys.has(key)) { result.push(`${key}=${val}`) } }
return result.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\n+$/, '') + '\n'
})
try { await chmod(ep, 0o600) } catch { }
await restartGateway()
for (const [key, val] of Object.entries(entries)) {
await saveEnvValueForProfile(profile, key, val)
}
await restartGatewayForProfile(profile)
ctx.body = { success: true }
} catch (err: any) {
ctx.status = 500; ctx.body = { error: err.message }