feat: add unified IM access policies

This commit is contained in:
xmanrui 2026-09-01 10:45:12 +08:00
parent 075d2713c1
commit 840e5aa857
89 changed files with 5573 additions and 1676 deletions

View file

@ -26,6 +26,7 @@ test('client exposes the fixed DingTalk RPC channel and endpoint names', () => {
setWorkspace: 'bot.workspace.set',
setAgentPreset: 'bot.preset.set',
setContextEnhancement: 'bot.context-enhancement.set',
setAccessPolicy: 'bot.access-policy.set',
});
});

View file

@ -21,6 +21,10 @@ import {
createOutboundArtifactTool,
releaseOutboundArtifact,
} from '../../../src/channels/shared/semantic/artifact.mjs';
import {
COMMAND_PERMISSION_DENIED_MESSAGE,
directAccessPolicy,
} from '../access-policy-fixture.mjs';
function deferred() {
let resolve;
@ -681,6 +685,75 @@ test('DingTalk checks the group mention before downloading a picture', async ()
assert.equal(asks, 0);
});
test('DingTalk applies the unified access policy before attachments or Harness work', async () => {
const fixture = stateFixture();
fixture.sessions.set('p2p:staff-member', 'session-member');
let downloads = 0;
const harnessCalls = [];
const sent = [];
const accessPolicy = directAccessPolicy({
users: [{ id: 'staff-member', canExecuteCommands: false }],
privilegedIds: ['staff-owner'],
});
const bridge = new DingtalkHarnessBridge({
api: {
downloadImage: async () => {
downloads += 1;
return PNG_BYTES;
},
sendText: async ({ text }) => {
sent.push(text);
return { messageId: `dingtalk-policy-${sent.length}` };
},
},
clientId: 'ding-client',
clientSecret: 'host-secret',
accessPolicy,
harness: {
sessionExists: async (sessionId) => {
harnessCalls.push(['sessionExists', sessionId]);
return true;
},
ask: async (sessionId, prompt) => {
harnessCalls.push(['ask', sessionId, prompt]);
return '白名单消息已处理';
},
},
state: fixture.state,
});
await bridge.accept(message('policy-blocked-picture', '', {
senderStaffId: 'staff-blocked',
msgtype: 'picture',
text: undefined,
content: { downloadCode: 'blocked-picture' },
robotCode: 'robot-code',
}));
assert.equal(downloads, 0);
assert.deepEqual(harnessCalls, []);
assert.deepEqual(sent, []);
await bridge.accept(message('policy-member-text', '普通消息', {
senderStaffId: 'staff-member',
}));
assert.equal(harnessCalls.some(([operation]) => operation === 'ask'), true);
assert.deepEqual(sent, ['白名单消息已处理']);
const callsBeforeDeniedCommand = harnessCalls.length;
const repliesBeforeDeniedCommand = sent.length;
await bridge.accept(message('policy-member-command', '/help', {
senderStaffId: 'staff-member',
}));
assert.equal(harnessCalls.length, callsBeforeDeniedCommand);
assert.deepEqual(sent.slice(repliesBeforeDeniedCommand), [COMMAND_PERMISSION_DENIED_MESSAGE]);
accessPolicy.getSettings().direct.allowlist.users = [];
await bridge.accept(message('policy-owner-command', '/help', {
senderStaffId: 'staff-owner',
}));
assert.match(sent.at(-1), /\/help/);
});
test('DingTalk returns a specific retry message when picture download fails', async () => {
const fixture = stateFixture();
fixture.sessions.set('p2p:staff-approved', 'session-image');