Files
oh-my-pi/packages/coding-agent/src/session/checkpoint-entries.ts
T
can1357 7eeaba0471 refactor(coding-agent/session): restructured monolithic agent session
- Extracted internal handlers and logic from AgentSession into dedicated runner, guard, and coordinator modules.
- Created standalone modules for bash execution, evaluation runners, IRC bridging, and prewalk coordination.
- Established dedicated session components for tracking stats, todos, streams, and retry fallback chains.
- Preserved existing session behavior while significantly reducing monolithic class size and complexity.
2026-07-24 01:24:44 +02:00

82 lines
3.6 KiB
TypeScript

import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
import { stringProperty } from "@oh-my-pi/pi-utils";
import type { CompletedRewindState } from "../tools/checkpoint";
import { writeDeviceDispatch } from "../tools/resolve";
import type { SessionEntry } from "./session-entries";
/** Extracts text from custom message content. */
export function customMessageContentText(content: string | (TextContent | ImageContent)[]): string {
if (typeof content === "string") return content;
const parts: string[] = [];
for (const part of content) {
if (part.type === "text") parts.push(part.text);
}
return parts.join("\n");
}
/** Extracts the report body from persisted rewind-report content. */
export function reportFromRewindReportContent(content: string): string {
const marker = "\nReport:\n";
const index = content.lastIndexOf(marker);
const report = index >= 0 ? content.slice(index + marker.length) : content;
return report.trim();
}
/** Checkpoint-domain tool names normalized from native and xdev calls. */
export type SemanticCheckpointToolName = "checkpoint" | "rewind";
/** Normalized checkpoint-domain tool result. */
export interface SemanticToolResult {
toolName: SemanticCheckpointToolName;
details?: unknown;
}
/** Normalizes checkpoint and rewind results across native calls and xdev dispatches. */
export function semanticToolResult(toolName: string | undefined, result: unknown): SemanticToolResult | undefined {
if (toolName === "checkpoint" || toolName === "rewind") {
const details = result && typeof result === "object" && "details" in result ? result.details : undefined;
return { toolName, details };
}
const dispatch = writeDeviceDispatch(toolName ?? "", result);
if (dispatch?.mode !== "execute" || (dispatch.tool !== "checkpoint" && dispatch.tool !== "rewind")) {
return undefined;
}
return { toolName: dispatch.tool, details: dispatch.inner };
}
/** Restores completed rewind state from a persisted session entry. */
export function completedRewindFromEntry(entry: SessionEntry): CompletedRewindState | undefined {
if (entry.type !== "custom_message" || entry.customType !== "rewind-report") return undefined;
const details = entry.details;
if (!details || typeof details !== "object") return undefined;
const startedAt = stringProperty(details, "startedAt");
const rewoundAt = stringProperty(details, "rewoundAt");
if (!startedAt || !rewoundAt) return undefined;
const report =
stringProperty(details, "report")?.trim() ||
reportFromRewindReportContent(customMessageContentText(entry.content));
return report.length > 0 ? { report, startedAt, rewoundAt } : undefined;
}
/** Whether an entry is a successful checkpoint tool result. */
export function isSuccessfulCheckpointEntry(
entry: SessionEntry,
): entry is SessionEntry & { type: "message"; message: Extract<AgentMessage, { role: "toolResult" }> } {
if (entry.type !== "message" || entry.message.role !== "toolResult" || entry.message.isError === true) {
return false;
}
return semanticToolResult(entry.message.toolName, entry.message)?.toolName === "checkpoint";
}
/** Returns the checkpoint start timestamp represented by an entry. */
export function checkpointStartedAtFromEntry(entry: SessionEntry): string | undefined {
if (!isSuccessfulCheckpointEntry(entry)) return undefined;
const details = semanticToolResult(entry.message.toolName, entry.message)?.details;
if (details && typeof details === "object") {
const startedAt = stringProperty(details, "startedAt");
if (startedAt) return startedAt;
}
return entry.timestamp;
}