( messages: readonly Message[], onReaction: (reaction: string) => void, )
| 396 | } |
| 397 | |
| 398 | export async function fireCompanionObserver( |
| 399 | messages: readonly Message[], |
| 400 | onReaction: (reaction: string) => void, |
| 401 | ): Promise<void> { |
| 402 | const companion = getCompanion() |
| 403 | if (!companion || getGlobalConfig().companionMuted) { |
| 404 | lastObservedMessageCount = messages.length |
| 405 | return |
| 406 | } |
| 407 | |
| 408 | const addressed = wasBuddyAddressed(messages, companion.name) |
| 409 | const newToolOutput = collectRecentToolResults(messages.slice(lastObservedMessageCount)) |
| 410 | lastObservedMessageCount = messages.length |
| 411 | |
| 412 | const recentToolOutput = collectRecentToolResults( |
| 413 | messages.slice(-REACTION_TRANSCRIPT_WINDOW), |
| 414 | ) |
| 415 | const reason = addressed ? null : classifyToolResultContext(newToolOutput) |
| 416 | const effectiveReason = reason ?? 'turn' |
| 417 | const now = Date.now() |
| 418 | |
| 419 | if (!addressed && !reason && now - lastReactionAt < BUDDY_REACTION_COOLDOWN_MS) { |
| 420 | return |
| 421 | } |
| 422 | |
| 423 | const transcript = buildReactionTranscript(messages, recentToolOutput) |
| 424 | if (!transcript.trim()) { |
| 425 | return |
| 426 | } |
| 427 | |
| 428 | lastReactionAt = now |
| 429 | const reaction = await postBuddyReaction( |
| 430 | companion, |
| 431 | transcript, |
| 432 | effectiveReason, |
| 433 | recentReactions, |
| 434 | addressed, |
| 435 | AbortSignal.timeout(BUDDY_REQUEST_TIMEOUT_MS), |
| 436 | ) |
| 437 | |
| 438 | if (!reaction) { |
| 439 | return |
| 440 | } |
| 441 | |
| 442 | pushRecentReaction(reaction) |
| 443 | onReaction(reaction) |
| 444 | } |
| 445 | |
| 446 | export async function buildHatchContext(): Promise<string> { |
| 447 | const cwd = getCwd() |
no test coverage detected