Add tiers, continuation, fetch cooldown, persistent action log, voice read, batch MCP

This commit is contained in:
Fisto
2026-09-11 15:53:06 +02:00
parent 82f78bb2b4
commit 8e8c15e8bc
17 changed files with 669 additions and 139 deletions
+56 -1
View File
@@ -50,6 +50,10 @@ export class MessageLog {
snapshot() {
return { trusted: [...this.trusted], untrusted: [...this.untrusted] };
}
find(id: string): PuppetMessage | undefined {
return this.trusted.find(message => message.id === id) ?? this.untrusted.find(message => message.id === id);
}
}
const USER_MENTION = /<@!?(\d+)>/g;
@@ -113,7 +117,18 @@ export function formatContextLine(message: CachedMessage): string {
const parts: string[] = [];
if (message.content) parts.push(message.content);
for (const name of message.attachments ?? []) parts.push(`[file: ${name}]`);
return `${message.authorName}: ${parts.join(" ")}`.trim();
let stamp = "";
if (message.timestamp) {
const date = new Date(message.timestamp);
if (!Number.isNaN(date.getTime())) {
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
stamp = `[${hours}:${minutes}] `;
}
}
return `${stamp}${message.authorName}: ${parts.join(" ")}`.trim();
}
/** Assemble the newest messages inside a message count limit and a character limit. */
@@ -158,6 +173,46 @@ export function compareSnowflakes(a: string, b: string): number {
}
}
/* Trust tiers */
export type TrustTier = "user" | "trusted" | "admin";
const TIER_RANK: Record<TrustTier, number> = { user: 0, trusted: 1, admin: 2 };
export function hasTier(tier: TrustTier | undefined, required: TrustTier): boolean {
return !!tier && TIER_RANK[tier] >= TIER_RANK[required];
}
export function parseTier(value: string): TrustTier | undefined {
return value === "user" || value === "trusted" || value === "admin" ? value : undefined;
}
/* Secret redaction for persistent logs. Keep it minimal. */
const SECRET_PATTERNS = [
/[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{5,7}\.[A-Za-z0-9_-]{25,}/g,
/mfa\.[A-Za-z0-9_-]{20,}/g
];
export function redactSecrets(text: string): string {
let output = text;
for (const pattern of SECRET_PATTERNS) output = output.replace(pattern, "[redacted]");
return output;
}
export function redactValue(value: unknown, depth = 0): unknown {
if (typeof value === "string") return redactSecrets(value);
if (depth > 4 || value === null || typeof value !== "object") return value;
if (Array.isArray(value)) return value.map(item => redactValue(item, depth + 1));
const result: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
result[key] = redactValue(item, depth + 1);
}
return result;
}
/** Convert a core `message.fetch` or `message.read` message into a cached message. */
export function toCachedMessage(raw: any): CachedMessage {
return {