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
+86 -5
View File
@@ -34,7 +34,7 @@ async function connectLoop() {
puppet = new Puppet(client);
client.onEvent(event => {
puppet!.handleEvent(event).catch(error => console.error("[daemon] event error:", error));
void puppet!.enqueueEvent(event);
});
coreConnected = true;
@@ -62,6 +62,23 @@ function jsonText(value: unknown) {
return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] };
}
/** Read-only tools that the batch tool can run. Mutating tools stay out. */
const READ_ONLY_TOOLS: Record<string, (args: any) => Promise<unknown> | unknown> = {
get_state: () => requireCore().getState(),
get_voice_state: args => requireCore().voiceGet(args?.userId, args?.guildId),
get_context: args =>
requirePuppet().getContext(args?.channelId, {
maxMessages: args?.maxMessages,
maxChars: args?.maxChars,
refresh: args?.refresh
}),
get_message_log: () => requirePuppet().getLog(),
get_action_log: args => requirePuppet().readActionLog(args?.limit ?? 100),
get_puppet_config: () => requirePuppet().getConfig(),
whitelist_list: () => ({ ownerId: requirePuppet().owner, whitelist: requirePuppet().whitelist }),
read_messages: args => paced(() => requireCore().readMessages(args?.channelId, args?.limit ?? 20))
};
function buildServer(): McpServer {
const server = new McpServer({ name: "veskslop-control", version: "0.0.0" });
@@ -160,6 +177,8 @@ function buildServer(): McpServer {
config: z.object({
contextMessages: z.number().int().min(1).max(50).optional(),
contextChars: z.number().int().min(200).max(20000).optional(),
fetchCooldownSeconds: z.number().int().min(0).max(300).optional(),
continuationSeconds: z.number().int().min(0).max(300).optional(),
responsePolicy: z
.object({
defaultMode: z.enum(["channel", "dm"]).optional(),
@@ -173,10 +192,70 @@ function buildServer(): McpServer {
async ({ config }) => jsonText(requirePuppet().updateConfig(config))
);
server.registerTool(
"get_voice_state",
{
description: "Which voice channel a user is in and who else is there. Defaults to the logged-in user.",
inputSchema: {
userId: z.string().optional().describe("User ID. Defaults to the logged-in user."),
guildId: z.string().optional().describe("Guild ID. Omit for a DM call.")
}
},
async ({ userId, guildId }) => jsonText(await requireCore().voiceGet(userId, guildId))
);
server.registerTool(
"get_action_log",
{
description: "Read the persistent action log: triggers, commands, replies, and errors.",
inputSchema: {
limit: z.number().int().min(1).max(500).default(100).describe("How many entries")
}
},
async ({ limit }) => jsonText(requirePuppet().readActionLog(limit))
);
server.registerTool(
"run_commands",
{
description: "Run several read-only commands in one call, in order.",
inputSchema: {
commands: z
.array(
z.object({
tool: z.string().describe("Read-only tool name"),
arguments: z.record(z.string(), z.any()).optional()
})
)
.min(1)
.max(10)
}
},
async ({ commands }) => {
const results: Array<Record<string, unknown>> = [];
for (const item of commands) {
const handler = READ_ONLY_TOOLS[item.tool];
if (!handler) {
results.push({ tool: item.tool, ok: false, error: "unknown or non-read-only tool" });
continue;
}
try {
results.push({ tool: item.tool, ok: true, result: await handler((item.arguments ?? {}) as any) });
} catch (error) {
results.push({ tool: item.tool, ok: false, error: String((error as Error).message) });
}
}
return jsonText({ results });
}
);
server.registerTool(
"whitelist_list",
{
description: "List the puppet owner and trusted (whitelisted) user IDs."
description: "List the puppet owner and trusted users with their tiers."
},
async () => jsonText({ ownerId: requirePuppet().owner, whitelist: requirePuppet().whitelist })
);
@@ -184,12 +263,14 @@ function buildServer(): McpServer {
server.registerTool(
"whitelist_add",
{
description: "Add users to the puppet whitelist.",
description: "Add users to the puppet whitelist with an optional tier. Defaults to 'user'.",
inputSchema: {
userIds: z.array(z.string()).min(1).describe("User IDs to trust")
userIds: z.array(z.string()).min(1).describe("User IDs to trust"),
tier: z.enum(["user", "trusted", "admin"]).optional().describe("Trust tier")
}
},
async ({ userIds }) => jsonText({ whitelist: requirePuppet().addToWhitelist(userIds) })
async ({ userIds, tier }) =>
jsonText({ whitelist: requirePuppet().addToWhitelist(userIds, tier ?? "user") })
);
server.registerTool(