mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-05 07:40:16 +08:00
fix(channels): stabilize default account selection
This commit is contained in:
@@ -2564,8 +2564,20 @@ const MESSAGING_CREDENTIAL_FIELDS = [
|
|||||||
'password',
|
'password',
|
||||||
'signingSecret',
|
'signingSecret',
|
||||||
'token',
|
'token',
|
||||||
|
'tokenFile',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function hasConfiguredMessagingValue(value) {
|
||||||
|
if (typeof value === 'string') return value.trim().length > 0
|
||||||
|
if (normalizeSecretRef(value)) return true
|
||||||
|
return value !== undefined && value !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function channelRootHasMessagingCredential(root) {
|
||||||
|
if (!root || typeof root !== 'object' || Array.isArray(root)) return false
|
||||||
|
return MESSAGING_CREDENTIAL_FIELDS.some(key => hasConfiguredMessagingValue(root[key]))
|
||||||
|
}
|
||||||
|
|
||||||
function preserveMessagingCredentialRefs(entry, form, current) {
|
function preserveMessagingCredentialRefs(entry, form, current) {
|
||||||
delete entry.__secretRefs
|
delete entry.__secretRefs
|
||||||
for (const key of MESSAGING_CREDENTIAL_FIELDS) {
|
for (const key of MESSAGING_CREDENTIAL_FIELDS) {
|
||||||
@@ -3083,12 +3095,19 @@ function mergeMessagingAccountEntry(cfg, storageKey, accountId, entry) {
|
|||||||
const root = existingRoot && typeof existingRoot === 'object' && !Array.isArray(existingRoot)
|
const root = existingRoot && typeof existingRoot === 'object' && !Array.isArray(existingRoot)
|
||||||
? existingRoot
|
? existingRoot
|
||||||
: { enabled: true }
|
: { enabled: true }
|
||||||
|
const accountsBefore = root.accounts && typeof root.accounts === 'object' && !Array.isArray(root.accounts)
|
||||||
|
? Object.keys(root.accounts).filter(Boolean)
|
||||||
|
: []
|
||||||
|
const shouldSetDefaultAccount = !String(root.defaultAccount || '').trim()
|
||||||
|
&& !channelRootHasMessagingCredential(root)
|
||||||
|
&& accountsBefore.length === 0
|
||||||
root.enabled = true
|
root.enabled = true
|
||||||
if (!root.accounts || typeof root.accounts !== 'object' || Array.isArray(root.accounts)) root.accounts = {}
|
if (!root.accounts || typeof root.accounts !== 'object' || Array.isArray(root.accounts)) root.accounts = {}
|
||||||
const existingAccount = root.accounts[accountId]
|
const existingAccount = root.accounts[accountId]
|
||||||
root.accounts[accountId] = existingAccount && typeof existingAccount === 'object' && !Array.isArray(existingAccount)
|
root.accounts[accountId] = existingAccount && typeof existingAccount === 'object' && !Array.isArray(existingAccount)
|
||||||
? { ...existingAccount, ...entry }
|
? { ...existingAccount, ...entry }
|
||||||
: entry
|
: entry
|
||||||
|
if (shouldSetDefaultAccount) root.defaultAccount = accountId
|
||||||
cfg.channels[storageKey] = root
|
cfg.channels[storageKey] = root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ fn preserve_messaging_credential_refs(
|
|||||||
"password",
|
"password",
|
||||||
"signingSecret",
|
"signingSecret",
|
||||||
"token",
|
"token",
|
||||||
|
"tokenFile",
|
||||||
] {
|
] {
|
||||||
if !form_obj.contains_key(key) {
|
if !form_obj.contains_key(key) {
|
||||||
continue;
|
continue;
|
||||||
@@ -162,6 +163,36 @@ fn preserve_messaging_credential_refs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_configured_messaging_value(value: Option<&Value>) -> bool {
|
||||||
|
match value {
|
||||||
|
Some(Value::String(raw)) => !raw.trim().is_empty(),
|
||||||
|
Some(value) if secret_ref_parts(value).is_some() => true,
|
||||||
|
Some(Value::Null) | None => false,
|
||||||
|
Some(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn channel_root_has_messaging_credential(root: &Map<String, Value>) -> bool {
|
||||||
|
[
|
||||||
|
"accessToken",
|
||||||
|
"appId",
|
||||||
|
"appPassword",
|
||||||
|
"appSecret",
|
||||||
|
"appToken",
|
||||||
|
"botToken",
|
||||||
|
"clientId",
|
||||||
|
"clientSecret",
|
||||||
|
"gatewayPassword",
|
||||||
|
"gatewayToken",
|
||||||
|
"password",
|
||||||
|
"signingSecret",
|
||||||
|
"token",
|
||||||
|
"tokenFile",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|key| has_configured_messaging_value(root.get(*key)))
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_bool_as_string(form: &mut Map<String, Value>, source: &Value, key: &str) {
|
fn insert_bool_as_string(form: &mut Map<String, Value>, source: &Value, key: &str) {
|
||||||
if let Some(v) = source.get(key).and_then(|v| v.as_bool()) {
|
if let Some(v) = source.get(key).and_then(|v| v.as_bool()) {
|
||||||
form.insert(
|
form.insert(
|
||||||
@@ -454,6 +485,19 @@ fn merge_account_channel_entry(
|
|||||||
let channel_obj = channel
|
let channel_obj = channel
|
||||||
.as_object_mut()
|
.as_object_mut()
|
||||||
.ok_or(format!("{} 节点格式错误", key))?;
|
.ok_or(format!("{} 节点格式错误", key))?;
|
||||||
|
let accounts_before = channel_obj
|
||||||
|
.get("accounts")
|
||||||
|
.and_then(|value| value.as_object())
|
||||||
|
.map(|accounts| accounts.keys().filter(|id| !id.is_empty()).count())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let should_set_default_account = channel_obj
|
||||||
|
.get("defaultAccount")
|
||||||
|
.and_then(|value| value.as_str())
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.is_none()
|
||||||
|
&& !channel_root_has_messaging_credential(channel_obj)
|
||||||
|
&& accounts_before == 0;
|
||||||
channel_obj.insert("enabled".into(), Value::Bool(true));
|
channel_obj.insert("enabled".into(), Value::Bool(true));
|
||||||
let accounts = channel_obj.entry("accounts").or_insert_with(|| json!({}));
|
let accounts = channel_obj.entry("accounts").or_insert_with(|| json!({}));
|
||||||
let accounts_obj = accounts.as_object_mut().ok_or("accounts 格式错误")?;
|
let accounts_obj = accounts.as_object_mut().ok_or("accounts 格式错误")?;
|
||||||
@@ -467,6 +511,12 @@ fn merge_account_channel_entry(
|
|||||||
new_entry
|
new_entry
|
||||||
};
|
};
|
||||||
accounts_obj.insert(account_id.to_string(), Value::Object(merged));
|
accounts_obj.insert(account_id.to_string(), Value::Object(merged));
|
||||||
|
if should_set_default_account {
|
||||||
|
channel_obj.insert(
|
||||||
|
"defaultAccount".into(),
|
||||||
|
Value::String(account_id.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -275,3 +275,58 @@ test('OpenClaw 渠道保存带账号标识时会写入 accounts 而不是覆盖
|
|||||||
assert.equal(cfg.channels.slack.accounts['team-a'].botToken, 'team-slack')
|
assert.equal(cfg.channels.slack.accounts['team-a'].botToken, 'team-slack')
|
||||||
assert.equal(cfg.channels.slack.accounts['team-a'].appToken, 'team-app')
|
assert.equal(cfg.channels.slack.accounts['team-a'].appToken, 'team-app')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('OpenClaw 渠道保存第一个命名账号时会固定 defaultAccount', () => {
|
||||||
|
const cfg = { channels: {} }
|
||||||
|
|
||||||
|
mergeOpenClawMessagingPlatformConfig(cfg, {
|
||||||
|
platform: 'telegram',
|
||||||
|
accountId: 'alerts',
|
||||||
|
form: { botToken: 'alerts-token' },
|
||||||
|
})
|
||||||
|
mergeOpenClawMessagingPlatformConfig(cfg, {
|
||||||
|
platform: 'telegram',
|
||||||
|
accountId: 'ops',
|
||||||
|
form: { botToken: 'ops-token' },
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(cfg.channels.telegram.defaultAccount, 'alerts')
|
||||||
|
assert.equal(cfg.channels.telegram.accounts.alerts.botToken, 'alerts-token')
|
||||||
|
assert.equal(cfg.channels.telegram.accounts.ops.botToken, 'ops-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('OpenClaw 渠道保存命名账号时不会覆盖已有默认账号或根凭证默认账号', () => {
|
||||||
|
const explicitDefault = {
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
defaultAccount: 'ops',
|
||||||
|
accounts: { ops: { token: 'ops-token' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mergeOpenClawMessagingPlatformConfig(explicitDefault, {
|
||||||
|
platform: 'discord',
|
||||||
|
accountId: 'alerts',
|
||||||
|
form: { token: 'alerts-token' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const rootDefault = {
|
||||||
|
channels: {
|
||||||
|
slack: {
|
||||||
|
mode: 'socket',
|
||||||
|
botToken: 'root-bot',
|
||||||
|
appToken: 'root-app',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mergeOpenClawMessagingPlatformConfig(rootDefault, {
|
||||||
|
platform: 'slack',
|
||||||
|
accountId: 'team-a',
|
||||||
|
form: { mode: 'socket', botToken: 'team-bot', appToken: 'team-app' },
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(explicitDefault.channels.discord.defaultAccount, 'ops')
|
||||||
|
assert.equal(explicitDefault.channels.discord.accounts.alerts.token, 'alerts-token')
|
||||||
|
assert.equal(rootDefault.channels.slack.defaultAccount, undefined)
|
||||||
|
assert.equal(rootDefault.channels.slack.accounts['team-a'].botToken, 'team-bot')
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user