/** * RPC protocol types for headless operation. * * Commands are sent as JSON lines on stdin. * Responses and events are emitted as JSON lines on stdout. */ import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core"; import type { ImageContent, Model } from "@earendil-works/pi-ai"; import type { SessionStats } from "../../core/agent-session.ts"; import type { KeybindingsConfig } from "../../core/keybindings.ts"; import type { Settings, SettingsScope } from "../../core/settings-manager.ts"; import type { BashResult } from "../../core/bash-executor.ts"; import type { CompactionResult } from "../../core/compaction/index.ts"; import type { BranchSummaryEntry, SessionEntry, SessionTreeNode } from "../../core/session-manager.ts"; import type { SourceInfo } from "../../core/source-info.ts"; import type { ChangelogEntry } from "../../utils/changelog.ts"; export type RpcCommand = { id?: string; type: "prompt"; message: string; images?: ImageContent[]; streamingBehavior?: "steer" | "followUp"; /** Buffer the prompt during compaction and auto-send it after compaction_end (mirrors TUI compaction queue). */ queueWhileCompacting?: boolean; } | { id?: string; type: "steer"; message: string; images?: ImageContent[]; } | { id?: string; type: "follow_up"; message: string; images?: ImageContent[]; } | { id?: string; type: "abort"; } | { id?: string; type: "clear_queue"; } | { id?: string; type: "new_session"; parentSession?: string; } | { id?: string; type: "handoff_new"; goal?: string; } | { id?: string; type: "get_state"; } | { id?: string; type: "set_model"; provider: string; modelId: string; } | { id?: string; type: "cycle_model"; direction?: "forward" | "backward"; } | { id?: string; type: "get_available_models"; refresh?: boolean; } | { id?: string; type: "set_thinking_level"; level: ThinkingLevel; } | { id?: string; type: "cycle_thinking_level"; } | { id?: string; type: "get_available_thinking_levels"; } | { id?: string; type: "set_steering_mode"; mode: "all" | "one-at-a-time"; } | { id?: string; type: "set_follow_up_mode"; mode: "all" | "one-at-a-time"; } | { id?: string; type: "replace_queue"; steering: string[]; followUp: string[]; } | { id?: string; type: "compact"; customInstructions?: string; } | { id?: string; type: "set_auto_compaction"; enabled: boolean; } | { id?: string; type: "set_auto_handoff"; enabled: boolean; } | { id?: string; type: "set_auto_handoff_threshold"; tokens: number; } | { id?: string; type: "set_auto_retry"; enabled: boolean; } | { id?: string; type: "abort_retry"; } | { id?: string; type: "bash"; command: string; excludeFromContext?: boolean; } | { id?: string; type: "abort_bash"; } | { id?: string; type: "get_session_stats"; } | { id?: string; type: "export_html"; outputPath?: string; themeName?: string; } | { id?: string; type: "switch_session"; sessionPath: string; } | { id?: string; type: "fork"; entryId: string; } | { id?: string; type: "clone"; } | { id?: string; type: "get_fork_messages"; } | { id?: string; type: "get_entries"; since?: string; } | { id?: string; type: "get_tree"; filter?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; } | { id?: string; type: "get_last_assistant_text"; } | { id?: string; type: "set_session_name"; name: string; } | { id?: string; type: "get_settings"; scope?: "global" | "project" | "effective"; } | { id?: string; type: "set_settings"; scope?: SettingsScope; values: Partial; } | { id?: string; type: "factory_reset_settings"; } | { id?: string; type: "get_auth_providers"; } | { id?: string; type: "login"; providerId: string; authType: "oauth" | "api_key"; } | { id?: string; type: "logout"; providerId: string; } | { id?: string; type: "get_auth_state"; } | { id?: string; type: "get_scoped_models"; } | { id?: string; type: "set_scoped_models"; enabled?: string[]; reorder?: string[]; } | { id?: string; type: "import_jsonl"; path: string; } | { id?: string; type: "navigate_tree"; targetId: string; summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string; } | { id?: string; type: "list_sessions"; scope?: "current" | "all"; } | { id?: string; type: "rename_session"; path: string; name: string; } | { id?: string; type: "delete_session"; path: string; } | { id?: string; type: "set_session_models"; enabled?: string[]; reorder?: string[]; } | { id?: string; type: "git"; command: string; } | { id?: string; type: "reload"; } | { id?: string; type: "set_entry_label"; entryId: string; label?: string; } | { id?: string; type: "get_trust"; } | { id?: string; type: "set_trust"; decision: boolean | null; } | { id?: string; type: "get_hotkeys"; } | { id?: string; type: "get_available_themes"; } | { id?: string; type: "get_version_info"; } | { id?: string; type: "share_gist"; public?: boolean; themeName?: string; } | { id?: string; type: "get_messages"; } | { id?: string; type: "get_commands"; } | { id?: string; type: "get_skills"; }; /** A command available for invocation via prompt */ export interface RpcSlashCommand { /** Command name (without leading slash) */ name: string; /** Human-readable description */ description?: string; /** What kind of command this is */ source: "extension" | "prompt" | "skill" | "builtin"; /** Source metadata for the owning resource */ sourceInfo: SourceInfo; /** True for interactive-TUI-only builtins that cannot be invoked via prompt */ interactiveOnly?: boolean; } /** A resolved skill in the get_skills catalog (mirrors the TUI skill-toggle resolution). */ export interface RpcSkillInfo { /** Skill name (frontmatter name, falling back to the SKILL.md parent directory name). */ name: string; /** Skill description from frontmatter. */ description?: string; /** "global" for user-level skills, "project" for project-level skills. */ scope: "global" | "project"; /** Toggle pattern relative to the skill base dir (same shape the TUI writes to settings.skills). */ pattern: string; /** Resolved enabled state per isEnabledByOverrides semantics. */ enabled: boolean; } /** Session listing entry (SessionInfo with transcript text stripped). */ export interface RpcSessionInfo { path: string; id: string; cwd: string; name?: string; parentSessionPath?: string; created: string; modified: string; messageCount: number; firstMessage: string; } export interface RpcSessionState { model?: Model; thinkingLevel: ThinkingLevel; isStreaming: boolean; isCompacting: boolean; steeringMode: "all" | "one-at-a-time"; followUpMode: "all" | "one-at-a-time"; sessionFile?: string; sessionId: string; sessionName?: string; autoCompactionEnabled: boolean; autoHandoffEnabled: boolean; autoHandoffThresholdTokens: number; messageCount: number; pendingMessageCount: number; steering: readonly string[]; followUp: readonly string[]; workflow?: WorkflowState; } export type PrototypePhase = "grilling" | "research" | "plan" | "reuse" | "handoff" | "loop" | "audit" | "complete"; export type QuicktypePhase = "grilling" | "plan" | "reuse" | "handoff" | "loop" | "audit" | "complete"; export interface WorkflowState { mode: "prototype" | "quicktype"; phase: PrototypePhase | QuicktypePhase; step: number; done: boolean; } export type RpcResponse = { id?: string; type: "response"; command: "prompt"; success: true; } | { id?: string; type: "response"; command: "steer"; success: true; } | { id?: string; type: "response"; command: "follow_up"; success: true; } | { id?: string; type: "response"; command: "abort"; success: true; } | { id?: string; type: "response"; command: "clear_queue"; success: true; data: { steering: string[]; followUp: string[]; }; } | { id?: string; type: "response"; command: "new_session"; success: true; data: { cancelled: boolean; }; } | { id?: string; type: "response"; command: "handoff_new"; success: true; data: { cancelled: boolean; }; } | { id?: string; type: "response"; command: "get_state"; success: true; data: RpcSessionState; } | { id?: string; type: "response"; command: "set_model"; success: true; data: Model; } | { id?: string; type: "response"; command: "cycle_model"; success: true; data: { model: Model; thinkingLevel: ThinkingLevel; isScoped: boolean; } | null; } | { id?: string; type: "response"; command: "get_available_models"; success: true; data: { models: Model[]; }; } | { id?: string; type: "response"; command: "set_thinking_level"; success: true; } | { id?: string; type: "response"; command: "cycle_thinking_level"; success: true; data: { level: ThinkingLevel; } | null; } | { id?: string; type: "response"; command: "get_available_thinking_levels"; success: true; data: { levels: ThinkingLevel[]; }; } | { id?: string; type: "response"; command: "set_steering_mode"; success: true; } | { id?: string; type: "response"; command: "set_follow_up_mode"; success: true; } | { id?: string; type: "response"; command: "compact"; success: true; data: CompactionResult; } | { id?: string; type: "response"; command: "set_auto_compaction"; success: true; } | { id?: string; type: "response"; command: "set_auto_handoff"; success: true; } | { id?: string; type: "response"; command: "set_auto_handoff_threshold"; success: true; } | { id?: string; type: "response"; command: "set_auto_retry"; success: true; } | { id?: string; type: "response"; command: "abort_retry"; success: true; } | { id?: string; type: "response"; command: "bash"; success: true; data: BashResult; } | { id?: string; type: "response"; command: "abort_bash"; success: true; } | { id?: string; type: "response"; command: "get_session_stats"; success: true; data: SessionStats; } | { id?: string; type: "response"; command: "export_html"; success: true; data: { path: string; }; } | { id?: string; type: "response"; command: "switch_session"; success: true; data: { cancelled: boolean; }; } | { id?: string; type: "response"; command: "fork"; success: true; data: { text: string; cancelled: boolean; }; } | { id?: string; type: "response"; command: "clone"; success: true; data: { cancelled: boolean; }; } | { id?: string; type: "response"; command: "get_fork_messages"; success: true; data: { messages: Array<{ entryId: string; text: string; }>; }; } | { id?: string; type: "response"; command: "get_entries"; success: true; data: { entries: SessionEntry[]; leafId: string | null; }; } | { id?: string; type: "response"; command: "get_tree"; success: true; data: { tree: SessionTreeNode[]; leafId: string | null; }; } | { id?: string; type: "response"; command: "get_last_assistant_text"; success: true; data: { text: string | null; }; } | { id?: string; type: "response"; command: "set_session_name"; success: true; } | { id?: string; type: "response"; command: "get_messages"; success: true; data: { messages: AgentMessage[]; }; } | { id?: string; type: "response"; command: "get_commands"; success: true; data: { commands: RpcSlashCommand[]; }; } | { id?: string; type: "response"; command: "get_skills"; success: true; data: { skills: RpcSkillInfo[]; patterns: string[]; }; } | { id?: string; type: "response"; command: "get_settings"; success: true; data: { scope: "global" | "project" | "effective"; settings: Settings; }; } | { id?: string; type: "response"; command: "set_settings"; success: true; data: { scope: SettingsScope; values: Partial; }; } | { id?: string; type: "response"; command: "factory_reset_settings"; success: true; } | { id?: string; type: "response"; command: "get_auth_providers"; success: true; data: { providers: Array<{ providerId: string; name: string; authTypes: Array<"oauth" | "api_key">; }>; }; } | { id?: string; type: "response"; command: "login"; success: true; data: { providerId: string; authType: "oauth" | "api_key"; message: string; }; } | { id?: string; type: "response"; command: "logout"; success: true; data: { providerId: string; }; } | { id?: string; type: "response"; command: "get_auth_state"; success: true; data: { providers: Array<{ providerId: string; authType: "oauth" | "api_key"; source: string; }>; }; } | { id?: string; type: "response"; command: "get_scoped_models"; success: true; data: { enabled: string[]; models: Model[]; }; } | { id?: string; type: "response"; command: "set_scoped_models"; success: true; data: { enabled: string[]; }; } | { id?: string; type: "response"; command: "import_jsonl"; success: true; data: { sessionPath: string; }; } | { id?: string; type: "response"; command: "navigate_tree"; success: true; data: { editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry | null; }; } | { id?: string; type: "response"; command: "list_sessions"; success: true; data: { sessions: RpcSessionInfo[]; }; } | { id?: string; type: "response"; command: "rename_session"; success: true; data: { path: string; name: string; }; } | { id?: string; type: "response"; command: "delete_session"; success: true; data: { path: string; method: "trash" | "unlink"; }; } | { id?: string; type: "response"; command: "set_session_models"; success: true; data: { enabled: string[]; models: Model[]; }; } | { id?: string; type: "response"; command: "git"; success: true; } | { id?: string; type: "response"; command: "reload"; success: true; } | { id?: string; type: "response"; command: "set_entry_label"; success: true; data: { labelId: string; }; } | { id?: string; type: "response"; command: "get_trust"; success: true; data: { cwd: string; savedDecision: { path: string; decision: boolean; } | null; projectTrusted: boolean; trustRequired: boolean; }; } | { id?: string; type: "response"; command: "set_trust"; success: true; data: { decision: boolean | null; restartRequired: boolean; }; } | { id?: string; type: "response"; command: "get_hotkeys"; success: true; data: { hotkeys: KeybindingsConfig; }; } | { id?: string; type: "response"; command: "get_available_themes"; success: true; data: { themes: Array<{ name: string; path?: string; }>; current: string | undefined; }; } | { id?: string; type: "response"; command: "get_version_info"; success: true; data: { version: string; changelog: ChangelogEntry[]; }; } | { id?: string; type: "response"; command: "share_gist"; success: true; data: { url: string; gistUrl: string; }; } | { id?: string; type: "response"; command: string; success: false; error: string; }; /** Emitted when an extension needs user input */ export type RpcExtensionUIRequest = { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[]; timeout?: number; } | { type: "extension_ui_request"; id: string; method: "multiselect"; title: string; options: string[]; timeout?: number; } | { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string; timeout?: number; } | { type: "extension_ui_request"; id: string; method: "input"; title: string; placeholder?: string; /** "secret": render as single-line password (e.g. API key entry). Defaults to "text". */ inputKind?: "text" | "secret"; timeout?: number; } | { type: "extension_ui_request"; id: string; method: "editor"; title: string; prefill?: string; } | { type: "extension_ui_request"; id: string; method: "notify"; message: string; notifyType?: "info" | "warning" | "error"; } | { type: "extension_ui_request"; id: string; method: "setStatus"; statusKey: string; statusText: string | undefined; } | { type: "extension_ui_request"; id: string; method: "setWidget"; widgetKey: string; widgetLines: string[] | undefined; widgetPlacement?: "aboveEditor" | "belowEditor"; } | { type: "extension_ui_request"; id: string; method: "setTitle"; title: string; } | { type: "extension_ui_request"; id: string; method: "set_editor_text"; text: string; } | { type: "extension_ui_request"; id: string; method: "working"; /** Working/loading message shown during streaming. Omit to restore the default. */ message?: string; /** Show or hide the built-in working loader row during streaming. */ visible?: boolean; /** Custom animation frames for the normal streaming loader. Empty array hides the indicator; omit to restore the default spinner. */ frames?: string[]; /** Frame interval in milliseconds for animated indicators. */ intervalMs?: number; }; /** Response to an extension UI request */ export type RpcExtensionUIResponse = { type: "extension_ui_response"; id: string; value: string; } | { type: "extension_ui_response"; id: string; values: string[]; } | { type: "extension_ui_response"; id: string; confirmed: boolean; } | { type: "extension_ui_response"; id: string; cancelled: true; }; export type RpcCommandType = RpcCommand["type"];