Fix WhatsApp access settings showing empty grant state (4.9.1-ops.6).

Client normalizeBot dropped accessGrant/groupSessionScope, so pending, contacts, and group cards never appeared. Also record contacts only on DM/@, auto-create group buckets on @, and add one-click grant buttons.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-05 17:05:16 +08:00
parent a0c49707a1
commit 93672dfd91
8 changed files with 685 additions and 444 deletions

View file

@ -457,16 +457,76 @@ export function AccessGrantSettingsPage({ channel, account, rpcCall, onSaved })
}, '添加群'))),
h('fieldset', { className: 'dim-accessScene', disabled: saving },
h('legend', null, '最近联系人(自动沉淀)'),
h('p', { className: 'dim-accessUsersEmpty' },
'仅记录私聊机器人,或在群里 @ 机器人的人。电话是唯一授权键,可一键加入私聊/本群授权。'),
contacts.length === 0
? h('div', { className: 'dim-accessUsersEmpty' }, '暂无联系人。私聊或群内触发后会出现在此,便于补齐电话与昵称。')
: h('ul', { className: 'dim-accessUserList' }, contacts.slice(0, 30).map((contact) =>
h('li', {
? h('div', { className: 'dim-accessUsersEmpty' }, '暂无联系人。有人私聊或 @ 机器人后会出现在此。')
: h('ul', { className: 'dim-accessUserList' }, contacts.slice(0, 30).map((contact) => {
const phone = contact.phone || '';
const alreadyDirect = phone && draft.directMembers.some((m) => m.phone === phone);
const groupTargets = (contact.groupJids ?? []).filter((jid) => knownGroupJids.includes(jid));
return h('li', {
key: `${contact.phone ?? ''}-${(contact.lids ?? []).join(',')}`,
className: 'dim-accessUserRow',
},
h('span', null, contactLabel(contact)),
h('span', null, (contact.scenes ?? []).join('/')),
contact.phone ? null : h('span', null, '待补电话'))))),
h('div', { className: 'dim-accessField' },
h('strong', null, contactLabel(contact)),
h('span', null, (contact.scenes ?? []).join(' / ')),
phone ? null : h('span', null, '待补电话')),
phone ? h('button', {
type: 'button',
className: 'dim-deliveryButton',
'data-kind': 'primary',
disabled: saving || alreadyDirect,
onClick: () => {
setDraft((current) => {
if (current.directMembers.some((m) => m.phone === phone)) return current;
return {
...current,
directMembers: [
...current.directMembers,
{ phone, canExecuteCommands: true },
],
};
});
setFeedback({ tone: 'success', message: '已加入私聊授权,记得点保存。' });
},
}, alreadyDirect ? '已在私聊' : '加私聊') : null,
...groupTargets.map((groupJid) => {
const group = draft.groups[groupJid] ?? { admins: [], members: [] };
const already = group.admins.includes(phone)
|| group.members.some((m) => m.phone === phone);
return h('button', {
key: `add-${groupJid}`,
type: 'button',
className: 'dim-deliveryButton',
disabled: saving || !phone || already,
onClick: () => {
setDraft((current) => {
const existing = current.groups[groupJid] ?? { title: '', admins: [], members: [] };
if (existing.admins.includes(phone)
|| existing.members.some((m) => m.phone === phone)) {
return current;
}
return {
...current,
groups: {
...current.groups,
[groupJid]: {
...existing,
members: [
...existing.members,
{ phone, canExecuteCommands: true },
],
},
},
};
});
setFeedback({ tone: 'success', message: '已加入本群授权,记得点保存。' });
},
}, already ? '已在本群' : '加本群');
}));
}))),
h('fieldset', { className: 'dim-accessScene', disabled: saving },
h('legend', null, '群会话策略'),
h('label', { className: 'dim-accessField' },

View file

@ -1,7 +1,9 @@
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
import { normalizeLastMessageError } from '../../last-message-error.js';
import { normalizeAccessGrant } from '../../../../src/channels/shared/access-grant.mjs';
import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
import { normalizeGroupSessionScope } from '../../../../src/channels/shared/session-scope.mjs';
export const WHATSAPP_RPC_CHANNEL = '/whatsapp';
@ -13,6 +15,9 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
reconnectBot: 'bot.reconnect',
deleteBot: 'bot.delete',
setAccessPolicy: 'bot.access-policy.set',
setAccessGrant: 'bot.access-grant.set',
resolveAccessPending: 'bot.access-pending.resolve',
setGroupSessionScope: 'bot.group-session-scope.set',
setWorkspace: 'bot.workspace.set',
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
setContextEnhancement: 'bot.context-enhancement.set',
@ -95,6 +100,12 @@ function normalizeBot(value) {
...(Object.hasOwn(value, 'accessPolicy')
? { accessPolicy: normalizeAccessPolicy(value.accessPolicy) }
: {}),
...(Object.hasOwn(value, 'accessGrant')
? { accessGrant: normalizeAccessGrant(value.accessGrant) }
: {}),
...(Object.hasOwn(value, 'groupSessionScope')
? { groupSessionScope: normalizeGroupSessionScope(value.groupSessionScope) }
: {}),
bot: {
name: text(value.bot?.name, 'WhatsApp机器人', 100),
idMasked: text(value.bot?.idMasked, 'WhatsApp账号', 140),

View file

@ -11,6 +11,22 @@ const EN = Object.freeze({
'机器人设置页签': 'Bot settings tabs',
'投递设置': 'Delivery settings',
'访问设置': 'Access settings',
"加本群": "Add to group",
"已在本群": "Already in group",
"加私聊": "Add to DMs",
"已在私聊": "Already in DMs",
"已加入本群授权,记得点保存。": "Added to this group. Remember to save.",
"已加入私聊授权,记得点保存。": "Added to DM access. Remember to save.",
"暂无联系人。有人私聊或 @ 机器人后会出现在此。": "No contacts yet. They appear here after someone DMs or @mentions the bot.",
"仅记录私聊机器人,或在群里 @ 机器人的人。电话是唯一授权键,可一键加入私聊/本群授权。": "Only people who DM the bot or @mention it in a group are recorded. Phone is the sole auth key; use one-click to grant DM or group access.",
"WhatsApp 按电话号码授权:全局管理员管私聊;群管理员只批本群。成员不可跨群、有群权也不自动获得私聊权。": "WhatsApp authorizes by phone number: global admins manage DMs; group admins approve only their group. Members do not cross groups, and group access does not grant DMs.",
"保存分级访问设置": "Save graded access settings",
"保存访问与会话设置": "Save access and session settings",