fix: resolve architecture disconnects and codebase weaknesses

- Standardize MessageRecord types — single source of truth from @bete/shared
- Clean up config: remove unused GUILD_ID/TEXT_GUILD_ID/TEXT_CHANNEL_ID, fix WEBSERVER_PORT default (3001), remove default admin password
- Move mascot_chat_messages table to Drizzle schema with proper migration
- Remove runtime DDL (CREATE TABLE IF NOT EXISTS) from mascot-chat repository
- Remove phantom analytics/ module from documentation
- Add better-sqlite3 dependency to root devDependencies
- Replace 'as any' casts with proper type assertions across AI moderation
- Add error logging to silent catch blocks in LLM client
- Apply Biome formatting and import organization

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-09 13:07:01 +07:00
co-authored by Claude Opus 4.8
parent 67d66bb5dd
commit 3614d32701
21 changed files with 206 additions and 150 deletions
@@ -413,7 +413,7 @@ async function processIndividualFallback(
type: "individual",
message,
skipNormalAnalysis: false,
} as any)) as
} as unknown)) as
| { ok: true; results: AnalysisResult[] }
| { ok: false; results: AnalysisResult[]; error: string };
@@ -443,7 +443,7 @@ async function processIndividualFallback(
type: "individual",
message,
skipNormalAnalysis: true,
} as any)) as
} as unknown)) as
| { ok: true; results: AnalysisResult[] }
| { ok: false; results: AnalysisResult[]; error: string };
@@ -4,6 +4,10 @@ import { config } from "../../shared/config/config.js";
import { createModerationAction } from "../message-capture/messageStore.js";
import type { MessageRecord } from "../message-capture/types.js";
interface ChannelWithSend {
send: (content: string | object, options?: unknown) => Promise<unknown>;
}
const logger = createChildLogger("auto-delete-manager");
const parseStringList = (value?: string | null): string[] => {
@@ -353,7 +357,7 @@ export async function attemptAutoDeleteFlaggedMessage(
if (
logChannel &&
"send" in logChannel &&
typeof (logChannel as any).send === "function"
typeof (logChannel as ChannelWithSend).send === "function"
) {
const severity = message.ai_severity ?? "none";
const categories =
@@ -362,7 +366,7 @@ export async function attemptAutoDeleteFlaggedMessage(
0,
200,
);
await (logChannel as any).send(
await (logChannel as ChannelWithSend).send(
`**🧹 Auto-Delete** — Pesan dari <@${message.user_id}> di <#${channelId}>\n` +
`**Status:** ${message.ai_status}\n` +
`**Severitas:** ${severity}\n` +
@@ -14,6 +14,24 @@ import { withLlmConcurrency } from "./concurrencyLimiter.js";
const log = createChildLogger("llm-client");
/**
* Covers all LLM response chunk shapes the streaming handler supports.
* Different providers (OpenAI, Anthropic-compatible, local LLMs) may return
* content in different fields — we try them all via optional chaining.
*/
type LLMResponseChunk = {
choices?: Array<{
delta?: { content?: string | null };
message?: { content?: string | null };
finish_reason?: string | null;
text?: string;
}>;
message?: { content?: string | null };
content?: string;
response?: string;
finish_reason?: string;
};
// ---------------------------------------------------------------------------
// Lazy singleton — created on first use so that config is always resolved.
// ---------------------------------------------------------------------------
@@ -112,7 +130,7 @@ export async function llmChat(
if (currentParams.stream) {
let content = "";
let finishReason = "stop";
for await (const chunk of response as any) {
for await (const chunk of response as unknown as AsyncIterable<LLMResponseChunk>) {
const choice = chunk?.choices?.[0];
const textChunk =
choice?.delta?.content ||
@@ -179,7 +179,12 @@ export function extractJson(content: string): unknown {
if (parsed && typeof parsed === "object") {
return parsed;
}
} catch (_) {}
} catch (err) {
log.debug(
{ err: err instanceof Error ? err.message : String(err) },
"Failed to parse JSON from code block — trying next block",
);
}
}
for (let start = 0; start < content.length; start++) {
@@ -224,7 +229,12 @@ export function extractJson(content: string): unknown {
if (parsed && typeof parsed === "object") {
return parsed;
}
} catch (_) {}
} catch (err) {
log.debug(
{ err: err instanceof Error ? err.message : String(err) },
"Failed to parse JSON candidate — trying next position",
);
}
break;
}
}
@@ -266,7 +276,7 @@ export function parseModerationResponse(
parsed = { results: [parsed] };
} else {
const arrayKey = Object.keys(parsed).find((key) => {
const val = (parsed as any)[key];
const val = parsed[key];
return (
Array.isArray(val) &&
val.length > 0 &&
@@ -274,12 +284,12 @@ export function parseModerationResponse(
(item: unknown) =>
typeof item === "object" &&
item !== null &&
"message_id" in (item as any),
"message_id" in (item as Record<string, unknown>),
)
);
});
if (arrayKey) {
parsed.results = (parsed as any)[arrayKey];
parsed.results = parsed[arrayKey];
} else {
parsed = { results: [parsed] };
}
@@ -100,7 +100,7 @@ export async function pruneExpiredTexts(): Promise<number> {
`DELETE FROM text_analysis_cache WHERE expires_at < $1`,
[Date.now()],
);
return (result as any).rowCount ?? 0;
return (result as unknown as { rowCount?: number }).rowCount ?? 0;
} catch (error) {
logger.error(
{ error: error instanceof Error ? error.message : String(error) },