refactor(hooks): split session-notification and unstable-agent-babysitter
Extract notification and babysitter logic: - session-notification-formatting.ts, session-notification-scheduler.ts - session-notification-sender.ts, session-todo-status.ts - task-message-analyzer.ts: message analysis for babysitter hook
This commit is contained in:
@@ -1 +1,9 @@
|
||||
export { createUnstableAgentBabysitterHook } from "./unstable-agent-babysitter-hook"
|
||||
export {
|
||||
buildReminder,
|
||||
extractMessages,
|
||||
getMessageInfo,
|
||||
getMessageParts,
|
||||
isUnstableTask,
|
||||
THINKING_SUMMARY_MAX_CHARS,
|
||||
} from "./task-message-analyzer"
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { BackgroundTask } from "../../features/background-agent"
|
||||
|
||||
export const THINKING_SUMMARY_MAX_CHARS = 500 as const
|
||||
|
||||
type MessageInfo = {
|
||||
role?: string
|
||||
agent?: string
|
||||
model?: { providerID: string; modelID: string }
|
||||
providerID?: string
|
||||
modelID?: string
|
||||
}
|
||||
|
||||
type MessagePart = {
|
||||
type?: string
|
||||
text?: string
|
||||
thinking?: string
|
||||
}
|
||||
|
||||
function hasData(value: unknown): value is { data?: unknown } {
|
||||
return typeof value === "object" && value !== null && "data" in value
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null
|
||||
}
|
||||
|
||||
export function getMessageInfo(value: unknown): MessageInfo | undefined {
|
||||
if (!isRecord(value)) return undefined
|
||||
if (!isRecord(value.info)) return undefined
|
||||
const info = value.info
|
||||
const modelValue = isRecord(info.model)
|
||||
? info.model
|
||||
: undefined
|
||||
const model = modelValue && typeof modelValue.providerID === "string" && typeof modelValue.modelID === "string"
|
||||
? { providerID: modelValue.providerID, modelID: modelValue.modelID }
|
||||
: undefined
|
||||
return {
|
||||
role: typeof info.role === "string" ? info.role : undefined,
|
||||
agent: typeof info.agent === "string" ? info.agent : undefined,
|
||||
model,
|
||||
providerID: typeof info.providerID === "string" ? info.providerID : undefined,
|
||||
modelID: typeof info.modelID === "string" ? info.modelID : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export function getMessageParts(value: unknown): MessagePart[] {
|
||||
if (!isRecord(value)) return []
|
||||
if (!Array.isArray(value.parts)) return []
|
||||
return value.parts.filter(isRecord).map((part) => ({
|
||||
type: typeof part.type === "string" ? part.type : undefined,
|
||||
text: typeof part.text === "string" ? part.text : undefined,
|
||||
thinking: typeof part.thinking === "string" ? part.thinking : undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
export function extractMessages(value: unknown): unknown[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
if (hasData(value) && Array.isArray(value.data)) {
|
||||
return value.data
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function isUnstableTask(task: BackgroundTask): boolean {
|
||||
if (task.isUnstableAgent === true) return true
|
||||
const modelId = task.model?.modelID?.toLowerCase()
|
||||
return modelId ? modelId.includes("gemini") || modelId.includes("minimax") : false
|
||||
}
|
||||
|
||||
export function buildReminder(task: BackgroundTask, summary: string | null, idleMs: number): string {
|
||||
const idleSeconds = Math.round(idleMs / 1000)
|
||||
const summaryText = summary ?? "(No thinking trace available)"
|
||||
return `Unstable background agent appears idle for ${idleSeconds}s.
|
||||
|
||||
Task ID: ${task.id}
|
||||
Description: ${task.description}
|
||||
Agent: ${task.agent}
|
||||
Status: ${task.status}
|
||||
Session ID: ${task.sessionID ?? "N/A"}
|
||||
|
||||
Thinking summary (first ${THINKING_SUMMARY_MAX_CHARS} chars):
|
||||
${summaryText}
|
||||
|
||||
Suggested actions:
|
||||
- background_output task_id="${task.id}" full_session=true include_thinking=true include_tool_results=true message_limit=50
|
||||
- background_cancel taskId="${task.id}"
|
||||
|
||||
This is a reminder only. No automatic action was taken.`
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
import type { BackgroundManager, BackgroundTask } from "../../features/background-agent"
|
||||
import type { BackgroundManager } from "../../features/background-agent"
|
||||
import { getMainSessionID, getSessionAgent } from "../../features/claude-code-session-state"
|
||||
import { log } from "../../shared/logger"
|
||||
import {
|
||||
buildReminder,
|
||||
extractMessages,
|
||||
getMessageInfo,
|
||||
getMessageParts,
|
||||
isUnstableTask,
|
||||
THINKING_SUMMARY_MAX_CHARS,
|
||||
} from "./task-message-analyzer"
|
||||
|
||||
const HOOK_NAME = "unstable-agent-babysitter"
|
||||
const DEFAULT_TIMEOUT_MS = 120000
|
||||
const COOLDOWN_MS = 5 * 60 * 1000
|
||||
const THINKING_SUMMARY_MAX_CHARS = 500 as const
|
||||
|
||||
type BabysittingConfig = {
|
||||
timeout_ms?: number
|
||||
@@ -43,72 +50,6 @@ type BabysitterOptions = {
|
||||
config?: BabysittingConfig
|
||||
}
|
||||
|
||||
type MessageInfo = {
|
||||
role?: string
|
||||
agent?: string
|
||||
model?: { providerID: string; modelID: string }
|
||||
providerID?: string
|
||||
modelID?: string
|
||||
}
|
||||
|
||||
type MessagePart = {
|
||||
type?: string
|
||||
text?: string
|
||||
thinking?: string
|
||||
}
|
||||
|
||||
function hasData(value: unknown): value is { data?: unknown } {
|
||||
return typeof value === "object" && value !== null && "data" in value
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null
|
||||
}
|
||||
|
||||
function getMessageInfo(value: unknown): MessageInfo | undefined {
|
||||
if (!isRecord(value)) return undefined
|
||||
if (!isRecord(value.info)) return undefined
|
||||
const info = value.info
|
||||
const modelValue = isRecord(info.model)
|
||||
? info.model
|
||||
: undefined
|
||||
const model = modelValue && typeof modelValue.providerID === "string" && typeof modelValue.modelID === "string"
|
||||
? { providerID: modelValue.providerID, modelID: modelValue.modelID }
|
||||
: undefined
|
||||
return {
|
||||
role: typeof info.role === "string" ? info.role : undefined,
|
||||
agent: typeof info.agent === "string" ? info.agent : undefined,
|
||||
model,
|
||||
providerID: typeof info.providerID === "string" ? info.providerID : undefined,
|
||||
modelID: typeof info.modelID === "string" ? info.modelID : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function getMessageParts(value: unknown): MessagePart[] {
|
||||
if (!isRecord(value)) return []
|
||||
if (!Array.isArray(value.parts)) return []
|
||||
return value.parts.filter(isRecord).map((part) => ({
|
||||
type: typeof part.type === "string" ? part.type : undefined,
|
||||
text: typeof part.text === "string" ? part.text : undefined,
|
||||
thinking: typeof part.thinking === "string" ? part.thinking : undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
function extractMessages(value: unknown): unknown[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
if (hasData(value) && Array.isArray(value.data)) {
|
||||
return value.data
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function isUnstableTask(task: BackgroundTask): boolean {
|
||||
if (task.isUnstableAgent === true) return true
|
||||
const modelId = task.model?.modelID?.toLowerCase()
|
||||
return modelId ? modelId.includes("gemini") || modelId.includes("minimax") : false
|
||||
}
|
||||
|
||||
async function resolveMainSessionTarget(
|
||||
ctx: BabysitterContext,
|
||||
@@ -169,27 +110,6 @@ async function getThinkingSummary(ctx: BabysitterContext, sessionID: string): Pr
|
||||
}
|
||||
}
|
||||
|
||||
function buildReminder(task: BackgroundTask, summary: string | null, idleMs: number): string {
|
||||
const idleSeconds = Math.round(idleMs / 1000)
|
||||
const summaryText = summary ?? "(No thinking trace available)"
|
||||
return `Unstable background agent appears idle for ${idleSeconds}s.
|
||||
|
||||
Task ID: ${task.id}
|
||||
Description: ${task.description}
|
||||
Agent: ${task.agent}
|
||||
Status: ${task.status}
|
||||
Session ID: ${task.sessionID ?? "N/A"}
|
||||
|
||||
Thinking summary (first ${THINKING_SUMMARY_MAX_CHARS} chars):
|
||||
${summaryText}
|
||||
|
||||
Suggested actions:
|
||||
- background_output task_id="${task.id}" full_session=true include_thinking=true include_tool_results=true message_limit=50
|
||||
- background_cancel taskId="${task.id}"
|
||||
|
||||
This is a reminder only. No automatic action was taken.`
|
||||
}
|
||||
|
||||
export function createUnstableAgentBabysitterHook(ctx: BabysitterContext, options: BabysitterOptions) {
|
||||
const reminderCooldowns = new Map<string, number>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user