Skip to content

Commit c156f7c

Browse files
committed
fix: reduce plugin and discord warning noise
1 parent a9317a4 commit c156f7c

File tree

10 files changed

+175
-27
lines changed

10 files changed

+175
-27
lines changed

extensions/discord/src/monitor/listeners.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -755,14 +755,13 @@ export class DiscordThreadUpdateListener extends ThreadUpdateListener {
755755
return;
756756
}
757757
const logger = this.logger ?? discordEventQueueLog;
758-
logger.info("Discord thread archived — resetting session", { threadId });
759758
const count = await closeDiscordThreadSessions({
760759
cfg: this.cfg,
761760
accountId: this.accountId,
762761
threadId,
763762
});
764763
if (count > 0) {
765-
logger.info("Discord thread sessions reset after archival", { threadId, count });
764+
logger.info("Discord thread archived — reset sessions", { threadId, count });
766765
}
767766
},
768767
onError: (err) => {

extensions/discord/src/monitor/thread-session-close.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,24 @@ describe("closeDiscordThreadSessions", () => {
135135
expect(hoisted.updateSessionStore).not.toHaveBeenCalled();
136136
});
137137

138+
it("does not recount sessions that were already reset", async () => {
139+
const store = {
140+
[MATCHED_KEY]: { updatedAt: 0 },
141+
[UNMATCHED_KEY]: { updatedAt: 1_700_000_000_001 },
142+
};
143+
setupStore(store);
144+
145+
const count = await closeDiscordThreadSessions({
146+
cfg: {},
147+
accountId: "default",
148+
threadId: THREAD_ID,
149+
});
150+
151+
expect(count).toBe(0);
152+
expect(store[MATCHED_KEY].updatedAt).toBe(0);
153+
expect(store[UNMATCHED_KEY].updatedAt).toBe(1_700_000_000_001);
154+
});
155+
138156
it("resolves the store path using cfg.session.store and accountId", async () => {
139157
const store = {};
140158
setupStore(store);

extensions/discord/src/monitor/thread-session-close.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ export async function closeDiscordThreadSessions(params: {
4747
if (!entry || !sessionKeyContainsThreadId(key)) {
4848
continue;
4949
}
50+
if (entry.updatedAt === 0) {
51+
continue;
52+
}
5053
// Setting updatedAt to 0 signals that this session is stale.
5154
// evaluateSessionFreshness will create a new session on the next message.
5255
entry.updatedAt = 0;

extensions/tlon/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,32 @@ const ALLOWED_TLON_COMMANDS = new Set([
2727
/**
2828
* Find the tlon binary from the skill package
2929
*/
30+
let cachedTlonBinary: string | undefined;
31+
3032
function findTlonBinary(): string {
33+
if (cachedTlonBinary) {
34+
return cachedTlonBinary;
35+
}
3136
// Check in node_modules/.bin
3237
const skillBin = join(__dirname, "node_modules", ".bin", "tlon");
33-
console.log(`[tlon] Checking for binary at: ${skillBin}, exists: ${existsSync(skillBin)}`);
34-
if (existsSync(skillBin)) return skillBin;
38+
if (existsSync(skillBin)) {
39+
cachedTlonBinary = skillBin;
40+
return skillBin;
41+
}
3542

3643
// Check for platform-specific binary directly
3744
const platform = process.platform;
3845
const arch = process.arch;
3946
const platformPkg = `@tloncorp/tlon-skill-${platform}-${arch}`;
4047
const platformBin = join(__dirname, "node_modules", platformPkg, "tlon");
41-
console.log(
42-
`[tlon] Checking for platform binary at: ${platformBin}, exists: ${existsSync(platformBin)}`,
43-
);
44-
if (existsSync(platformBin)) return platformBin;
48+
if (existsSync(platformBin)) {
49+
cachedTlonBinary = platformBin;
50+
return platformBin;
51+
}
4552

4653
// Fallback to PATH
47-
console.log(`[tlon] Falling back to PATH lookup for 'tlon'`);
48-
return "tlon";
54+
cachedTlonBinary = "tlon";
55+
return cachedTlonBinary;
4956
}
5057

5158
/**
@@ -132,9 +139,7 @@ const plugin = {
132139
setTlonRuntime(api.runtime);
133140
api.registerChannel({ plugin: tlonPlugin });
134141

135-
// Register the tlon tool
136-
const tlonBinary = findTlonBinary();
137-
api.logger.info(`[tlon] Registering tlon tool, binary: ${tlonBinary}`);
142+
api.logger.debug?.("[tlon] Registering tlon tool");
138143
api.registerTool({
139144
name: "tlon",
140145
label: "Tlon CLI",
@@ -156,6 +161,7 @@ const plugin = {
156161
async execute(_id: string, params: { command: string }) {
157162
try {
158163
const args = shellSplit(params.command);
164+
const tlonBinary = findTlonBinary();
159165

160166
// Validate first argument is a whitelisted tlon subcommand
161167
const subcommand = args[0];

scripts/copy-bundled-plugin-metadata.mjs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ function normalizeManifestRelativePath(rawPath) {
4040
return rawPath.replaceAll("\\", "/").replace(/^\.\//u, "");
4141
}
4242

43+
function resolveDeclaredSkillSourcePath(params) {
44+
const normalized = normalizeManifestRelativePath(params.rawPath);
45+
const pluginLocalPath = ensurePathInsideRoot(params.pluginDir, normalized);
46+
if (fs.existsSync(pluginLocalPath)) {
47+
return pluginLocalPath;
48+
}
49+
if (!/^node_modules(?:\/|$)/u.test(normalized)) {
50+
return pluginLocalPath;
51+
}
52+
return ensurePathInsideRoot(params.repoRoot, normalized);
53+
}
54+
4355
function resolveBundledSkillTarget(rawPath) {
4456
const normalized = normalizeManifestRelativePath(rawPath);
4557
if (/^node_modules(?:\/|$)/u.test(normalized)) {
@@ -68,7 +80,11 @@ function copyDeclaredPluginSkillPaths(params) {
6880
if (typeof raw !== "string" || raw.trim().length === 0) {
6981
continue;
7082
}
71-
const sourcePath = ensurePathInsideRoot(params.pluginDir, raw);
83+
const sourcePath = resolveDeclaredSkillSourcePath({
84+
rawPath: raw,
85+
pluginDir: params.pluginDir,
86+
repoRoot: params.repoRoot,
87+
});
7288
const target = resolveBundledSkillTarget(raw);
7389
if (!fs.existsSync(sourcePath)) {
7490
// Some Docker/lightweight builds intentionally omit optional plugin-local
@@ -138,7 +154,12 @@ export function copyBundledPluginMetadata(params = {}) {
138154
// remove the older bad node_modules tree so release packs cannot pick it up.
139155
removePathIfExists(path.join(distPluginDir, GENERATED_BUNDLED_SKILLS_DIR));
140156
removePathIfExists(path.join(distPluginDir, "node_modules"));
141-
const copiedSkills = copyDeclaredPluginSkillPaths({ manifest, pluginDir, distPluginDir });
157+
const copiedSkills = copyDeclaredPluginSkillPaths({
158+
manifest,
159+
pluginDir,
160+
distPluginDir,
161+
repoRoot,
162+
});
142163
const bundledManifest = Array.isArray(manifest.skills)
143164
? { ...manifest, skills: copiedSkills }
144165
: manifest;

src/logging/console-capture.test.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,19 @@ describe("enableConsoleCapture", () => {
7777
vi.useRealTimers();
7878
});
7979

80-
it("suppresses discord EventQueue slow listener duplicates", () => {
81-
setLoggerOverride({ level: "info", file: tempLogPath() });
82-
const warn = vi.fn();
83-
console.warn = warn;
84-
enableConsoleCapture();
85-
console.warn(
86-
"[EventQueue] Slow listener detected: DiscordMessageListener took 12.3 seconds for event MESSAGE_CREATE",
87-
);
88-
expect(warn).not.toHaveBeenCalled();
89-
});
80+
it.each(["DiscordMessageListener", "DiscordReactionListener", "DiscordReactionRemoveListener"])(
81+
"suppresses discord EventQueue slow listener duplicates for %s",
82+
(listener) => {
83+
setLoggerOverride({ level: "info", file: tempLogPath() });
84+
const warn = vi.fn();
85+
console.warn = warn;
86+
enableConsoleCapture();
87+
console.warn(
88+
`[EventQueue] Slow listener detected: ${listener} took 12.3 seconds for event MESSAGE_CREATE`,
89+
);
90+
expect(warn).not.toHaveBeenCalled();
91+
},
92+
);
9093

9194
it("does not double-prefix timestamps", () => {
9295
setLoggerOverride({ level: "info", file: tempLogPath() });

src/logging/console.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ const SUPPRESSED_CONSOLE_PREFIXES = [
145145
"Session already open",
146146
] as const;
147147

148+
const SUPPRESSED_DISCORD_EVENTQUEUE_LISTENERS = [
149+
"DiscordMessageListener",
150+
"DiscordReactionListener",
151+
"DiscordReactionRemoveListener",
152+
] as const;
153+
148154
function shouldSuppressConsoleMessage(message: string): boolean {
149155
if (isVerbose()) {
150156
return false;
@@ -154,7 +160,7 @@ function shouldSuppressConsoleMessage(message: string): boolean {
154160
}
155161
if (
156162
message.startsWith("[EventQueue] Slow listener detected") &&
157-
message.includes("DiscordMessageListener")
163+
SUPPRESSED_DISCORD_EVENTQUEUE_LISTENERS.some((listener) => message.includes(listener))
158164
) {
159165
return true;
160166
}

src/plugins/copy-bundled-plugin-metadata.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,49 @@ describe("copyBundledPluginMetadata", () => {
152152
expect(bundledManifest.skills).toEqual(["./bundled-skills/@tloncorp/tlon-skill"]);
153153
});
154154

155+
it("falls back to repo-root hoisted node_modules skill paths", () => {
156+
const repoRoot = makeRepoRoot("openclaw-bundled-plugin-hoisted-skill-");
157+
const pluginDir = path.join(repoRoot, "extensions", "tlon");
158+
const hoistedSkillDir = path.join(repoRoot, "node_modules", "@tloncorp", "tlon-skill");
159+
fs.mkdirSync(hoistedSkillDir, { recursive: true });
160+
fs.writeFileSync(path.join(hoistedSkillDir, "SKILL.md"), "# Hoisted Tlon Skill\n", "utf8");
161+
fs.mkdirSync(pluginDir, { recursive: true });
162+
writeJson(path.join(pluginDir, "openclaw.plugin.json"), {
163+
id: "tlon",
164+
configSchema: { type: "object" },
165+
skills: ["node_modules/@tloncorp/tlon-skill"],
166+
});
167+
writeJson(path.join(pluginDir, "package.json"), {
168+
name: "@openclaw/tlon",
169+
openclaw: { extensions: ["./index.ts"] },
170+
});
171+
172+
copyBundledPluginMetadata({ repoRoot });
173+
174+
expect(
175+
fs.readFileSync(
176+
path.join(
177+
repoRoot,
178+
"dist",
179+
"extensions",
180+
"tlon",
181+
"bundled-skills",
182+
"@tloncorp",
183+
"tlon-skill",
184+
"SKILL.md",
185+
),
186+
"utf8",
187+
),
188+
).toContain("Hoisted Tlon Skill");
189+
const bundledManifest = JSON.parse(
190+
fs.readFileSync(
191+
path.join(repoRoot, "dist", "extensions", "tlon", "openclaw.plugin.json"),
192+
"utf8",
193+
),
194+
) as { skills?: string[] };
195+
expect(bundledManifest.skills).toEqual(["./bundled-skills/@tloncorp/tlon-skill"]);
196+
});
197+
155198
it("omits missing declared skill paths and removes stale generated outputs", () => {
156199
const repoRoot = makeRepoRoot("openclaw-bundled-plugin-missing-skill-");
157200
const pluginDir = path.join(repoRoot, "extensions", "tlon");

src/plugins/manifest-registry.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,44 @@ describe("loadPluginManifestRegistry", () => {
314314
expect(countDuplicateWarnings(loadRegistry(candidates))).toBe(0);
315315
});
316316

317+
it("accepts provider-style id hints without warning", () => {
318+
const dir = makeTempDir();
319+
writeManifest(dir, { id: "openai", configSchema: { type: "object" } });
320+
321+
const registry = loadRegistry([
322+
createPluginCandidate({
323+
idHint: "openai-provider",
324+
rootDir: dir,
325+
origin: "bundled",
326+
}),
327+
]);
328+
329+
expect(registry.diagnostics.some((diag) => diag.message.includes("plugin id mismatch"))).toBe(
330+
false,
331+
);
332+
});
333+
334+
it("still warns for unrelated id hint mismatches", () => {
335+
const dir = makeTempDir();
336+
writeManifest(dir, { id: "openai", configSchema: { type: "object" } });
337+
338+
const registry = loadRegistry([
339+
createPluginCandidate({
340+
idHint: "totally-different",
341+
rootDir: dir,
342+
origin: "bundled",
343+
}),
344+
]);
345+
346+
expect(
347+
registry.diagnostics.some((diag) =>
348+
diag.message.includes(
349+
'plugin id mismatch (manifest uses "openai", entry hints "totally-different")',
350+
),
351+
),
352+
).toBe(true);
353+
});
354+
317355
it("loads Codex bundle manifests into the registry", () => {
318356
const bundleDir = makeTempDir();
319357
mkdirSafe(path.join(bundleDir, ".codex-plugin"));

src/plugins/manifest-registry.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ function normalizeManifestLabel(raw: string | undefined): string | undefined {
122122
return trimmed ? trimmed : undefined;
123123
}
124124

125+
function isCompatiblePluginIdHint(idHint: string | undefined, manifestId: string): boolean {
126+
const normalizedHint = idHint?.trim();
127+
if (!normalizedHint) {
128+
return true;
129+
}
130+
if (normalizedHint === manifestId) {
131+
return true;
132+
}
133+
return normalizedHint === `${manifestId}-provider`;
134+
}
135+
125136
function buildRecord(params: {
126137
manifest: PluginManifest;
127138
candidate: PluginCandidate;
@@ -304,7 +315,7 @@ export function loadPluginManifestRegistry(params: {
304315
}
305316
const manifest = manifestRes.manifest;
306317

307-
if (candidate.idHint && candidate.idHint !== manifest.id) {
318+
if (!isCompatiblePluginIdHint(candidate.idHint, manifest.id)) {
308319
diagnostics.push({
309320
level: "warn",
310321
pluginId: manifest.id,

0 commit comments

Comments
 (0)