style: lint
This commit is contained in:
@@ -18,9 +18,9 @@ export async function dispatchAction(
|
||||
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
shouldContinue: true,
|
||||
result: `System Error: Invalid action payload: ${parseResult.issues.map(i => i.message).join(', ')}`,
|
||||
shouldContinue: true,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ export async function dispatchAction(
|
||||
|
||||
if (!handler) {
|
||||
return {
|
||||
success: false,
|
||||
shouldContinue: true,
|
||||
result: `System Error: Action "${validatedAction.action}" is not implemented.`,
|
||||
shouldContinue: true,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ export async function dispatchAction(
|
||||
catch (error) {
|
||||
log.withError(error as Error).error('Action execution failed')
|
||||
return {
|
||||
success: false,
|
||||
shouldContinue: true,
|
||||
result: `System Error: Execution failed: ${(error as Error).message}`,
|
||||
shouldContinue: true,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,6 @@ import type { BotContext } from '../types'
|
||||
import { pushToEventQueue } from '../../lib/db'
|
||||
import { onMessageArrival } from './scheduler'
|
||||
|
||||
/**
|
||||
* Set up the ready event handler
|
||||
* Logs connection information when Satori client is ready
|
||||
*/
|
||||
export function setupReadyEventHandler(
|
||||
satoriClient: SatoriClient,
|
||||
logger: Logg,
|
||||
): void {
|
||||
satoriClient.onReady((ready: SatoriReadyBody) => {
|
||||
logger.log('Satori client ready:', ready)
|
||||
logger.log(`Connected to ${ready.logins.length} platform(s)`)
|
||||
|
||||
for (const login of ready.logins) {
|
||||
logger.log(`- ${login.platform} (${login.self_id}): ${login.status}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the message-created event handler
|
||||
* Processes incoming messages, filters bot's own messages, and triggers bot responses
|
||||
@@ -68,3 +50,21 @@ export function setupMessageEventHandler(
|
||||
await onMessageArrival(botContext, satoriClient)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the ready event handler
|
||||
* Logs connection information when Satori client is ready
|
||||
*/
|
||||
export function setupReadyEventHandler(
|
||||
satoriClient: SatoriClient,
|
||||
logger: Logg,
|
||||
): void {
|
||||
satoriClient.onReady((ready: SatoriReadyBody) => {
|
||||
logger.log('Satori client ready:', ready)
|
||||
logger.log(`Connected to ${ready.logins.length} platform(s)`)
|
||||
|
||||
for (const login of ready.logins) {
|
||||
logger.log(`- ${login.platform} (${login.self_id}): ${login.status}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ export async function handleLoopStep(
|
||||
// Dynamic history injection: Fetch last 10 messages from DB
|
||||
const dbMessages = await getRecentMessages(chatCtx.channelId, 10)
|
||||
const llmMessages: LLMMessage[] = dbMessages.map(m => ({
|
||||
role: m.userId === chatCtx.selfId ? 'assistant' : 'user',
|
||||
content: m.content,
|
||||
role: m.userId === chatCtx.selfId ? 'assistant' : 'user',
|
||||
}))
|
||||
|
||||
const actionPayload = await imagineAnAction(
|
||||
@@ -79,8 +79,8 @@ export async function handleLoopStep(
|
||||
llmMessages,
|
||||
chatCtx?.actions || [],
|
||||
{
|
||||
unreadEvents: ctx.unreadEvents,
|
||||
incomingEvents: currentIncoming ? [currentIncoming] : [],
|
||||
unreadEvents: ctx.unreadEvents,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -129,6 +129,14 @@ export async function loopIterationForChannel(
|
||||
await handleLoopStep(bot, satoriClient, chatContext, incomingEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the periodic loop
|
||||
* Begins the recursive periodic processing of channels with unread messages
|
||||
*/
|
||||
export function startPeriodicLoop(botCtx: BotContext, satoriClient: SatoriClient) {
|
||||
loopPeriodic(botCtx, satoriClient)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process periodic loop iteration for existing channels with unread messages
|
||||
* Only processes channels that have unread messages to avoid unnecessary LLM calls
|
||||
@@ -200,14 +208,6 @@ function loopPeriodic(botCtx: BotContext, satoriClient: SatoriClient) {
|
||||
}, PERIODIC_LOOP_INTERVAL_MS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the periodic loop
|
||||
* Begins the recursive periodic processing of channels with unread messages
|
||||
*/
|
||||
export function startPeriodicLoop(botCtx: BotContext, satoriClient: SatoriClient) {
|
||||
loopPeriodic(botCtx, satoriClient)
|
||||
}
|
||||
|
||||
let isQueueConsumerRunning = false
|
||||
|
||||
/**
|
||||
@@ -267,9 +267,9 @@ export async function onMessageArrival(
|
||||
botContext.logger
|
||||
.withFields({
|
||||
channelId: chatCtx.channelId,
|
||||
sourceUserId: currMsg.event.user?.id || currMsg.event.member?.user?.id,
|
||||
selfId: chatCtx.selfId,
|
||||
messageId: currMsg.event.id,
|
||||
selfId: chatCtx.selfId,
|
||||
sourceUserId: currMsg.event.user?.id || currMsg.event.member?.user?.id,
|
||||
})
|
||||
.debug('[DEBUG] Skipping bot\'s own event in unreadEvents - filtered out')
|
||||
botContext.eventQueue.shift()
|
||||
@@ -294,7 +294,7 @@ export async function onMessageArrival(
|
||||
}
|
||||
|
||||
const unreadEventId = await pushToUnreadEvents(chatCtx.channelId, currMsg.event)
|
||||
unreadEventsForThisChannel.push({ id: unreadEventId, event: currMsg.event })
|
||||
unreadEventsForThisChannel.push({ event: currMsg.event, id: unreadEventId })
|
||||
|
||||
if (unreadEventsForThisChannel.length > MAX_UNREAD_EVENTS) {
|
||||
unreadEventsForThisChannel = unreadEventsForThisChannel.slice(-MAX_UNREAD_EVENTS)
|
||||
|
||||
@@ -20,8 +20,8 @@ export async function imagineAnAction(
|
||||
messages: LLMMessage[],
|
||||
actions: { action: Action, result: unknown }[],
|
||||
globalStates: {
|
||||
unreadEvents: Record<string, StoredUnreadEvent[]>
|
||||
incomingEvents?: SatoriEvent[]
|
||||
unreadEvents: Record<string, StoredUnreadEvent[]>
|
||||
},
|
||||
): Promise<Action | undefined> {
|
||||
const logger = useLogg('imagineAnAction').useGlobalConfig()
|
||||
@@ -57,11 +57,11 @@ export async function imagineAnAction(
|
||||
|
||||
try {
|
||||
const req = {
|
||||
abortSignal: currentAbortController?.signal,
|
||||
apiKey: config.llm.apiKey,
|
||||
baseURL: config.llm.baseUrl,
|
||||
model: config.llm.model,
|
||||
messages: requestMessages,
|
||||
abortSignal: currentAbortController?.signal,
|
||||
model: config.llm.model,
|
||||
} satisfies GenerateTextOptions
|
||||
|
||||
if (config.llm.ollamaDisableThink) {
|
||||
@@ -76,12 +76,12 @@ export async function imagineAnAction(
|
||||
}
|
||||
|
||||
logger.withFields({
|
||||
response: res.text,
|
||||
unreadEvents: Object.fromEntries(Object.entries(globalStates.unreadEvents).map(([key, value]) => [key, value.length])),
|
||||
now: new Date().toLocaleString(),
|
||||
totalTokens: res.usage.totalTokens,
|
||||
promptTokens: res.usage.inputTokens,
|
||||
completion_tokens: res.usage.outputTokens,
|
||||
now: new Date().toLocaleString(),
|
||||
promptTokens: res.usage.inputTokens,
|
||||
response: res.text,
|
||||
totalTokens: res.usage.totalTokens,
|
||||
unreadEvents: Object.fromEntries(Object.entries(globalStates.unreadEvents).map(([key, value]) => [key, value.length])),
|
||||
}).log('Generated action')
|
||||
|
||||
responseText = res.text
|
||||
|
||||
@@ -15,12 +15,12 @@ export async function createBotContext(logger: Logg): Promise<BotContext> {
|
||||
])
|
||||
|
||||
const botSelf: BotContext = {
|
||||
eventQueue,
|
||||
unreadEvents,
|
||||
processedIds: new Set(),
|
||||
logger,
|
||||
lastInteractedChannelIds: [],
|
||||
chats: new Map<string, ChatContext>(),
|
||||
eventQueue,
|
||||
lastInteractedChannelIds: [],
|
||||
logger,
|
||||
processedIds: new Set(),
|
||||
unreadEvents,
|
||||
}
|
||||
|
||||
return botSelf
|
||||
@@ -48,13 +48,13 @@ export async function ensureChatContext(botCtx: BotContext, channelId: string):
|
||||
const channelInfo = channels.find(c => c.id === channelId)
|
||||
|
||||
const newChatContext: ChatContext = {
|
||||
actions: [],
|
||||
channelId,
|
||||
currentAbortController: undefined,
|
||||
currentTask: undefined,
|
||||
isProcessing: false,
|
||||
platform: channelInfo?.platform || '',
|
||||
selfId: channelInfo?.selfId || '',
|
||||
isProcessing: false,
|
||||
currentTask: undefined,
|
||||
currentAbortController: undefined,
|
||||
actions: [],
|
||||
}
|
||||
|
||||
log
|
||||
|
||||
@@ -24,8 +24,8 @@ export const ListChannelsActionSchema = v.object({
|
||||
|
||||
export const SendMessageActionSchema = v.object({
|
||||
action: v.literal('send_message'),
|
||||
content: v.string(),
|
||||
channelId: v.string(),
|
||||
content: v.string(),
|
||||
})
|
||||
|
||||
export const ReadUnreadMessagesActionSchema = v.object({
|
||||
@@ -44,9 +44,42 @@ export const ActionSchema = v.union([
|
||||
|
||||
export type Action = v.InferOutput<typeof ActionSchema>
|
||||
|
||||
export interface BotContext {
|
||||
chats: Map<string, ChatContext>
|
||||
currentProcessingStartTime?: number
|
||||
eventQueue: PendingEvent[]
|
||||
lastInteractedChannelIds: string[]
|
||||
logger: Logg
|
||||
processedIds: Set<string>
|
||||
unreadEvents: Record<string, StoredUnreadEvent[]> // channelId -> events
|
||||
}
|
||||
|
||||
export interface CancellablePromise<T> {
|
||||
promise: Promise<T>
|
||||
cancel: () => void
|
||||
promise: Promise<T>
|
||||
}
|
||||
|
||||
export interface ChatContext {
|
||||
actions: { action: Action, result: unknown }[]
|
||||
channelId: string
|
||||
currentAbortController?: AbortController
|
||||
currentTask?: CancellablePromise<void>
|
||||
|
||||
isProcessing: boolean
|
||||
platform: string
|
||||
|
||||
selfId: string
|
||||
}
|
||||
|
||||
export interface PendingEvent {
|
||||
event: SatoriEvent
|
||||
id: string
|
||||
status: 'pending' | 'ready'
|
||||
}
|
||||
|
||||
export interface StoredUnreadEvent {
|
||||
event: SatoriEvent
|
||||
id: string
|
||||
}
|
||||
|
||||
export function cancellable<T>(promise: Promise<T>): CancellablePromise<T> {
|
||||
@@ -58,40 +91,7 @@ export function cancellable<T>(promise: Promise<T>): CancellablePromise<T> {
|
||||
})
|
||||
|
||||
return {
|
||||
promise: wrappedPromise,
|
||||
cancel: () => cancel?.(),
|
||||
promise: wrappedPromise,
|
||||
}
|
||||
}
|
||||
|
||||
export interface PendingEvent {
|
||||
id: string
|
||||
event: SatoriEvent
|
||||
status: 'pending' | 'ready'
|
||||
}
|
||||
|
||||
export interface StoredUnreadEvent {
|
||||
id: string
|
||||
event: SatoriEvent
|
||||
}
|
||||
|
||||
export interface BotContext {
|
||||
logger: Logg
|
||||
eventQueue: PendingEvent[]
|
||||
unreadEvents: Record<string, StoredUnreadEvent[]> // channelId -> events
|
||||
processedIds: Set<string>
|
||||
lastInteractedChannelIds: string[]
|
||||
currentProcessingStartTime?: number
|
||||
chats: Map<string, ChatContext>
|
||||
}
|
||||
|
||||
export interface ChatContext {
|
||||
channelId: string
|
||||
platform: string
|
||||
selfId: string
|
||||
isProcessing: boolean
|
||||
|
||||
currentTask?: CancellablePromise<void>
|
||||
currentAbortController?: AbortController
|
||||
|
||||
actions: { action: Action, result: unknown }[]
|
||||
}
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
import type { SatoriEvent, SatoriMessage } from '../adapter/satori/types'
|
||||
import type { Action, BotContext, ChatContext } from './types'
|
||||
/**
|
||||
* Intelligently truncate action history while preserving logical chains.
|
||||
* If the first action in the kept list is a 'continue', it backtracks to include
|
||||
* the action that triggered it, ensuring the LLM has full context of its sequence.
|
||||
* Format debug context for logging
|
||||
* Creates a summary of bot state for debugging
|
||||
*/
|
||||
export function trimActions(
|
||||
actions: { action: Action, result: unknown }[],
|
||||
max: number,
|
||||
keep: number,
|
||||
): { action: Action, result: unknown }[] {
|
||||
if (actions.length <= max) {
|
||||
return actions
|
||||
export function formatDebugContext(
|
||||
ctx: BotContext,
|
||||
chatCtx?: ChatContext,
|
||||
): Record<string, unknown> {
|
||||
const unreadEventsSummary = Object.fromEntries(
|
||||
Object.entries(ctx.unreadEvents).map(([key, value]) => [key, value.length]),
|
||||
)
|
||||
|
||||
const context: Record<string, unknown> = {
|
||||
messageQueueLength: ctx.eventQueue.length,
|
||||
totalUnreadCount: Object.values(ctx.unreadEvents).reduce((acc, cur) => acc + cur.length, 0),
|
||||
unreadEvents: unreadEventsSummary,
|
||||
}
|
||||
|
||||
let startIndex = actions.length - keep
|
||||
if (chatCtx) {
|
||||
context.channelId = chatCtx.channelId
|
||||
context.totalActionsInContext = chatCtx.actions.length
|
||||
|
||||
// Backtrack to avoid starting with a 'continue' action which lacks its previous context
|
||||
while (startIndex > 0) {
|
||||
const currentAction = actions[startIndex].action
|
||||
if (currentAction.action === 'continue') {
|
||||
startIndex--
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
const lastActions = chatCtx.actions.slice(-3).map(action => ({
|
||||
action: action.action.action,
|
||||
result: typeof action.result === 'string' ? action.result.substring(0, 100) : String(action.result).substring(0, 100),
|
||||
}))
|
||||
context.lastActions = lastActions
|
||||
}
|
||||
|
||||
return actions.slice(startIndex)
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,33 +69,31 @@ export function isBotOwnMessage(
|
||||
}
|
||||
|
||||
/**
|
||||
* Format debug context for logging
|
||||
* Creates a summary of bot state for debugging
|
||||
* Intelligently truncate action history while preserving logical chains.
|
||||
* If the first action in the kept list is a 'continue', it backtracks to include
|
||||
* the action that triggered it, ensuring the LLM has full context of its sequence.
|
||||
*/
|
||||
export function formatDebugContext(
|
||||
ctx: BotContext,
|
||||
chatCtx?: ChatContext,
|
||||
): Record<string, unknown> {
|
||||
const unreadEventsSummary = Object.fromEntries(
|
||||
Object.entries(ctx.unreadEvents).map(([key, value]) => [key, value.length]),
|
||||
)
|
||||
|
||||
const context: Record<string, unknown> = {
|
||||
messageQueueLength: ctx.eventQueue.length,
|
||||
unreadEvents: unreadEventsSummary,
|
||||
totalUnreadCount: Object.values(ctx.unreadEvents).reduce((acc, cur) => acc + cur.length, 0),
|
||||
export function trimActions(
|
||||
actions: { action: Action, result: unknown }[],
|
||||
max: number,
|
||||
keep: number,
|
||||
): { action: Action, result: unknown }[] {
|
||||
if (actions.length <= max) {
|
||||
return actions
|
||||
}
|
||||
|
||||
if (chatCtx) {
|
||||
context.channelId = chatCtx.channelId
|
||||
context.totalActionsInContext = chatCtx.actions.length
|
||||
let startIndex = actions.length - keep
|
||||
|
||||
const lastActions = chatCtx.actions.slice(-3).map(action => ({
|
||||
action: action.action.action,
|
||||
result: typeof action.result === 'string' ? action.result.substring(0, 100) : String(action.result).substring(0, 100),
|
||||
}))
|
||||
context.lastActions = lastActions
|
||||
// Backtrack to avoid starting with a 'continue' action which lacks its previous context
|
||||
while (startIndex > 0) {
|
||||
const currentAction = actions[startIndex].action
|
||||
if (currentAction.action === 'continue') {
|
||||
startIndex--
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
return actions.slice(startIndex)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user