fix probe logic

This commit is contained in:
jaydenfyi 2026-01-25 10:09:53 +08:00
parent 7234f9d24b
commit 3dbca21334
3 changed files with 53 additions and 41 deletions

View File

@ -59,45 +59,23 @@ function readStringParam(
return options.trim !== false ? str.trim() : str;
}
/** Supported Twitch actions */
const TWITCH_ACTIONS = new Set(["send" as const]);
type TwitchAction = typeof TWITCH_ACTIONS extends Set<infer U> ? U : never;
/**
* Twitch message actions adapter.
*
* Supports the "send" action for sending messages to Twitch channels.
*
* NOTE: Currently only the "send" action is supported. The action gate pattern
* (configurable actions per channel) will be added in a future update. For now,
* the available actions are hardcoded as Twitch chat only supports sending messages.
*/
export const twitchMessageActions: ChannelMessageActionAdapter = {
/**
* List available actions for this channel.
*
* Currently supports:
* - "send" - Send a message to a Twitch channel
*
* Future actions may include:
* - "ban"/"timeout" - Moderation actions
* - "announce" - Send announcements
*
* @param params - Parameters including config
* @returns Array of available action names
*/
listActions: () => {
// Action gate pattern: In future, this will check channel config to determine
// which actions are enabled. For now, all Twitch channels support "send".
const actions = new Set<string>(["send"]);
return Array.from(actions);
},
listActions: () => [...TWITCH_ACTIONS],
/**
* Check if an action is supported.
*
* @param params - Action to check
* @returns true if the action is supported
*/
supportsAction: ({ action }) => {
return action === "send";
},
supportsAction: ({ action }) => TWITCH_ACTIONS.has(action as TwitchAction),
/**
* Extract tool send parameters from action arguments.

View File

@ -22,7 +22,7 @@ const TwitchAccountSchema = z.object({
enabled: z.boolean().optional(),
/** Allowlist of Twitch user IDs who can interact with the bot (use IDs for safety, not usernames) */
allowFrom: z.array(z.string()).optional(),
/** Roles allowed to interact with the bot (e.g., ["mod", "vip", "sub"]) */
/** Roles allowed to interact with the bot (e.g., ["moderator", "vip", "subscriber"]) */
allowedRoles: z.array(TwitchRoleSchema).optional(),
/** Require @mention to trigger bot responses */
requireMention: z.boolean().optional(),

View File

@ -40,14 +40,6 @@ export async function probeTwitch(
let client: ChatClient | undefined;
try {
// Create a timeout promise
const timeout = new Promise<never>((_, reject) => {
setTimeout(
() => reject(new Error(`connection timeout after ${timeoutMs}ms`)),
timeoutMs,
);
});
// Create auth provider with the token
const authProvider = new StaticAuthProvider(
account.clientId ?? "",
@ -59,11 +51,53 @@ export async function probeTwitch(
authProvider,
});
// Race between connection and timeout
await Promise.race([client.connect(), timeout]);
// Create a promise that resolves when connected
const connectionPromise = new Promise<void>((resolve, reject) => {
let settled = false;
let connectListener: ReturnType<ChatClient["onConnect"]> | undefined;
let disconnectListener: ReturnType<ChatClient["onDisconnect"]> | undefined;
let authFailListener: ReturnType<ChatClient["onAuthenticationFailure"]> | undefined;
// Wait a moment for the connection to fully establish
await new Promise<void>((resolve) => setTimeout(resolve, 500));
const cleanup = () => {
if (settled) return;
settled = true;
// Remove all listeners
connectListener?.unbind();
disconnectListener?.unbind();
authFailListener?.unbind();
};
// Success: connection established
connectListener = client?.onConnect(() => {
cleanup();
resolve();
});
// Failure: disconnected (e.g., auth failed)
disconnectListener = client?.onDisconnect((_manually, reason) => {
cleanup();
reject(reason || new Error("Disconnected"));
});
// Failure: authentication failed
authFailListener = client?.onAuthenticationFailure(() => {
cleanup();
reject(new Error("Authentication failed"));
});
});
// Create timeout promise
const timeout = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
});
// Set up listeners BEFORE connecting, then race against timeout
client.connect();
await Promise.race([connectionPromise, timeout]);
// Clean up connection before returning
client.quit();
client = undefined;
// If we got here, connection was successful
return {