* Event payload and result shapes shared between the extensions and hooks
* subsystems.
*
* Both subsystems observe the same agent/session lifecycle, so the *event*
* payloads (what happened) and the simpler *result* shapes (handler return
* values that don't depend on subsystem-specific identifiers like
* `AgentMessage` vs `Message`) are intentionally identical.
*
* Anything that diverges between the two subsystems — UI context, runtime
* context, command context, tool-call discrimination, or return shapes that
* carry subsystem-specific message types — lives in the per-subsystem
* `types.ts` files and is documented there.
*/
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { CompactionPreparation, CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
import type { ImageContent, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
import type { Rule } from "../capability/rule";
import type { Goal, GoalModeState } from "../goals/state";
import type { BranchSummaryEntry, CompactionEntry, SessionEntry } from "../session/session-manager";
import type { TodoItem } from "../tools/todo-write";
export interface SessionStartEvent {
type: "session_start";
}
export interface SessionBeforeSwitchEvent {
type: "session_before_switch";
reason: "new" | "resume" | "fork";
targetSessionFile?: string;
}
export interface SessionSwitchEvent {
type: "session_switch";
reason: "new" | "resume" | "fork";
previousSessionFile: string | undefined;
}
export interface SessionBeforeBranchEvent {
type: "session_before_branch";
entryId: string;
}
export interface SessionBranchEvent {
type: "session_branch";
previousSessionFile: string | undefined;
}
export interface SessionBeforeCompactEvent {
type: "session_before_compact";
preparation: CompactionPreparation;
branchEntries: SessionEntry[];
customInstructions?: string;
signal: AbortSignal;
}
export interface SessionCompactingEvent {
type: "session.compacting";
sessionId: string;
messages: AgentMessage[];
}
export interface SessionCompactEvent {
type: "session_compact";
compactionEntry: CompactionEntry;
fromExtension: boolean;
}
export interface SessionShutdownEvent {
type: "session_shutdown";
}
export interface TreePreparation {
targetId: string;
oldLeafId: string | null;
commonAncestorId: string | null;
entriesToSummarize: SessionEntry[];
userWantsSummary: boolean;
}
export interface SessionBeforeTreeEvent {
type: "session_before_tree";
preparation: TreePreparation;
signal: AbortSignal;
}
export interface SessionTreeEvent {
type: "session_tree";
newLeafId: string | null;
oldLeafId: string | null;
summaryEntry?: BranchSummaryEntry;
fromExtension?: boolean;
}
export interface GoalUpdatedEvent {
type: "goal_updated";
goal: Goal | null;
state?: GoalModeState;
}
export type SessionEvent =
| SessionStartEvent
| SessionBeforeSwitchEvent
| SessionSwitchEvent
| SessionBeforeBranchEvent
| SessionBranchEvent
| SessionBeforeCompactEvent
| SessionCompactingEvent
| SessionCompactEvent
| SessionShutdownEvent
| SessionBeforeTreeEvent
| SessionTreeEvent
| GoalUpdatedEvent;
* Fired before each LLM call.
*
* Original session messages are NOT modified - only the messages sent to the
* LLM are affected when a handler returns a replacement (the return shape
* differs between extensions and hooks; see each subsystem's
* `ContextEventResult`).
*/
export interface ContextEvent {
type: "context";
messages: AgentMessage[];
}
* Fired when an agent loop starts (once per user prompt).
*/
export interface AgentStartEvent {
type: "agent_start";
}
export interface AgentEndEvent {
type: "agent_end";
messages: AgentMessage[];
}
export interface TurnStartEvent {
type: "turn_start";
turnIndex: number;
timestamp: number;
}
export interface TurnEndEvent {
type: "turn_end";
turnIndex: number;
message: AgentMessage;
toolResults: ToolResultMessage[];
}
export interface AutoCompactionStartEvent {
type: "auto_compaction_start";
reason: "threshold" | "overflow" | "idle" | "incomplete";
action: "context-full" | "handoff" | "shake";
}
export interface AutoCompactionEndEvent {
type: "auto_compaction_end";
action: "context-full" | "handoff" | "shake";
result: CompactionResult | undefined;
aborted: boolean;
willRetry: boolean;
errorMessage?: string;
skipped?: boolean;
}
export interface AutoRetryStartEvent {
type: "auto_retry_start";
attempt: number;
maxAttempts: number;
delayMs: number;
errorMessage: string;
}
export interface AutoRetryEndEvent {
type: "auto_retry_end";
success: boolean;
attempt: number;
finalError?: string;
}
export interface TtsrTriggeredEvent {
type: "ttsr_triggered";
rules: Rule[];
}
export interface TodoReminderEvent {
type: "todo_reminder";
todos: TodoItem[];
attempt: number;
maxAttempts: number;
}
* Return type for `tool_call` handlers.
* Allows handlers to block tool execution.
*/
export interface ToolCallEventResult {
block?: boolean;
reason?: string;
}
* Return type for `tool_result` handlers.
* Allows handlers to modify tool results.
*/
export interface ToolResultEventResult {
content?: (TextContent | ImageContent)[];
details?: unknown;
isError?: boolean;
}
export interface SessionBeforeSwitchResult {
cancel?: boolean;
}
export interface SessionBeforeBranchResult {
* If true, abort the branch entirely. No new session file is created,
* conversation stays unchanged.
*/
cancel?: boolean;
* If true, the branch proceeds (new session file created, session state updated)
* but the in-memory conversation is NOT rewound to the branch point.
*
* Use case: git-checkpoint handler that restores code state separately.
* The handler handles state restoration itself, so it doesn't want the
* agent's conversation to be rewound (which would lose recent context).
*
* - `cancel: true` → nothing happens, user stays in current session
* - `skipConversationRestore: true` → branch happens, but messages stay as-is
* - neither → branch happens AND messages rewind to branch point (default)
*/
skipConversationRestore?: boolean;
}
export interface SessionBeforeCompactResult {
cancel?: boolean;
compaction?: CompactionResult;
}
export interface SessionCompactingResult {
context?: string[];
prompt?: string;
preserveData?: Record<string, unknown>;
}
export interface SessionBeforeTreeResult {
cancel?: boolean;
* Custom summary (skips default summarizer).
* Only used if preparation.userWantsSummary is true.
*/
summary?: {
summary: string;
details?: unknown;
};
}