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

@ -51,6 +51,10 @@ import {
messageFailureText,
setLastMessageFailure,
} from '../shared/message-failure.mjs';
import {
COMMAND_PERMISSION_DENIED_MESSAGE,
evaluateInboundAccess,
} from '../shared/inbound-access.mjs';
import { t } from '../shared/i18n.mjs';
const DEFAULT_FILE_UPLOAD_TIMEOUT_MS = 120_000;
@ -490,6 +494,7 @@ export class WecomHarnessBridge {
#harness;
#state;
#contextEnhancement;
#accessPolicy;
#status;
#logger;
#replyTimeoutMs;
@ -512,6 +517,7 @@ export class WecomHarnessBridge {
harness,
state,
contextEnhancement,
accessPolicy,
status = createWecomBridgeStatus(),
logger = console,
replyTimeoutMs = 600_000,
@ -530,6 +536,7 @@ export class WecomHarnessBridge {
this.#harness = harness;
this.#state = state;
this.#contextEnhancement = contextEnhancement;
this.#accessPolicy = accessPolicy;
this.#status = status;
this.#logger = logger;
this.#replyTimeoutMs = replyTimeoutMs;
@ -557,16 +564,28 @@ export class WecomHarnessBridge {
|| this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
const key = conversationKey(frame);
const pending = this.#pendingInteractions.get(key);
const commandMessage = wecomInboundMessage(frame, this.#client);
const commandText = nonEmptyString(commandMessage.content) ?? '';
const conversationType = body.chattype === 'single' ? 'direct' : 'group';
const access = evaluateInboundAccess(this.#accessPolicy, {
conversationType,
senderIds: senderId,
text: commandText,
hasImages: hasInboundImages(commandMessage),
hasFiles: hasInboundFiles(commandMessage),
});
if (!access.allowed) {
this.#acceptedMessageIds.set(messageId, null);
return this.#finishAccessDecision(frame, messageId, chatId, access);
}
this.#acceptedMessageIds.set(messageId, captureContextEnhancement(
this.#contextEnhancement,
body.chattype === 'single' ? 'direct' : 'group',
conversationType,
));
if (body.chattype === 'single') {
rememberConnectionTestTarget(this.#state, { chatId });
}
const pending = this.#pendingInteractions.get(key);
const commandMessage = wecomInboundMessage(frame, this.#client);
const commandText = nonEmptyString(commandMessage.content) ?? '';
const batchCommand = isBatchInputCommand(commandText);
const batchStatus = this.#batchInputs.status(key);
if (batchCommand && body.chattype === 'group') {
@ -760,6 +779,34 @@ export class WecomHarnessBridge {
return task;
}
#finishAccessDecision(frame, messageId, chatId, access) {
let task;
task = Promise.resolve().then(async () => {
if (this.#state.hasSeen(messageId)) return;
await this.#state.markSeen(messageId);
if (access.reason === 'command-not-allowed') {
this.#status.messagesReceived += 1;
this.#status.lastMessageAt = new Date().toISOString();
await this.#sendImmediate(frame, chatId, t(COMMAND_PERMISSION_DENIED_MESSAGE));
this.#status.messagesReplied += 1;
this.#status.lastReplyAt = new Date().toISOString();
} else {
this.#status.messagesRejected += 1;
this.#status.lastRejectedAt = new Date().toISOString();
}
this.#status.lastError = null;
}).catch((error) => {
if (this.#signal?.aborted) return;
this.#status.lastError = error?.message ?? String(error);
this.#logger.error?.('[dsh-im:wecom] failed to apply inbound access policy', error);
}).finally(() => {
this.#acceptedMessageIds.delete(messageId);
this.#commandTasks.delete(task);
});
this.#commandTasks.add(task);
return task;
}
async waitForIdle() {
await Promise.allSettled([
...this.#queues.values(),

View file

@ -29,6 +29,7 @@ export class WecomRuntime {
#harness;
#state;
#contextEnhancement;
#accessPolicy;
#logger;
#replyTimeoutMs;
#connectTimeoutMs;
@ -47,6 +48,7 @@ export class WecomRuntime {
harness,
state,
contextEnhancement,
accessPolicy,
logger = console,
replyTimeoutMs = 600_000,
connectTimeoutMs = 20_000,
@ -61,6 +63,7 @@ export class WecomRuntime {
this.#harness = harness;
this.#state = state;
this.#contextEnhancement = contextEnhancement;
this.#accessPolicy = accessPolicy;
this.#logger = logger;
this.#replyTimeoutMs = replyTimeoutMs;
this.#connectTimeoutMs = connectTimeoutMs;
@ -111,6 +114,7 @@ export class WecomRuntime {
harness: this.#harness,
state: this.#state,
contextEnhancement: this.#contextEnhancement,
accessPolicy: this.#accessPolicy,
status: this.#status,
logger: this.#logger,
replyTimeoutMs: this.#replyTimeoutMs,