Change branch() to use entryId instead of entryIndex

- AgentSession.branch(entryId: string) now takes entry ID
- SessionBeforeBranchEvent.entryId replaces entryIndex
- getUserMessagesForBranching() returns entryId
- Update RPC types and client
- Update UserMessageSelectorComponent
- Update hook examples and tests
- Update docs (hooks.md, sdk.md)
This commit is contained in:
Mario Zechner 2025-12-31 13:47:34 +01:00
parent 027d39aa33
commit 8e1e99ca05
12 changed files with 64 additions and 50 deletions

View file

@ -195,7 +195,7 @@ Fired when branching via `/branch`.
```typescript
pi.on("session_before_branch", async (event, ctx) => {
// event.entryIndex - entry index being branched from
// event.entryId - ID of the entry being branched from
return { cancel: true }; // Cancel branch
// OR
@ -634,15 +634,23 @@ export default function (pi: HookAPI) {
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
export default function (pi: HookAPI) {
const checkpoints = new Map<number, string>();
const checkpoints = new Map<string, string>();
let currentEntryId: string | undefined;
pi.on("turn_start", async (event) => {
pi.on("tool_result", async (_event, ctx) => {
const leaf = ctx.sessionManager.getLeafEntry();
if (leaf) currentEntryId = leaf.id;
});
pi.on("turn_start", async () => {
const { stdout } = await pi.exec("git", ["stash", "create"]);
if (stdout.trim()) checkpoints.set(event.turnIndex, stdout.trim());
if (stdout.trim() && currentEntryId) {
checkpoints.set(currentEntryId, stdout.trim());
}
});
pi.on("session_before_branch", async (event, ctx) => {
const ref = checkpoints.get(event.entryIndex);
const ref = checkpoints.get(event.entryId);
if (!ref || !ctx.hasUI) return;
const ok = await ctx.ui.confirm("Restore?", "Restore code to checkpoint?");

View file

@ -99,11 +99,12 @@ interface AgentSession {
isStreaming: boolean;
// Session management
newSession(): Promise<boolean>; // Returns false if cancelled by hook
reset(): Promise<boolean>; // Returns false if cancelled by hook
switchSession(sessionPath: string): Promise<boolean>;
// Branching (tree-based)
branch(entryId: string): Promise<{ cancelled: boolean }>;
// Branching
branch(entryId: string): Promise<{ selectedText: string; cancelled: boolean }>; // Creates new session file
navigateTree(targetId: string, options?: { summarize?: boolean }): Promise<{ editorText?: string; cancelled: boolean }>; // In-place navigation
// Hook message injection
sendHookMessage(message: HookMessage, triggerTurn?: boolean): void;
@ -400,10 +401,10 @@ const { session } = await createAgentSession({
```typescript
import { Type } from "@sinclair/typebox";
import { createAgentSession, discoverCustomTools, type CustomAgentTool } from "@mariozechner/pi-coding-agent";
import { createAgentSession, discoverCustomTools, type CustomTool } from "@mariozechner/pi-coding-agent";
// Inline custom tool
const myTool: CustomAgentTool = {
const myTool: CustomTool = {
name: "my_tool",
label: "My Tool",
description: "Does something useful",
@ -793,7 +794,7 @@ import {
readTool,
bashTool,
type HookFactory,
type CustomAgentTool,
type CustomTool,
} from "@mariozechner/pi-coding-agent";
// Set up auth storage (custom location)
@ -816,7 +817,7 @@ const auditHook: HookFactory = (api) => {
};
// Inline tool
const statusTool: CustomAgentTool = {
const statusTool: CustomTool = {
name: "status",
label: "Status",
description: "Get system status",
@ -932,7 +933,7 @@ createGrepTool, createFindTool, createLsTool
// Types
type CreateAgentSessionOptions
type CreateAgentSessionResult
type CustomAgentTool
type CustomTool
type HookFactory
type Skill
type FileSlashCommand