Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

220 lines
5.9 KiB
TypeScript

/**
* Elevated-permission helpers — the set of moderation/admin permissions worth
* auditing, plus predicates (`hasElevatedPermissions`, `isElevatedRole`) used
* when diffing Discord permission changes into the connector's audit events.
*/
import type { IAgentRuntime } from "@elizaos/core";
import type {
AuditLogEvent,
Guild,
GuildMember,
PermissionOverwrites,
Role,
} from "discord.js";
import type { AuditInfo, PermissionDiff, PermissionState } from "./types";
/**
* Permissions that indicate moderation/admin capabilities.
* Changes to these permissions are considered elevated and worth tracking.
*/
export const ELEVATED_PERMISSIONS = [
"Administrator",
"ManageGuild",
"ManageChannels",
"ManageRoles",
"KickMembers",
"BanMembers",
"ModerateMembers",
"ManageMessages",
"ManageWebhooks",
"ManageNicknames",
"MuteMembers",
"DeafenMembers",
"MoveMembers",
"ManageEvents",
"ManageThreads",
] as const;
/**
* Check if a role has any elevated (moderation/admin) permissions
*/
export function isElevatedRole(role: Role): boolean {
return ELEVATED_PERMISSIONS.some((p) => role.permissions.has(p));
}
/**
* Check if an array of permission names contains any elevated permissions
*/
export function hasElevatedPermissions(permissions: string[]): boolean {
return permissions.some((p) =>
ELEVATED_PERMISSIONS.includes(p as (typeof ELEVATED_PERMISSIONS)[number]),
);
}
/**
* Fetch the most recent matching audit log entry for an action.
* Matches by target ID and filters to entries within 10 seconds.
* Returns null gracefully on errors (rate limits, missing permissions, etc.)
*/
export async function fetchAuditEntry(
guild: Guild,
actionType: AuditLogEvent,
target: string,
runtime: IAgentRuntime,
): Promise<AuditInfo | null> {
try {
const logs = await guild.fetchAuditLogs({ type: actionType, limit: 5 });
const now = Date.now();
for (const entry of logs.entries.values()) {
// Match by target and ensure entry is recent (within 10 seconds)
// Type guard: entry.target can be various types, not all have 'id'
const targetId =
entry.target && "id" in entry.target ? entry.target.id : undefined;
if (targetId === target && now - entry.createdTimestamp < 10000) {
return {
executorId: entry.executor?.id ?? "unknown",
executorTag: entry.executor?.tag ?? "Unknown",
reason: entry.reason,
};
}
}
} catch (err) {
// Graceful degradation - rate limits, missing permissions, etc.
runtime.logger.debug(`Audit log fetch failed (non-critical): ${err}`);
}
return null;
}
/**
* Get the permission state from allow/deny arrays
*/
function getState(
perm: string,
allow: string[],
deny: string[],
): PermissionState {
if (allow.includes(perm)) {
return "ALLOW";
}
if (deny.includes(perm)) {
return "DENY";
}
return "NEUTRAL";
}
/**
* Convert an overwrite to a list of changes (used for CREATE/DELETE)
* For CREATE: from NEUTRAL to the actual state (ALLOW/DENY)
* For DELETE: from the actual state (ALLOW/DENY) to NEUTRAL
*/
function overwriteToChanges(
ow: PermissionOverwrites,
isDelete: boolean = false,
): PermissionDiff[] {
const changes: PermissionDiff[] = [];
for (const p of ow.allow.toArray()) {
changes.push({
permission: p,
oldState: isDelete ? "ALLOW" : "NEUTRAL",
newState: isDelete ? "NEUTRAL" : "ALLOW",
});
}
for (const p of ow.deny.toArray()) {
changes.push({
permission: p,
oldState: isDelete ? "DENY" : "NEUTRAL",
newState: isDelete ? "NEUTRAL" : "DENY",
});
}
return changes;
}
/**
* Diff two permission overwrites and determine what changed.
* Returns the list of changes and the action type (CREATE/UPDATE/DELETE).
*/
export function diffOverwrites(
oldOw: PermissionOverwrites | undefined | null,
newOw: PermissionOverwrites | undefined | null,
): { changes: PermissionDiff[]; action: "CREATE" | "UPDATE" | "DELETE" } {
// Both null - no change
if (!oldOw && !newOw) {
return { changes: [], action: "UPDATE" };
}
// Created new overwrite
if (!oldOw && newOw) {
return { changes: overwriteToChanges(newOw, false), action: "CREATE" };
}
// Deleted overwrite
if (oldOw && !newOw) {
return { changes: overwriteToChanges(oldOw, true), action: "DELETE" };
}
// Updated overwrite - compare old and new
const changes: PermissionDiff[] = [];
const oldAllow = oldOw?.allow.toArray() ?? [];
const oldDeny = oldOw?.deny.toArray() ?? [];
const newAllow = newOw?.allow.toArray() ?? [];
const newDeny = newOw?.deny.toArray() ?? [];
// Get all permissions that exist in either old or new
const allPerms = new Set([...oldAllow, ...oldDeny, ...newAllow, ...newDeny]);
for (const perm of allPerms) {
const oldState = getState(perm, oldAllow, oldDeny);
const newState = getState(perm, newAllow, newDeny);
if (oldState !== newState) {
changes.push({ permission: perm, oldState, newState });
}
}
return { changes, action: "UPDATE" };
}
/**
* Diff two role permission sets and return what changed.
*/
export function diffRolePermissions(
oldRole: Role,
newRole: Role,
): PermissionDiff[] {
const oldPerms = oldRole.permissions.toArray();
const newPerms = newRole.permissions.toArray();
const changes: PermissionDiff[] = [];
// Find added permissions
for (const p of newPerms) {
if (!oldPerms.includes(p)) {
changes.push({ permission: p, oldState: "NEUTRAL", newState: "ALLOW" });
}
}
// Find removed permissions
for (const p of oldPerms) {
if (!newPerms.includes(p)) {
changes.push({ permission: p, oldState: "ALLOW", newState: "NEUTRAL" });
}
}
return changes;
}
/**
* Diff member roles to find added and removed roles.
*/
export function diffMemberRoles(
oldMember: GuildMember,
newMember: GuildMember,
): { added: Role[]; removed: Role[] } {
const oldRoles = oldMember.roles.cache;
const newRoles = newMember.roles.cache;
return {
added: [...newRoles.filter((r) => !oldRoles.has(r.id)).values()],
removed: [...oldRoles.filter((r) => !newRoles.has(r.id)).values()],
};
}