Files
openclaw/extensions/discord/src/status-issues.ts
scoootscooob 5682ec37fa refactor: move Discord channel implementation to extensions/ (#45660)
* refactor: move Discord channel implementation to extensions/discord/src/

Move all Discord source files from src/discord/ to extensions/discord/src/,
following the extension migration pattern. Source files in src/discord/ are
replaced with re-export shims. Channel-plugin files from
src/channels/plugins/*/discord* are similarly moved and shimmed.

- Copy all .ts source files preserving subdirectory structure (monitor/, voice/)
- Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues)
- Fix all relative imports to use correct paths from new location
- Create re-export shims at original locations for backward compatibility
- Delete test files from shim locations (tests live in extension now)
- Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate
  extension files outside src/
- Update write-plugin-sdk-entry-dts.ts to match new declaration output paths

* fix: add importOriginal to thread-bindings session-meta mock for extensions test

* style: fix formatting in thread-bindings lifecycle test
2026-03-14 02:53:57 -07:00

170 lines
5.1 KiB
TypeScript

import {
appendMatchMetadata,
asString,
isRecord,
resolveEnabledConfiguredAccountId,
} from "../../../src/channels/plugins/status-issues/shared.js";
import type {
ChannelAccountSnapshot,
ChannelStatusIssue,
} from "../../../src/channels/plugins/types.js";
type DiscordIntentSummary = {
messageContent?: "enabled" | "limited" | "disabled";
};
type DiscordApplicationSummary = {
intents?: DiscordIntentSummary;
};
type DiscordAccountStatus = {
accountId?: unknown;
enabled?: unknown;
configured?: unknown;
application?: unknown;
audit?: unknown;
};
type DiscordPermissionsAuditSummary = {
unresolvedChannels?: number;
channels?: Array<{
channelId: string;
ok?: boolean;
missing?: string[];
error?: string | null;
matchKey?: string;
matchSource?: string;
}>;
};
function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccountStatus | null {
if (!isRecord(value)) {
return null;
}
return {
accountId: value.accountId,
enabled: value.enabled,
configured: value.configured,
application: value.application,
audit: value.audit,
};
}
function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummary {
if (!isRecord(value)) {
return {};
}
const intentsRaw = value.intents;
if (!isRecord(intentsRaw)) {
return {};
}
return {
intents: {
messageContent:
intentsRaw.messageContent === "enabled" ||
intentsRaw.messageContent === "limited" ||
intentsRaw.messageContent === "disabled"
? intentsRaw.messageContent
: undefined,
},
};
}
function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsAuditSummary {
if (!isRecord(value)) {
return {};
}
const unresolvedChannels =
typeof value.unresolvedChannels === "number" && Number.isFinite(value.unresolvedChannels)
? value.unresolvedChannels
: undefined;
const channelsRaw = value.channels;
const channels = Array.isArray(channelsRaw)
? (channelsRaw
.map((entry) => {
if (!isRecord(entry)) {
return null;
}
const channelId = asString(entry.channelId);
if (!channelId) {
return null;
}
const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
const missing = Array.isArray(entry.missing)
? entry.missing.map((v) => asString(v)).filter(Boolean)
: undefined;
const error = asString(entry.error) ?? null;
const matchKey = asString(entry.matchKey) ?? undefined;
const matchSource = asString(entry.matchSource) ?? undefined;
return {
channelId,
ok,
missing: missing?.length ? missing : undefined,
error,
matchKey,
matchSource,
};
})
.filter(Boolean) as DiscordPermissionsAuditSummary["channels"])
: undefined;
return { unresolvedChannels, channels };
}
export function collectDiscordStatusIssues(
accounts: ChannelAccountSnapshot[],
): ChannelStatusIssue[] {
const issues: ChannelStatusIssue[] = [];
for (const entry of accounts) {
const account = readDiscordAccountStatus(entry);
if (!account) {
continue;
}
const accountId = resolveEnabledConfiguredAccountId(account);
if (!accountId) {
continue;
}
const app = readDiscordApplicationSummary(account.application);
const messageContent = app.intents?.messageContent;
if (messageContent === "disabled") {
issues.push({
channel: "discord",
accountId,
kind: "intent",
message: "Message Content Intent is disabled. Bot may not see normal channel messages.",
fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.",
});
}
const audit = readDiscordPermissionsAuditSummary(account.audit);
if (audit.unresolvedChannels && audit.unresolvedChannels > 0) {
issues.push({
channel: "discord",
accountId,
kind: "config",
message: `Some configured guild channels are not numeric IDs (unresolvedChannels=${audit.unresolvedChannels}). Permission audit can only check numeric channel IDs.`,
fix: "Use numeric channel IDs as keys in channels.discord.guilds.*.channels (then rerun channels status --probe).",
});
}
for (const channel of audit.channels ?? []) {
if (channel.ok === true) {
continue;
}
const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : "";
const error = channel.error ? `: ${channel.error}` : "";
const baseMessage = `Channel ${channel.channelId} permission check failed.${missing}${error}`;
issues.push({
channel: "discord",
accountId,
kind: "permissions",
message: appendMatchMetadata(baseMessage, {
matchKey: channel.matchKey,
matchSource: channel.matchSource,
}),
fix: "Ensure the bot role can view + send in this channel (and that channel overrides don't deny it).",
});
}
}
return issues;
}