refactor: share sampled entry summary formatting

This commit is contained in:
Peter Steinberger
2026-03-08 00:03:02 +00:00
parent cc03c097c5
commit 990fc36cbd
5 changed files with 77 additions and 28 deletions

View File

@@ -1,9 +1,10 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import {
addAllowlistUserEntriesFromConfigEntry,
buildAllowlistResolutionSummary,
canonicalizeAllowlistWithResolvedIds,
patchAllowlistUsersInConfigEntries,
summarizeMapping,
} from "./resolve-utils.js";
describe("buildAllowlistResolutionSummary", () => {
@@ -94,3 +95,23 @@ describe("patchAllowlistUsersInConfigEntries", () => {
expect((patched.beta as { users: string[] }).users).toEqual(["*"]);
});
});
describe("summarizeMapping", () => {
it("logs sampled resolved and unresolved entries", () => {
const runtime = { log: vi.fn() };
summarizeMapping("discord allowlist", ["a", "b", "c", "d", "e", "f", "g"], ["x", "y"], runtime);
expect(runtime.log).toHaveBeenCalledWith(
"discord allowlist resolved: a, b, c, d, e, f (+1)\ndiscord allowlist unresolved: x, y",
);
});
it("skips logging when both lists are empty", () => {
const runtime = { log: vi.fn() };
summarizeMapping("discord allowlist", [], [], runtime);
expect(runtime.log).not.toHaveBeenCalled();
});
});

View File

@@ -1,5 +1,6 @@
import { mapAllowFromEntries } from "../../plugin-sdk/channel-config-helpers.js";
import type { RuntimeEnv } from "../../runtime.js";
import { summarizeStringEntries } from "../../shared/string-sample.js";
export type AllowlistUserResolutionLike = {
input: string;
@@ -150,15 +151,10 @@ export function summarizeMapping(
): void {
const lines: string[] = [];
if (mapping.length > 0) {
const sample = mapping.slice(0, 6);
const suffix = mapping.length > sample.length ? ` (+${mapping.length - sample.length})` : "";
lines.push(`${label} resolved: ${sample.join(", ")}${suffix}`);
lines.push(`${label} resolved: ${summarizeStringEntries({ entries: mapping, limit: 6 })}`);
}
if (unresolved.length > 0) {
const sample = unresolved.slice(0, 6);
const suffix =
unresolved.length > sample.length ? ` (+${unresolved.length - sample.length})` : "";
lines.push(`${label} unresolved: ${sample.join(", ")}${suffix}`);
lines.push(`${label} unresolved: ${summarizeStringEntries({ entries: unresolved, limit: 6 })}`);
}
if (lines.length > 0) {
runtime.log?.(lines.join("\n"));

View File

@@ -43,6 +43,7 @@ import { createDiscordRetryRunner } from "../../infra/retry-policy.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { getPluginCommandSpecs } from "../../plugins/commands.js";
import { createNonExitingRuntime, type RuntimeEnv } from "../../runtime.js";
import { summarizeStringEntries } from "../../shared/string-sample.js";
import { resolveDiscordAccount } from "../accounts.js";
import { fetchDiscordApplicationId } from "../probe.js";
import { normalizeDiscordToken } from "../token.js";
@@ -103,25 +104,6 @@ export type MonitorDiscordOpts = {
setStatus?: DiscordMonitorStatusSink;
};
function summarizeAllowList(list?: string[]) {
if (!list || list.length === 0) {
return "any";
}
const sample = list.slice(0, 4).map((entry) => String(entry));
const suffix = list.length > sample.length ? ` (+${list.length - sample.length})` : "";
return `${sample.join(", ")}${suffix}`;
}
function summarizeGuilds(entries?: Record<string, unknown>) {
if (!entries || Object.keys(entries).length === 0) {
return "any";
}
const keys = Object.keys(entries);
const sample = keys.slice(0, 4);
const suffix = keys.length > sample.length ? ` (+${keys.length - sample.length})` : "";
return `${sample.join(", ")}${suffix}`;
}
function formatThreadBindingDurationForConfigLabel(durationMs: number): string {
const label = formatThreadBindingDurationLabel(durationMs);
return label === "disabled" ? "off" : label;
@@ -402,8 +384,23 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
allowFrom = allowlistResolved.allowFrom;
if (shouldLogVerbose()) {
const allowFromSummary = summarizeStringEntries({
entries: allowFrom ?? [],
limit: 4,
emptyText: "any",
});
const groupDmChannelSummary = summarizeStringEntries({
entries: groupDmChannels ?? [],
limit: 4,
emptyText: "any",
});
const guildSummary = summarizeStringEntries({
entries: Object.keys(guildEntries ?? {}),
limit: 4,
emptyText: "any",
});
logVerbose(
`discord: config dm=${dmEnabled ? "on" : "off"} dmPolicy=${dmPolicy} allowFrom=${summarizeAllowList(allowFrom)} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${summarizeAllowList(groupDmChannels)} groupPolicy=${groupPolicy} guilds=${summarizeGuilds(guildEntries)} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))} native=${nativeEnabled ? "on" : "off"} nativeSkills=${nativeSkillsEnabled ? "on" : "off"} accessGroups=${useAccessGroups ? "on" : "off"} threadBindings=${threadBindingsEnabled ? "on" : "off"} threadIdleTimeout=${formatThreadBindingDurationForConfigLabel(threadBindingIdleTimeoutMs)} threadMaxAge=${formatThreadBindingDurationForConfigLabel(threadBindingMaxAgeMs)}`,
`discord: config dm=${dmEnabled ? "on" : "off"} dmPolicy=${dmPolicy} allowFrom=${allowFromSummary} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${groupDmChannelSummary} groupPolicy=${groupPolicy} guilds=${guildSummary} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))} native=${nativeEnabled ? "on" : "off"} nativeSkills=${nativeSkillsEnabled ? "on" : "off"} accessGroups=${useAccessGroups ? "on" : "off"} threadBindings=${threadBindingsEnabled ? "on" : "off"} threadIdleTimeout=${formatThreadBindingDurationForConfigLabel(threadBindingIdleTimeoutMs)} threadMaxAge=${formatThreadBindingDurationForConfigLabel(threadBindingMaxAgeMs)}`,
);
}

View File

@@ -0,0 +1,21 @@
import { describe, expect, it } from "vitest";
import { summarizeStringEntries } from "./string-sample.js";
describe("summarizeStringEntries", () => {
it("returns emptyText for empty lists", () => {
expect(summarizeStringEntries({ entries: [], emptyText: "any" })).toBe("any");
});
it("joins short lists without a suffix", () => {
expect(summarizeStringEntries({ entries: ["a", "b"], limit: 4 })).toBe("a, b");
});
it("adds a remainder suffix when truncating", () => {
expect(
summarizeStringEntries({
entries: ["a", "b", "c", "d", "e"],
limit: 4,
}),
).toBe("a, b, c, d (+1)");
});
});

View File

@@ -0,0 +1,14 @@
export function summarizeStringEntries(params: {
entries?: ReadonlyArray<string> | null;
limit?: number;
emptyText?: string;
}): string {
const entries = params.entries ?? [];
if (entries.length === 0) {
return params.emptyText ?? "";
}
const limit = Math.max(1, Math.floor(params.limit ?? 6));
const sample = entries.slice(0, limit);
const suffix = entries.length > sample.length ? ` (+${entries.length - sample.length})` : "";
return `${sample.join(", ")}${suffix}`;
}