docs: update docs

This commit is contained in:
can1357
2026-04-30 06:44:16 +02:00
parent 5003996a01
commit 9865a4ce6c
54 changed files with 2145 additions and 1959 deletions
+58 -49
View File
@@ -25,14 +25,17 @@ So this file documents the hook subsystem implementation itself (types/loader/ru
A hook module must default-export a factory:
```ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks";
export default function hook(pi: HookAPI): void {
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && String(event.input.command ?? "").includes("rm -rf")) {
return { block: true, reason: "blocked by policy" };
}
});
pi.on("tool_call", async (event, ctx) => {
if (
event.toolName === "bash" &&
String(event.input.command ?? "").includes("rm -rf")
) {
return { block: true, reason: "blocked by policy" };
}
});
}
```
@@ -126,7 +129,6 @@ tool_call handlers
└─ error ──> emit tool_result(isError=true) then rethrow original error
```
## Execution model and mutation semantics
### 1) Pre-execution: `tool_call`
@@ -252,85 +254,92 @@ Hook status text set via `ctx.ui.setStatus(key, text)` is:
### Block unsafe bash commands
```ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks";
export default function (pi: HookAPI): void {
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return;
const cmd = String(event.input.command ?? "");
if (!cmd.includes("rm -rf")) return;
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return;
const cmd = String(event.input.command ?? "");
if (!cmd.includes("rm -rf")) return;
if (!ctx.hasUI) return { block: true, reason: "rm -rf blocked (no UI)" };
const ok = await ctx.ui.confirm("Dangerous command", `Allow: ${cmd}`);
if (!ok) return { block: true, reason: "user denied command" };
});
if (!ctx.hasUI) return { block: true, reason: "rm -rf blocked (no UI)" };
const ok = await ctx.ui.confirm("Dangerous command", `Allow: ${cmd}`);
if (!ok) return { block: true, reason: "user denied command" };
});
}
```
### Redact tool output on post-execution
```ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks";
export default function (pi: HookAPI): void {
pi.on("tool_result", async event => {
if (event.toolName !== "read" || event.isError) return;
pi.on("tool_result", async (event) => {
if (event.toolName !== "read" || event.isError) return;
const redacted = event.content.map(chunk => {
if (chunk.type !== "text") return chunk;
return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\S+/g, "API_KEY=[REDACTED]") };
});
const redacted = event.content.map((chunk) => {
if (chunk.type !== "text") return chunk;
return {
...chunk,
text: chunk.text.replaceAll(/API_KEY=\S+/g, "API_KEY=[REDACTED]"),
};
});
return { content: redacted };
});
return { content: redacted };
});
}
```
### Modify model context per LLM call
```ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks";
export default function (pi: HookAPI): void {
pi.on("context", async event => {
const filtered = event.messages.filter(msg => !(msg.role === "custom" && msg.customType === "debug-only"));
return { messages: filtered };
});
pi.on("context", async (event) => {
const filtered = event.messages.filter(
(msg) => !(msg.role === "custom" && msg.customType === "debug-only"),
);
return { messages: filtered };
});
}
```
### Register slash command with command-safe context methods
```ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/hooks";
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks";
export default function (pi: HookAPI): void {
pi.registerCommand("handoff", {
description: "Create a new session with setup message",
handler: async (_args, ctx) => {
await ctx.waitForIdle();
await ctx.newSession({
parentSession: ctx.sessionManager.getSessionFile(),
setup: async sm => {
sm.appendMessage({
role: "user",
content: [{ type: "text", text: "Continue from prior session summary." }],
timestamp: Date.now(),
});
},
});
},
});
pi.registerCommand("handoff", {
description: "Create a new session with setup message",
handler: async (_args, ctx) => {
await ctx.waitForIdle();
await ctx.newSession({
parentSession: ctx.sessionManager.getSessionFile(),
setup: async (sm) => {
sm.appendMessage({
role: "user",
content: [
{ type: "text", text: "Continue from prior session summary." },
],
timestamp: Date.now(),
});
},
});
},
});
}
```
## Export surface
`src/extensibility/hooks/index.ts` exports:
`src/extensibility/hooks/index.ts` and the package subpath `@oh-my-pi/pi-coding-agent/extensibility/hooks` export:
- loading APIs (`discoverAndLoadHooks`, `loadHooks`)
- runner and wrapper (`HookRunner`, `HookToolWrapper`)
- all hook types
- `execCommand` re-export
And package root (`src/index.ts`) re-exports hook **types** as a legacy compatibility surface.
The package root (`@oh-my-pi/pi-coding-agent`) does not re-export `HookAPI`; import legacy hook types from the hooks subpath.