- Extract atlas/ into 15 focused modules (hook, event handler, tool policies, types, etc.) - Split auto-update-checker into checker/ and hook/ subdirectories with single-purpose files - Decompose session-recovery into separate recovery strategy files per error type - Extract todo-continuation-enforcer from monolith to directory with dedicated modules - Split background-task/tools.ts into individual tool creator files - Extract command-executor, tmux-utils into focused sub-modules - Split config/schema.ts into domain-specific schema files - Decompose cli/config-manager.ts into focused modules - Rollback skill-mcp-manager, model-availability, index.ts splits that broke tests - Fix all import path depths for moved files (../../ -> ../../../) - Add explicit type annotations to resolve TS7006 implicit any errors Typecheck: 0 errors Tests: 2359 pass, 5 fail (all pre-existing)
114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import type { BackgroundTask } from "../../features/background-agent"
|
|
import { consumeNewMessages } from "../../shared/session-cursor"
|
|
import type { BackgroundOutputClient, BackgroundOutputMessagesResult } from "./clients"
|
|
import { extractMessages, getErrorMessage } from "./session-messages"
|
|
import { formatDuration } from "./time-format"
|
|
|
|
function getTimeString(value: unknown): string {
|
|
return typeof value === "string" ? value : ""
|
|
}
|
|
|
|
export async function formatTaskResult(task: BackgroundTask, client: BackgroundOutputClient): Promise<string> {
|
|
if (!task.sessionID) {
|
|
return `Error: Task has no sessionID`
|
|
}
|
|
|
|
const messagesResult: BackgroundOutputMessagesResult = await client.session.messages({
|
|
path: { id: task.sessionID },
|
|
})
|
|
|
|
const errorMessage = getErrorMessage(messagesResult)
|
|
if (errorMessage) {
|
|
return `Error fetching messages: ${errorMessage}`
|
|
}
|
|
|
|
const messages = extractMessages(messagesResult)
|
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
return `Task Result
|
|
|
|
Task ID: ${task.id}
|
|
Description: ${task.description}
|
|
Duration: ${formatDuration(task.startedAt ?? new Date(), task.completedAt)}
|
|
Session ID: ${task.sessionID}
|
|
|
|
---
|
|
|
|
(No messages found)`
|
|
}
|
|
|
|
const relevantMessages = messages.filter((m) => m.info?.role === "assistant" || m.info?.role === "tool")
|
|
if (relevantMessages.length === 0) {
|
|
return `Task Result
|
|
|
|
Task ID: ${task.id}
|
|
Description: ${task.description}
|
|
Duration: ${formatDuration(task.startedAt ?? new Date(), task.completedAt)}
|
|
Session ID: ${task.sessionID}
|
|
|
|
---
|
|
|
|
(No assistant or tool response found)`
|
|
}
|
|
|
|
const sortedMessages = [...relevantMessages].sort((a, b) => {
|
|
const timeA = getTimeString(a.info?.time)
|
|
const timeB = getTimeString(b.info?.time)
|
|
return timeA.localeCompare(timeB)
|
|
})
|
|
|
|
const newMessages = consumeNewMessages(task.sessionID, sortedMessages)
|
|
if (newMessages.length === 0) {
|
|
const duration = formatDuration(task.startedAt ?? new Date(), task.completedAt)
|
|
return `Task Result
|
|
|
|
Task ID: ${task.id}
|
|
Description: ${task.description}
|
|
Duration: ${duration}
|
|
Session ID: ${task.sessionID}
|
|
|
|
---
|
|
|
|
(No new output since last check)`
|
|
}
|
|
|
|
const extractedContent: string[] = []
|
|
for (const message of newMessages) {
|
|
for (const part of message.parts ?? []) {
|
|
if ((part.type === "text" || part.type === "reasoning") && part.text) {
|
|
extractedContent.push(part.text)
|
|
continue
|
|
}
|
|
|
|
if (part.type === "tool_result") {
|
|
const toolResult = part as { content?: string | Array<{ type: string; text?: string }> }
|
|
if (typeof toolResult.content === "string" && toolResult.content) {
|
|
extractedContent.push(toolResult.content)
|
|
continue
|
|
}
|
|
|
|
if (Array.isArray(toolResult.content)) {
|
|
for (const block of toolResult.content) {
|
|
if ((block.type === "text" || block.type === "reasoning") && block.text) {
|
|
extractedContent.push(block.text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const textContent = extractedContent.filter((text) => text.length > 0).join("\n\n")
|
|
const duration = formatDuration(task.startedAt ?? new Date(), task.completedAt)
|
|
|
|
return `Task Result
|
|
|
|
Task ID: ${task.id}
|
|
Description: ${task.description}
|
|
Duration: ${duration}
|
|
Session ID: ${task.sessionID}
|
|
|
|
---
|
|
|
|
${textContent || "(No text output)"}`
|
|
}
|