import { existsSync, readdirSync } from "node:fs" import { join } from "node:path" import { MESSAGE_STORAGE } from "../../features/hook-message-injector" import type { DelegateTaskArgs } from "./types" /** * Parse a model string in "provider/model" format. */ export function parseModelString(model: string): { providerID: string; modelID: string } | undefined { const parts = model.split("/") if (parts.length >= 2) { return { providerID: parts[0], modelID: parts.slice(1).join("/") } } return undefined } /** * Get the message directory for a session, checking both direct and nested paths. */ export function getMessageDir(sessionID: string): string | null { if (!sessionID.startsWith("ses_")) return null if (!existsSync(MESSAGE_STORAGE)) return null const directPath = join(MESSAGE_STORAGE, sessionID) if (existsSync(directPath)) return directPath for (const dir of readdirSync(MESSAGE_STORAGE)) { const sessionPath = join(MESSAGE_STORAGE, dir, sessionID) if (existsSync(sessionPath)) return sessionPath } return null } /** * Format a duration between two dates as a human-readable string. */ export function formatDuration(start: Date, end?: Date): string { const duration = (end ?? new Date()).getTime() - start.getTime() const seconds = Math.floor(duration / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s` if (minutes > 0) return `${minutes}m ${seconds % 60}s` return `${seconds}s` } /** * Context for error formatting. */ export interface ErrorContext { operation: string args?: DelegateTaskArgs sessionID?: string agent?: string category?: string } /** * Format an error with detailed context for debugging. */ export function formatDetailedError(error: unknown, ctx: ErrorContext): string { const message = error instanceof Error ? error.message : String(error) const stack = error instanceof Error ? error.stack : undefined const lines: string[] = [ `${ctx.operation} failed`, "", `**Error**: ${message}`, ] if (ctx.sessionID) { lines.push(`**Session ID**: ${ctx.sessionID}`) } if (ctx.agent) { lines.push(`**Agent**: ${ctx.agent}${ctx.category ? ` (category: ${ctx.category})` : ""}`) } if (ctx.args) { lines.push("", "**Arguments**:") lines.push(`- description: "${ctx.args.description}"`) lines.push(`- category: ${ctx.args.category ?? "(none)"}`) lines.push(`- subagent_type: ${ctx.args.subagent_type ?? "(none)"}`) lines.push(`- run_in_background: ${ctx.args.run_in_background}`) lines.push(`- load_skills: [${ctx.args.load_skills?.join(", ") ?? ""}]`) if (ctx.args.session_id) { lines.push(`- session_id: ${ctx.args.session_id}`) } } if (stack) { lines.push("", "**Stack Trace**:") lines.push("```") lines.push(stack.split("\n").slice(0, 10).join("\n")) lines.push("```") } return lines.join("\n") }