import assert from "node:assert/strict"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test } from "bun:test"; import { getMemorySettings } from "../settings/agentStack.ts"; import { ClankerBot } from "../bot.ts"; import { getReplyAddressSignal as getReplyAddressSignalForReplyAdmission } from "./replyAdmission.ts"; import { isReplyChannel as isReplyChannelForPermissions } from "./permissions.ts"; import { Store } from "../store/store.ts"; import { rmTempDir } from "../testHelpers.ts"; import { createTestSettingsPatch } from "../testSettings.ts";
async function withTempStore(run) { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clanker-bot-reply-policy-test-")); const dbPath = path.join(dir, "clanker.db"); const store = new Store(dbPath); store.init();
try { await run(store); } finally { store.close(); await rmTempDir(dir); } }
function buildGuild() { return { id: "guild-1", emojis: { cache: { map() { return []; } } }, members: { cache: new Map() } }; }
function buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef }) {
return {
id: channelId,
guildId: guild.id,
name: "general",
guild,
isTextBased() {
return true;
},
async sendTyping() {
typingCallsRef.count += 1;
},
async send(payload) {
channelSendPayloads.push(payload);
return {
id: standalone-${Date.now()},
createdTimestamp: Date.now(),
guildId: guild.id,
channelId,
content: String(payload?.content || ""),
attachments: new Map(),
embeds: []
};
}
};
}
function buildPrivateThreadChannel({ guild, channelId = "private-thread-1" } = {}) { return { id: channelId, guildId: guild.id, guild, private: true, isTextBased() { return true; }, isThread() { return true; }, isDMBased() { return false; }, async send() { return undefined; } }; }
function buildIncomingMessage({
guild,
channel,
messageId,
content,
replyPayloads,
authorId = "user-1",
username = "alice",
referenceMessageId = null,
referencedAuthorId = null
}) {
return {
id: messageId,
createdTimestamp: Date.now(),
guildId: guild.id,
channelId: channel.id,
guild,
channel,
author: {
id: authorId,
username,
bot: false
},
member: {
displayName: username
},
content,
mentions: {
users: {
has() {
return false;
}
},
repliedUser: referencedAuthorId ? { id: referencedAuthorId } : null
},
reference: referenceMessageId ? { messageId: referenceMessageId } : null,
referencedMessage: referenceMessageId
? {
id: referenceMessageId,
author: referencedAuthorId ? { id: referencedAuthorId } : undefined
}
: null,
attachments: new Map(),
embeds: [],
reactions: {
cache: new Map()
},
async react() {
return undefined;
},
async reply(payload) {
replyPayloads.push(payload);
return {
id: reply-${Date.now()},
createdTimestamp: Date.now(),
guildId: guild.id,
channelId: channel.id,
content: String(payload?.content || ""),
attachments: new Map(),
embeds: []
};
}
};
}
function recordSameAuthorFollowupContext( store, { guildId, channelId, authorId = "user-1", authorName = "alice", botUserId = "bot-1", botName = "clanky", humanMessageId = "human-context-1", botMessageId = "bot-context-1" } ) { store.recordMessage({ messageId: humanMessageId, createdAt: Date.now() - 1_500, guildId, channelId, authorId, authorName, isBot: false, content: "starter takes are chaos", referencedMessageId: null }); store.recordMessage({ messageId: botMessageId, createdAt: Date.now() - 750, guildId, channelId, authorId: botUserId, authorName: botName, isBot: true, content: "last bot line", referencedMessageId: humanMessageId });
return { humanMessageId, botMessageId }; }
function patchTestSettings(store, patch) { store.patchSettings(createTestSettingsPatch(patch)); }
function applyBaselineSettings(store, channelId) { patchTestSettings(store, { interaction: { activity: { ambientReplyEagerness: 65, reactivity: 20, minSecondsBetweenMessages: 5, replyCoalesceWindowSeconds: 0, replyCoalesceMaxMessages: 1 } }, permissions: { replies: { allowReplies: true, allowUnsolicitedReplies: true, allowReactions: true, replyChannelIds: [], allowedChannelIds: [channelId], blockedChannelIds: [], blockedUserIds: [], maxMessagesPerHour: 120, maxReactionsPerHour: 120 } }, memory: { enabled: false, promptSlice: { maxRecentMessages: 12 } }, agentStack: { runtimeConfig: { research: { enabled: false, maxSearchesPerHour: 0 } } }, media: { videoContext: { enabled: false, maxLookupsPerHour: 0 } }, initiative: { discovery: { allowReplyImages: false, allowReplyVideos: false, allowReplyGifs: false } } }); }
test("same-author active follow-up turn can still post when model contributes value", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId);
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "evo lines decide everything ngl, base forms are only first impressions",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const { botMessageId } = recordSameAuthorFollowupContext(store, {
guildId: guild.id,
channelId
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-1",
content: "pokemon starter takes are all over the place rn",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 1);
assert.equal(typingCallsRef.count > 0, true);
assert.match(String(channelSendPayloads[0]?.content || ""), /evo lines decide everything/i);
const sentAction = store.getRecentActions(12).find(
(row) => row.kind === "sent_message" && row.message_id !== botMessageId
);
assert.equal(sentAction?.metadata?.replyPrompts?.hiddenByDefault, true);
assert.equal(typeof sentAction?.metadata?.replyPrompts?.systemPrompt, "string");
assert.equal(typeof sentAction?.metadata?.replyPrompts?.initialUserPrompt, "string");
assert.deepEqual(sentAction?.metadata?.replyPrompts?.followupUserPrompts, []);
}); });
test("same-author active follow-up turn is skipped when the model declines", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId);
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate() {
return {
text: JSON.stringify({
text: "[SKIP]",
skip: true,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
recordSameAuthorFollowupContext(store, {
guildId: guild.id,
channelId
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-2",
content: "random side chatter between people",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, false);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 0);
const recentActions = store.getRecentActions(12);
const skipped = recentActions.find(
(row) => row.kind === "reply_skipped" && row.message_id === "msg-2"
);
assert.equal(Boolean(skipped), true);
assert.equal(skipped?.content, "llm_skip");
assert.equal(skipped?.metadata?.replyPrompts?.hiddenByDefault, true);
assert.equal(typeof skipped?.metadata?.replyPrompts?.systemPrompt, "string");
assert.equal(typeof skipped?.metadata?.replyPrompts?.initialUserPrompt, "string");
assert.deepEqual(skipped?.metadata?.replyPrompts?.followupUserPrompts, []);
}); });
test("smoke: text followup-window turn addressed to another user is llm-skipped", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId);
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "[SKIP]",
skip: true,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-followup-directed-away",
content: "hey joey guess what game i'm playing",
replyPayloads
});
const recentMessages = [
{
message_id: "bot-context-followup",
author_id: "bot-1",
author_name: "clanky",
content: "yeah that build looked scuffed",
created_at: new Date(Date.now() - 1_200).toISOString()
},
{
message_id: "user-context-followup",
author_id: "user-2",
author_name: "joey",
content: "what game?",
created_at: new Date(Date.now() - 900).toISOString()
}
];
const settings = store.getSettings();
const addressSignal = await getReplyAddressSignalForReplyAdmission(
{
botUserId: String(bot.client.user?.id || "").trim(),
isDirectlyAddressed: (resolvedSettings, resolvedMessage) =>
bot.isDirectlyAddressed(resolvedSettings, resolvedMessage)
},
settings,
incoming,
recentMessages
);
assert.equal(Boolean(addressSignal?.triggered), false);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal
});
assert.equal(sent, false);
assert.equal(llmCalls.length, 1);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 0);
assert.equal(typingCallsRef.count, 1);
}); });
test("non-addressed initiative turn can still contribute when model responds", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ permissions: { replies: { replyChannelIds: [channelId] } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "lmao this queue got hands",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
store.recordMessage({
messageId: "bot-context-initiative-1",
createdAt: Date.now() - 750,
guildId: guild.id,
channelId,
authorId: "bot-1",
authorName: "clanky",
isBot: true,
content: "last bot line",
referencedMessageId: null
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-initiative-1",
content: "this match is chaos",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 1);
assert.equal(typingCallsRef.count > 0, true);
}); });
test("reply channels pass cold ambient turns to LLM for decision", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ interaction: { activity: { ambientReplyEagerness: 100 } }, permissions: { replies: { replyChannelIds: [channelId] } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "yep i'm tracking",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-initiative-no-context",
content: "anyone got loadout ideas",
replyPayloads
});
const settings = store.getSettings();
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages: [],
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(llmCalls.length, 1);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 1);
assert.equal(typingCallsRef.count > 0, true);
}); });
test("empty reply channel list disables reply-channel behavior everywhere (explicit-only)", async () => { await withTempStore(async (store) => { const channelId = "chan-default-reply"; applyBaselineSettings(store, channelId);
const bot = new ClankerBot({
appConfig: {},
store,
llm: null,
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const publicChannel = buildChannel({
guild,
channelId,
channelSendPayloads: [],
typingCallsRef: { count: 0 }
});
const privateThread = buildPrivateThreadChannel({ guild });
bot.client.channels.cache.set(publicChannel.id, publicChannel);
bot.client.channels.cache.set(privateThread.id, privateThread);
const settings = store.getSettings();
assert.equal(isReplyChannelForPermissions(settings, publicChannel.id), false);
assert.equal(isReplyChannelForPermissions(settings, privateThread.id), false);
}); });
test("non-addressed turn is dropped before llm when unsolicited gate is closed", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ permissions: { replies: { allowUnsolicitedReplies: false } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "this should never be used",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-gated",
content: "this should stay between humans",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, false);
assert.equal(llmCalls.length, 0);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 0);
assert.equal(typingCallsRef.count, 0);
}); });
test("direct-addressed turn bypasses unsolicited gate and marks response as required", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ permissions: { replies: { allowUnsolicitedReplies: false } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "yeah i'm here, what's up",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-direct",
content: "clanky you there?",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: true,
inferred: false,
triggered: true,
reason: "direct"
}
});
assert.equal(sent, true);
assert.equal(llmCalls.length, 1);
assert.equal(replyPayloads.length + channelSendPayloads.length, 1);
assert.equal(typingCallsRef.count > 0, true);
}); });
test("text reply follow-up can run web search and append cited sources", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ agentStack: { overrides: { researchRuntime: "local_external_search" }, runtimeConfig: { research: { enabled: true, maxSearchesPerHour: 8 } } } });
const llmCalls = [];
const searchCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
if (llmCalls.length === 1) {
return {
text: "",
toolCalls: [
{
id: "tc_web_1",
name: "web_search",
input: { query: "latest rust stable version" }
}
],
rawContent: [
{ type: "text", text: "" },
{ type: "tool_use", id: "tc_web_1", name: "web_search", input: { query: "latest rust stable version" } }
],
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
return {
text: JSON.stringify({
text: "latest stable rust is [1]",
skip: false,
reactionEmoji: null,
media: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: {
isConfigured() {
return true;
},
async searchAndRead(payload) {
searchCalls.push(payload);
return {
query: String(payload?.query || "").trim(),
results: [
{
title: "Rust 1.90.0",
url: "https://blog.rust-lang.org/2025/09/18/Rust-1.90.0.html",
domain: "blog.rust-lang.org",
snippet: "Rust 1.90.0 is released."
}
],
fetchedPages: 1,
providerUsed: "brave",
providerFallbackUsed: false
};
}
},
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
recordSameAuthorFollowupContext(store, {
guildId: guild.id,
channelId,
humanMessageId: "human-context-search-1",
botMessageId: "bot-context-search-1"
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-web-followup",
content: "what rust version is stable right now?",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(llmCalls.length, 2);
assert.equal(searchCalls.length, 1);
assert.equal(searchCalls[0]?.query, "latest rust stable version");
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 1);
assert.equal(typeof channelSendPayloads[0]?.content, "string");
assert.equal(channelSendPayloads[0].content.length > 0, true);
}); });
test("reply follow-up regeneration can add history images when model requests image lookup", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ interaction: { activity: { ambientReplyEagerness: 100 } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
if (llmCalls.length === 1) {
return {
text: "",
toolCalls: [
{
id: "tc_img_1",
name: "image_lookup",
input: { query: "that dog starter photo" }
}
],
rawContent: [
{ type: "text", text: "" },
{ type: "tool_use", id: "tc_img_1", name: "image_lookup", input: { query: "that dog starter photo" } }
],
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
return {
text: JSON.stringify({
text: "that one was the dog starter image",
skip: false,
reactionEmoji: null,
media: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
store.recordMessage({
messageId: "img-context-1",
createdAt: Date.now() - 3_000,
guildId: guild.id,
channelId,
authorId: "user-2",
authorName: "smelly conk",
isBot: false,
content:
"https://cdn.discordapp.com/attachments/chan-1/9001/starter-dog.jpg?ex=69a358b6&is=69a20736&hm=abc",
referencedMessageId: null
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-image-lookup",
content: "my bad, what is the photo referencing?",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(replyPayloads.length + channelSendPayloads.length, 1);
assert.equal(llmCalls.length, 2);
assert.match(String(llmCalls[0]?.userPrompt || ""), /smelly conk: \[IMG 1 by smelly conk/);
assert.doesNotMatch(String(llmCalls[0]?.userPrompt || ""), /Current message attachments:
- starter-dog/); const secondCallContext = llmCalls[1]?.contextMessages || []; const hasToolResult = secondCallContext.some((msg) => Array.isArray(msg?.content) && msg.content.some((c) => c?.type === "tool_result") ); assert.equal(hasToolResult, true); }); });
test("image lookup tool accepts direct IMG refs from chat history", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId);
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
if (llmCalls.length === 1) {
return {
text: "",
toolCalls: [
{
id: "tc_img_1",
name: "image_lookup",
input: { imageId: "IMG 1" }
}
],
rawContent: [
{ type: "text", text: "" },
{ type: "tool_use", id: "tc_img_1", name: "image_lookup", input: { imageId: "IMG 1" } }
],
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
return {
text: JSON.stringify({
text: "yep that was the earlier image",
skip: false,
reactionEmoji: null,
media: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
store.recordMessage({
messageId: "img-context-1",
createdAt: Date.now() - 3_000,
guildId: guild.id,
channelId,
authorId: "bot-1",
authorName: "clanky",
isBot: true,
content: "https://cdn.discordapp.com/attachments/chan-1/9001/selfie.png?ex=69a358b6&is=69a20736&hm=abc",
referencedMessageId: null
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-image-lookup-direct-ref",
content: "what was in that earlier pic",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(llmCalls.length, 2);
assert.equal(Array.isArray(llmCalls[1]?.imageInputs), true);
assert.equal(llmCalls[1].imageInputs.length, 1);
assert.equal(llmCalls[1].imageInputs[0].filename, "selfie.png");
}); });
test("reply tool loop keeps remaining concurrent tool results when one concurrent tool throws", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); patchTestSettings(store, { permissions: { devTasks: { allowedUserIds: ["user-1"] } }, agentStack: { runtimeConfig: { devTeam: { swarm: { enabled: false, command: "", args: [] }, codexCli: { enabled: true } } } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
if (llmCalls.length === 1) {
return {
text: "",
toolCalls: [
{
id: "tc_browser_1",
name: "browser_browse",
input: { query: "open the docs" }
},
{
id: "tc_code_1",
name: "spawn_code_worker",
input: { task: "inspect the repo status" }
}
],
rawContent: [
{ type: "text", text: "" },
{
type: "tool_use",
id: "tc_browser_1",
name: "browser_browse",
input: { query: "open the docs" }
},
{
type: "tool_use",
id: "tc_code_1",
name: "spawn_code_worker",
input: { task: "inspect the repo status" }
}
],
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
return {
text: JSON.stringify({
text: "kept the surviving tool result",
skip: false,
reactionEmoji: null,
media: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const originalToReplyPipelineRuntime = bot.toReplyPipelineRuntime.bind(bot);
bot.toReplyPipelineRuntime = () => ({
...originalToReplyPipelineRuntime(),
buildSubAgentSessionsRuntime: () => ({
manager: bot.subAgentSessions,
createBrowserSession() {
throw new Error("browser session init exploded");
}
})
});
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
store.recordMessage({
messageId: "bot-context-concurrent-tools",
createdAt: Date.now() - 750,
guildId: guild.id,
channelId,
authorId: "bot-1",
authorName: "clanky",
isBot: true,
content: "last bot line",
referencedMessageId: null
});
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-concurrent-tools",
content: "check both tools",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
recentMessages,
addressSignal: {
direct: false,
inferred: false,
triggered: false,
reason: "llm_decides"
}
});
assert.equal(sent, true);
assert.equal(llmCalls.length, 2);
assert.equal(channelSendPayloads.length, 1);
const followupContext = JSON.stringify(llmCalls[1]?.contextMessages || []);
assert.match(followupContext, /spawn_code_worker failed|Swarm worker runtime is not available/);
assert.match(followupContext, /browser_browse failed: browser session init exploded/);
}); });
test("reply generation passes a structured JSON schema contract for voice intent directives", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ voice: { enabled: true, admission: { intentConfidenceThreshold: 0.75 } } });
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
return {
text: JSON.stringify({
text: "yo",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
imageLookupQuery: null,
memoryLine: null,
selfMemoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-voice-schema-contract",
content: "yo clanker",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: true,
inferred: false,
triggered: true,
reason: "direct"
}
});
assert.equal(sent, true);
assert.equal(llmCalls.length >= 1, true);
assert.equal(typeof llmCalls[0]?.jsonSchema, "string");
assert.match(String(llmCalls[0]?.jsonSchema || ""), /"screenWatchIntent"/);
assert.equal(replyPayloads.length + channelSendPayloads.length, 1);
}); });
test("voice intent below confidence threshold falls back to normal text reply path", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ voice: { enabled: true, admission: { intentConfidenceThreshold: 0.9 } } });
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
let joinCallCount = 0;
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate() {
return {
text: JSON.stringify({
text: "yo say less",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "join", confidence: 0.5, reason: "weak intent guess" },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
bot.voiceSessionManager.requestJoin = async () => {
joinCallCount += 1;
return true;
};
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-voice-low-confidence",
content: "clanker join vc maybe?",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: true,
inferred: false,
triggered: true,
reason: "direct"
}
});
assert.equal(sent, true);
assert.equal(joinCallCount, 0);
assert.equal(replyPayloads.length + channelSendPayloads.length, 1);
assert.equal(typingCallsRef.count > 0, true);
}); });
test("smoke: 'clanka look at my screen' initiates a screen watch fallback link message", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId);
const shareUrl = "https://public.example.com/share/token-123";
const llmCalls = [];
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const createSessionCalls = [];
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate(payload) {
llmCalls.push(payload);
if (String(payload?.trace?.source || "") === "voice_operational_message") {
return {
text: `bet, open this and start sharing: ${shareUrl}`,
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
return {
text: JSON.stringify({
text: "[SKIP]",
skip: true,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
bot.attachScreenShareSessionManager({
async createSession(args) {
createSessionCalls.push(args);
return {
ok: true,
shareUrl,
expiresInMinutes: 12
};
}
});
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-screen-share-request",
content: "clanka look at my screen",
replyPayloads
});
const settings = store.getSettings();
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages: [],
addressSignal: {
direct: true,
inferred: true,
triggered: true,
reason: "name_variant"
}
});
assert.equal(sent, true);
assert.equal(replyPayloads.length + channelSendPayloads.length, 1);
assert.equal(createSessionCalls.length, 1);
assert.equal(createSessionCalls[0]?.guildId, guild.id);
assert.equal(createSessionCalls[0]?.channelId, channel.id);
assert.equal(createSessionCalls[0]?.requesterUserId, "user-1");
assert.equal(createSessionCalls[0]?.targetUserId, "user-1");
assert.equal(createSessionCalls[0]?.source, "message_event");
const sentContent = String(replyPayloads[0]?.content || channelSendPayloads[0]?.content || "");
assert.equal(sentContent.includes(shareUrl), true);
const operationalCall = llmCalls.find(
(call) => String(call?.trace?.source || "") === "voice_operational_message"
);
assert.equal(Boolean(operationalCall), true);
}); });
test("initiative-channel direct turns can be routed to thread replies when policy chooses reply mode", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ permissions: { replies: { replyChannelIds: [channelId] } } });
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate() {
return {
text: JSON.stringify({
text: "threaded response",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
bot.shouldSendAsReply = () => true;
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-initiative-threaded",
content: "clanker respond in thread",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: true,
inferred: false,
triggered: true,
reason: "direct"
}
});
assert.equal(sent, true);
assert.equal(replyPayloads.length + channelSendPayloads.length, 1);
}); });
test("initiative-channel direct turns can be routed to standalone channel messages when policy chooses standalone mode", async () => { await withTempStore(async (store) => { const channelId = "chan-1"; applyBaselineSettings(store, channelId); store.patchSettings({ permissions: { replies: { replyChannelIds: [channelId] } } });
const replyPayloads = [];
const channelSendPayloads = [];
const typingCallsRef = { count: 0 };
const bot = new ClankerBot({
appConfig: {},
store,
llm: {
async generate() {
return {
text: JSON.stringify({
text: "standalone response",
skip: false,
reactionEmoji: null,
media: null,
webSearchQuery: null,
memoryLookupQuery: null,
memoryLine: null,
automationAction: { operation: "none" },
voiceIntent: { intent: "none", confidence: 0, reason: null },
screenWatchIntent: { action: "none", confidence: 0, reason: null }
}),
provider: "test",
model: "test-model",
usage: null,
costUsd: 0
};
}
},
memory: null,
discovery: null,
search: null,
gifs: null,
video: null
});
bot.client.user = {
id: "bot-1",
username: "clanky",
tag: "clanky#0001"
};
bot.shouldSendAsReply = () => false;
const guild = buildGuild();
const channel = buildChannel({ guild, channelId, channelSendPayloads, typingCallsRef });
const incoming = buildIncomingMessage({
guild,
channel,
messageId: "msg-initiative-standalone",
content: "clanker respond standalone",
replyPayloads
});
const settings = store.getSettings();
const recentMessages = store.getRecentMessages(
channelId,
getMemorySettings(settings).promptSlice.maxRecentMessages
);
const sent = await bot.maybeReplyToMessage(incoming, settings, {
source: "message_event",
forceRespond: true,
recentMessages,
addressSignal: {
direct: true,
inferred: false,
triggered: true,
reason: "direct"
}
});
assert.equal(sent, true);
assert.equal(replyPayloads.length, 0);
assert.equal(channelSendPayloads.length, 1);
}); });
