Consolidate session events: remove session_before_new/session_new, add reason field to switch events

- Remove session_before_new and session_new hook events
- Add reason: 'new' | 'resume' to session_before_switch and session_switch events
- Remove 'new' reason from custom tool onSession (use 'switch' for both /new and /resume)
- Rename reset() to newSession(options?) in AgentSession
- Add NewSessionOptions with optional parentSession for lineage tracking
- Rename branchedFrom to parentSession in SessionHeader
- Rename RPC reset command to new_session with optional parentSession
- Update example hooks to use new event structure
- Update documentation and changelog

Based on discussion in #293
This commit is contained in:
Mario Zechner 2026-01-01 23:31:26 +01:00
parent 1d9fa13d58
commit 484d7e06bb
19 changed files with 117 additions and 117 deletions

View file

@ -241,19 +241,20 @@ Tools can implement `onSession` to react to session changes:
```typescript
interface CustomToolSessionEvent {
reason: "start" | "switch" | "branch" | "new" | "tree" | "shutdown";
reason: "start" | "switch" | "branch" | "tree" | "shutdown";
previousSessionFile: string | undefined;
}
```
**Reasons:**
- `start`: Initial session load on startup
- `switch`: User switched to a different session (`/resume`)
- `switch`: User started a new session (`/new`) or switched to a different session (`/resume`)
- `branch`: User branched from a previous message (`/branch`)
- `new`: User started a new session (`/new`)
- `tree`: User navigated to a different point in the session tree (`/tree`)
- `shutdown`: Process is exiting (Ctrl+C, Ctrl+D, or SIGTERM) - use to cleanup resources
To check if a session is fresh (no messages), use `ctx.sessionManager.getEntries().length === 0`.
### State Management Pattern
Tools that maintain state should store it in `details` of their results, not external files. This allows branching to work correctly, as the state is reconstructed from the session history.

View file

@ -123,13 +123,9 @@ user sends prompt ────────────────────
user sends another prompt ◄────────────────────────────────┘
/new (new session)
├─► session_before_new (can cancel)
└─► session_new
/resume (switch session)
├─► session_before_switch (can cancel)
└─► session_switch
/new (new session) or /resume (switch session)
├─► session_before_switch (can cancel, has reason: "new" | "resume")
└─► session_switch (has reason: "new" | "resume")
/branch
├─► session_before_branch (can cancel)
@ -161,34 +157,27 @@ pi.on("session_start", async (_event, ctx) => {
#### session_before_switch / session_switch
Fired when switching sessions via `/resume`.
Fired when starting a new session (`/new`) or switching sessions (`/resume`).
```typescript
pi.on("session_before_switch", async (event, ctx) => {
// event.targetSessionFile - session we're switching to
return { cancel: true }; // Cancel the switch
// event.reason - "new" (starting fresh) or "resume" (switching to existing)
// event.targetSessionFile - session we're switching to (only for "resume")
if (event.reason === "new") {
const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
if (!ok) return { cancel: true };
}
return { cancel: true }; // Cancel the switch/new
});
pi.on("session_switch", async (event, ctx) => {
// event.reason - "new" or "resume"
// event.previousSessionFile - session we came from
});
```
#### session_before_new / session_new
Fired when starting a new session via `/new`.
```typescript
pi.on("session_before_new", async (_event, ctx) => {
const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
if (!ok) return { cancel: true };
});
pi.on("session_new", async (_event, ctx) => {
// New session started
});
```
#### session_before_branch / session_branch
Fired when branching via `/branch`.

View file

@ -76,22 +76,27 @@ Response:
{"type": "response", "command": "abort", "success": true}
```
#### reset
#### new_session
Clear context and start a fresh session. Can be cancelled by a `before_clear` hook.
Start a fresh session. Can be cancelled by a `session_before_switch` hook.
```json
{"type": "reset"}
{"type": "new_session"}
```
With optional parent session tracking:
```json
{"type": "new_session", "parentSession": "/path/to/parent-session.jsonl"}
```
Response:
```json
{"type": "response", "command": "reset", "success": true, "data": {"cancelled": false}}
{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": false}}
```
If a hook cancelled the reset:
If a hook cancelled:
```json
{"type": "response", "command": "reset", "success": true, "data": {"cancelled": true}}
{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": true}}
```
### State

View file

@ -100,7 +100,7 @@ interface AgentSession {
isStreaming: boolean;
// Session management
reset(): Promise<boolean>; // Returns false if cancelled by hook
newSession(options?: { parentSession?: string }): Promise<boolean>; // Returns false if cancelled by hook
switchSession(sessionPath: string): Promise<boolean>;
// Branching

View file

@ -48,10 +48,10 @@ First line of the file. Metadata only, not part of the tree (no `id`/`parentId`)
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}
```
For branched sessions (created via `/branch` command):
For sessions with a parent (created via `/branch` or `newSession({ parentSession })`):
```json
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","branchedFrom":"/path/to/original/session.jsonl"}
{"type":"session","version":2,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}
```
### SessionMessageEntry