Add hooks system with pi.send() for external message injection

- Hook discovery from ~/.pi/agent/hooks/, .pi/hooks/, --hook flag
- Events: session_start, session_switch, agent_start/end, turn_start/end, tool_call, tool_result, branch
- tool_call can block execution, tool_result can modify results
- pi.send(text, attachments?) to inject messages from external sources
- UI primitives: ctx.ui.select/confirm/input/notify
- Context: ctx.exec(), ctx.cwd, ctx.sessionFile, ctx.hasUI
- Docs shipped with npm package and binary builds
- System prompt references docs folder
This commit is contained in:
Mario Zechner 2025-12-10 00:50:30 +01:00
parent 942d8d3c95
commit 7c553acd1e
21 changed files with 1307 additions and 83 deletions

View file

@ -888,6 +888,8 @@ export class AgentSession {
* Listeners are preserved and will continue receiving events.
*/
async switchSession(sessionPath: string): Promise<void> {
const previousSessionFile = this.sessionFile;
this._disconnectFromAgent();
await this.abort();
this._queuedMessages = [];
@ -895,6 +897,17 @@ export class AgentSession {
// Set new session
this.sessionManager.setSessionFile(sessionPath);
// Emit session_switch event
if (this._hookRunner) {
this._hookRunner.setSessionFile(sessionPath);
await this._hookRunner.emit({
type: "session_switch",
newSessionFile: sessionPath,
previousSessionFile,
reason: "switch",
});
}
// Reload messages
const loaded = loadSessionFromEntries(this.sessionManager.loadEntries());
this.agent.replaceMessages(loaded.messages);
@ -928,6 +941,7 @@ export class AgentSession {
* - skipped: True if a hook requested to skip conversation restore
*/
async branch(entryIndex: number): Promise<{ selectedText: string; skipped: boolean }> {
const previousSessionFile = this.sessionFile;
const entries = this.sessionManager.loadEntries();
const selectedEntry = entries[entryIndex];
@ -956,6 +970,17 @@ export class AgentSession {
const newSessionFile = this.sessionManager.createBranchedSessionFromEntries(entries, entryIndex);
this.sessionManager.setSessionFile(newSessionFile);
// Emit session_switch event
if (this._hookRunner) {
this._hookRunner.setSessionFile(newSessionFile);
await this._hookRunner.emit({
type: "session_switch",
newSessionFile,
previousSessionFile,
reason: "branch",
});
}
// Reload
const loaded = loadSessionFromEntries(this.sessionManager.loadEntries());
this.agent.replaceMessages(loaded.messages);