import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import { prompt } from "@oh-my-pi/pi-utils"; import * as z from "zod/v4"; import checkpointDescription from "../prompts/tools/checkpoint.md" with { type: "text" }; import rewindDescription from "../prompts/tools/rewind.md" with { type: "text" }; import type { ToolSession } from "."; import type { OutputMeta } from "./output-meta"; import { ToolError } from "./tool-errors"; import { toolResult } from "./tool-result"; export interface CheckpointState { /** Number of in-memory messages at checkpoint (AFTER checkpoint tool result is appended) */ checkpointMessageCount: number; /** Session entry ID at checkpoint (for session tree branching) */ checkpointEntryId: string | null; /** Timestamp */ startedAt: string; } const checkpointSchema = z.object({ goal: z.string().describe("investigation goal"), }); type CheckpointParams = z.infer; const rewindSchema = z.object({ report: z.string().describe("investigation findings"), }); type RewindParams = z.infer; export interface CheckpointToolDetails { goal: string; startedAt: string; meta?: OutputMeta; } export interface RewindToolDetails { report: string; rewound: boolean; meta?: OutputMeta; } function isTopLevelSession(session: ToolSession): boolean { const depth = session.taskDepth; return depth === undefined || depth === 0; } export class CheckpointTool implements AgentTool { readonly name = "checkpoint"; readonly label = "Checkpoint"; readonly summary = "Create a git-based checkpoint to save and restore session state"; readonly description: string; readonly parameters = checkpointSchema; readonly strict = true; readonly loadMode = "discoverable"; readonly intent = (args: Partial) => (args.goal ? `checkpointing: ${args.goal}` : "checkpointing"); constructor(private readonly session: ToolSession) { this.description = prompt.render(checkpointDescription); } static createIf(session: ToolSession): CheckpointTool | null { if (!isTopLevelSession(session)) return null; return new CheckpointTool(session); } async execute( _toolCallId: string, params: CheckpointParams, _signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback, _context?: AgentToolContext, ): Promise> { if (!isTopLevelSession(this.session)) { throw new ToolError("Checkpoint not available in subagents."); } if (this.session.getCheckpointState?.()) { throw new ToolError("Checkpoint already active."); } const startedAt = new Date().toISOString(); return toolResult({ goal: params.goal, startedAt }) .text( [ "Checkpoint created.", `Goal: ${params.goal}`, "Run your investigation, then call rewind with a concise report.", ].join("\n"), ) .done(); } } export class RewindTool implements AgentTool { readonly name = "rewind"; readonly label = "Rewind"; readonly summary = "Rewind to a previously created checkpoint"; readonly description: string; readonly parameters = rewindSchema; readonly strict = true; readonly loadMode = "discoverable"; readonly intent = (): string => "rewinding"; constructor(private readonly session: ToolSession) { this.description = prompt.render(rewindDescription); } static createIf(session: ToolSession): RewindTool | null { if (!isTopLevelSession(session)) return null; return new RewindTool(session); } async execute( _toolCallId: string, params: RewindParams, _signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback, _context?: AgentToolContext, ): Promise> { if (!isTopLevelSession(this.session)) { throw new ToolError("Checkpoint not available in subagents."); } if (!this.session.getCheckpointState?.()) { throw new ToolError("No active checkpoint."); } const report = params.report.trim(); if (report.length === 0) { throw new ToolError("Report cannot be empty."); } return toolResult({ report, rewound: true }) .text(["Rewind requested.", "Report captured for context replacement."].join("\n")) .done(); } }