@aliou/pi-dev-kit 0.6.5 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +11 -10
- package/src/commands/index.ts +5 -1
- package/src/commands/update.ts +125 -83
- package/src/skills/pi-extension/SKILL.md +108 -137
- package/src/skills/pi-extension/references/additional-apis.md +252 -208
- package/src/skills/pi-extension/references/commands.md +113 -33
- package/src/skills/pi-extension/references/components.md +267 -102
- package/src/skills/pi-extension/references/hooks.md +229 -156
- package/src/skills/pi-extension/references/messages.md +94 -106
- package/src/skills/pi-extension/references/modes.md +80 -90
- package/src/skills/pi-extension/references/providers.md +255 -96
- package/src/skills/pi-extension/references/publish.md +76 -62
- package/src/skills/pi-extension/references/state.md +80 -33
- package/src/skills/pi-extension/references/structure.md +126 -269
- package/src/skills/pi-extension/references/testing.md +1 -1
- package/src/skills/pi-extension/references/tools.md +198 -823
- package/src/tools/changelog-tool.ts +15 -3
- package/src/tools/docs-tool.ts +3 -3
- package/src/tools/index.ts +5 -1
- package/src/tools/package-manager-tool.ts +8 -4
- package/src/tools/utils.ts +33 -23
- package/src/tools/version-tool.ts +8 -4
- package/src/index.ts +0 -8
|
@@ -1,169 +1,264 @@
|
|
|
1
|
-
# Hooks
|
|
1
|
+
# Hooks and Events
|
|
2
2
|
|
|
3
|
-
Hooks let extensions
|
|
3
|
+
Hooks let extensions observe, modify, or block Pi lifecycle events. Register them with `pi.on(eventName, handler)`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
| `session_start` | Session starts, reloads, or is replaced | No | `{ reason: "startup" \| "reload" \| "new" \| "resume" \| "fork", previousSessionFile? }` |
|
|
12
|
-
| `session_before_switch` | Before `/new` or `/resume` replaces the current session | Yes (`{ cancel: true }`) | `{ reason: "new" \| "resume", targetSessionFile? }` |
|
|
13
|
-
| `session_before_fork` | Before forking a session | Yes (`{ cancel: true }`) | `{ entryId }` |
|
|
14
|
-
| `session_shutdown` | Current session runtime is shutting down or being replaced | No | `{ reason: "quit" | "reload" | "new-session" | "resume" | "fork", targetSessionFile? }` |
|
|
15
|
-
| `session_before_compact` | Before compaction | Yes (cancel or provide custom compaction) | event-specific compaction data |
|
|
16
|
-
|
|
17
|
-
### Agent Events
|
|
5
|
+
```typescript
|
|
6
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
7
|
+
// event is event-specific.
|
|
8
|
+
// ctx is ExtensionContext.
|
|
9
|
+
});
|
|
10
|
+
```
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
|---|---|---|
|
|
21
|
-
| `before_agent_start` | Before agent turn starts | `{ systemPrompt, systemPromptOptions }` |
|
|
22
|
-
| `agent_start` | Agent turn started | `{}` |
|
|
23
|
-
| `turn_start` | Turn begins processing | `{}` |
|
|
24
|
-
| `turn_end` | Turn finishes processing | `{}` |
|
|
25
|
-
| `model_select` | User changed the model | `{ model: string }` |
|
|
12
|
+
Handlers run in extension load order. For blocking/cancelling events, the first blocking result wins.
|
|
26
13
|
|
|
27
|
-
|
|
14
|
+
## Event Lifecycle Summary
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|---|---|---|---|
|
|
31
|
-
| `tool_call` | Before a tool executes | Yes (`{ block: true, reason }`) | `{ toolName, toolCallId, input }` |
|
|
16
|
+
Common startup and prompt flow:
|
|
32
17
|
|
|
33
|
-
|
|
18
|
+
1. `session_start`
|
|
19
|
+
2. `resources_discover`
|
|
20
|
+
3. user input arrives
|
|
21
|
+
4. extension command check
|
|
22
|
+
5. `input`
|
|
23
|
+
6. skill/prompt expansion
|
|
24
|
+
7. `before_agent_start`
|
|
25
|
+
8. `agent_start`
|
|
26
|
+
9. repeated turns:
|
|
27
|
+
- `turn_start`
|
|
28
|
+
- `context`
|
|
29
|
+
- `before_provider_request`
|
|
30
|
+
- `after_provider_response`
|
|
31
|
+
- message/tool lifecycle events
|
|
32
|
+
- `turn_end`
|
|
33
|
+
10. `agent_end`
|
|
34
|
+
11. `session_shutdown` on exit, reload, or session replacement
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|---|---|---|---|
|
|
37
|
-
| `input` | User submitted a message | Yes (return transformed text) | `{ text: string }` |
|
|
36
|
+
Read Pi `docs/extensions.md` for the exhaustive event diagram before changing lifecycle-heavy code.
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
## Resource Events
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|---|---|---|---|
|
|
43
|
-
| `user_bash` | Before bash command runs | Yes (return modified command/cwd/env) | `{ command, cwd }` |
|
|
40
|
+
### `resources_discover`
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
Contribute skill, prompt, and theme paths after startup or reload.
|
|
46
43
|
|
|
47
44
|
```typescript
|
|
48
|
-
pi.on("
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
pi.on("resources_discover", async (event) => {
|
|
46
|
+
return {
|
|
47
|
+
skillPaths: ["/path/to/skills"],
|
|
48
|
+
promptPaths: ["/path/to/prompts"],
|
|
49
|
+
themePaths: ["/path/to/themes"],
|
|
50
|
+
};
|
|
51
51
|
});
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
`event.reason` is `"startup"` or `"reload"`.
|
|
55
55
|
|
|
56
|
-
##
|
|
56
|
+
## Session Events
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
| Event | Can cancel | Notes |
|
|
59
|
+
|---|---:|---|
|
|
60
|
+
| `session_start` | No | Session started, reloaded, resumed, or forked. |
|
|
61
|
+
| `session_before_switch` | Yes | Before `/new` or `/resume`. |
|
|
62
|
+
| `session_before_fork` | Yes | Before `/fork` or `/clone`; includes `position: "before" | "at"`. |
|
|
63
|
+
| `session_before_compact` | Yes/custom | Cancel or provide a custom compaction result. |
|
|
64
|
+
| `session_compact` | No | After compaction. |
|
|
65
|
+
| `session_before_tree` | Yes/custom | Before `/tree` navigation. |
|
|
66
|
+
| `session_tree` | No | After `/tree` navigation. |
|
|
67
|
+
| `session_shutdown` | No | Runtime teardown for quit, reload, new, resume, or fork. |
|
|
59
68
|
|
|
60
|
-
|
|
69
|
+
After a session replacement, the old runtime is torn down and extensions are rebound. Use `session_shutdown` for cleanup and `session_start` to rebuild in-memory state.
|
|
61
70
|
|
|
62
71
|
```typescript
|
|
63
|
-
pi.on("
|
|
64
|
-
if (event.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const confirmed = await ctx.ui.confirm(
|
|
71
|
-
"Dangerous Command",
|
|
72
|
-
`Allow: ${event.input.command}?`
|
|
73
|
-
);
|
|
74
|
-
if (!confirmed) {
|
|
75
|
-
return { block: true, reason: "Blocked by user" };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return undefined; // Allow the tool call
|
|
72
|
+
pi.on("session_before_switch", async (event, ctx) => {
|
|
73
|
+
if (event.reason !== "new") return;
|
|
74
|
+
if (!ctx.hasUI) return { cancel: true };
|
|
75
|
+
|
|
76
|
+
const confirmed = await ctx.ui.confirm("Clear session?", "All messages will be lost.");
|
|
77
|
+
if (!confirmed) return { cancel: true };
|
|
79
78
|
});
|
|
80
79
|
```
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
## Agent and Message Events
|
|
82
|
+
|
|
83
|
+
| Event | Purpose |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `before_agent_start` | Inject a message or replace the system prompt for this turn. |
|
|
86
|
+
| `agent_start` / `agent_end` | Whole prompt lifecycle. |
|
|
87
|
+
| `turn_start` / `turn_end` | One provider response plus tool batch. |
|
|
88
|
+
| `context` | Modify copied messages before a provider call. |
|
|
89
|
+
| `message_start` / `message_update` / `message_end` | Observe or replace messages. |
|
|
90
|
+
| `before_provider_request` | Inspect/replace provider-specific payload. |
|
|
91
|
+
| `after_provider_response` | Inspect response status/headers before stream consumption. |
|
|
92
|
+
| `model_select` | Model changed. |
|
|
93
|
+
| `thinking_level_select` | Thinking level changed. |
|
|
94
|
+
|
|
95
|
+
### `before_agent_start`
|
|
96
|
+
|
|
97
|
+
Use this for system prompt changes that depend on dynamic context. Per-tool `promptSnippet` and `promptGuidelines` are preferred for simple tool-local guidance.
|
|
83
98
|
|
|
84
99
|
```typescript
|
|
85
|
-
pi.on("
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return { cancel: true };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
100
|
+
pi.on("before_agent_start", async (event) => {
|
|
101
|
+
return {
|
|
102
|
+
systemPrompt: `${event.systemPrompt}\n\nExtra instructions for this turn.`,
|
|
103
|
+
};
|
|
92
104
|
});
|
|
93
105
|
```
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
`event.systemPromptOptions` exposes structured prompt inputs such as selected tools, tool snippets, prompt guidelines, context files, and loaded skills. `ctx.getSystemPrompt()` reflects changes made by earlier `before_agent_start` handlers in the chain.
|
|
108
|
+
|
|
109
|
+
### `message_end`
|
|
110
|
+
|
|
111
|
+
`message_end` handlers can replace a finalized message. Keep the same role.
|
|
96
112
|
|
|
97
113
|
```typescript
|
|
98
|
-
pi.on("
|
|
114
|
+
pi.on("message_end", async (event) => {
|
|
115
|
+
if (event.message.role !== "assistant") return;
|
|
99
116
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
message: {
|
|
118
|
+
...event.message,
|
|
119
|
+
usage: {
|
|
120
|
+
...event.message.usage,
|
|
121
|
+
cost: { ...event.message.usage.cost, total: 0 },
|
|
122
|
+
},
|
|
104
123
|
},
|
|
105
124
|
};
|
|
106
125
|
});
|
|
107
126
|
```
|
|
108
127
|
|
|
109
|
-
##
|
|
128
|
+
## Tool Events
|
|
129
|
+
|
|
130
|
+
| Event | Can block/modify | Notes |
|
|
131
|
+
|---|---:|---|
|
|
132
|
+
| `tool_execution_start` | No | Tool started. |
|
|
133
|
+
| `tool_call` | Block/mutate input | Runs before tool execution. |
|
|
134
|
+
| `tool_execution_update` | No | Partial result update. |
|
|
135
|
+
| `tool_result` | Modify result | Runs after tool execution, before final events. |
|
|
136
|
+
| `tool_execution_end` | No | Tool completed. |
|
|
137
|
+
|
|
138
|
+
Tool calls from one assistant message are preflighted sequentially, then run concurrently by default. Do not assume sibling tool results are visible inside `tool_call`.
|
|
139
|
+
|
|
140
|
+
### Blocking tool calls
|
|
141
|
+
|
|
142
|
+
Use `isToolCallEventType` for typed built-in inputs.
|
|
110
143
|
|
|
111
144
|
```typescript
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
145
|
+
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
146
|
+
|
|
147
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
148
|
+
if (!isToolCallEventType("bash", event)) return;
|
|
149
|
+
|
|
150
|
+
if (!event.input.command.includes("rm -rf")) return;
|
|
151
|
+
|
|
152
|
+
if (!ctx.hasUI) {
|
|
153
|
+
return { block: true, reason: "Dangerous command blocked because no UI is available." };
|
|
115
154
|
}
|
|
116
|
-
|
|
155
|
+
|
|
156
|
+
const confirmed = await ctx.ui.confirm("Dangerous command", `Allow ${event.input.command}?`);
|
|
157
|
+
if (!confirmed) return { block: true, reason: "Blocked by user" };
|
|
117
158
|
});
|
|
118
159
|
```
|
|
119
160
|
|
|
120
|
-
|
|
161
|
+
`event.input` is mutable. Mutating it changes the arguments passed to the tool. Pi does not revalidate after your mutation.
|
|
162
|
+
|
|
163
|
+
### Typing custom tool input
|
|
164
|
+
|
|
165
|
+
Export your custom tool input type and use explicit type params with `isToolCallEventType`.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
if (isToolCallEventType<"my_tool", MyToolParams>("my_tool", event)) {
|
|
169
|
+
event.input.action;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Modifying tool results
|
|
121
174
|
|
|
122
175
|
```typescript
|
|
123
|
-
pi.on("
|
|
176
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
177
|
+
if (event.toolName !== "bash") return;
|
|
178
|
+
|
|
179
|
+
const response = await fetch("https://example.com/summarize", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
body: JSON.stringify({ content: event.content }),
|
|
182
|
+
signal: ctx.signal,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const summary = await response.text();
|
|
124
186
|
return {
|
|
125
|
-
|
|
126
|
-
cwd: "/sandboxed/directory",
|
|
127
|
-
env: { ...process.env, SANDBOX: "true" },
|
|
187
|
+
content: [...event.content, { type: "text", text: `\nSummary: ${summary}` }],
|
|
128
188
|
};
|
|
129
189
|
});
|
|
130
190
|
```
|
|
131
191
|
|
|
132
|
-
|
|
192
|
+
`tool_result` handlers chain like middleware. Return partial patches (`content`, `details`, `isError`) and omit fields that should stay unchanged.
|
|
133
193
|
|
|
134
|
-
|
|
194
|
+
## Input Events
|
|
135
195
|
|
|
136
|
-
|
|
196
|
+
`input` fires after extension command checks and before skill/template expansion. Return an action object.
|
|
137
197
|
|
|
138
198
|
```typescript
|
|
139
|
-
pi.on("
|
|
140
|
-
return {
|
|
141
|
-
|
|
142
|
-
|
|
199
|
+
pi.on("input", async (event) => {
|
|
200
|
+
if (event.source === "extension") return { action: "continue" };
|
|
201
|
+
|
|
202
|
+
if (event.text.startsWith("?quick ")) {
|
|
203
|
+
return {
|
|
204
|
+
action: "transform",
|
|
205
|
+
text: `Respond briefly: ${event.text.slice(7)}`,
|
|
206
|
+
images: event.images,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (event.text === "ping") {
|
|
211
|
+
return { action: "handled" };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { action: "continue" };
|
|
143
215
|
});
|
|
144
216
|
```
|
|
145
217
|
|
|
146
|
-
|
|
218
|
+
Actions:
|
|
147
219
|
|
|
148
|
-
|
|
220
|
+
- `continue`: keep processing.
|
|
221
|
+
- `transform`: replace text/images, then continue.
|
|
222
|
+
- `handled`: stop; the extension handled the input.
|
|
149
223
|
|
|
150
|
-
|
|
224
|
+
Transforms chain across handlers. The first `handled` wins.
|
|
225
|
+
|
|
226
|
+
## User Bash Events
|
|
227
|
+
|
|
228
|
+
`user_bash` fires for user `!` and `!!` commands. It is separate from LLM bash tool calls.
|
|
229
|
+
|
|
230
|
+
Use it to provide custom bash operations or a direct result.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { createLocalBashOperations } from "@earendil-works/pi-coding-agent";
|
|
234
|
+
|
|
235
|
+
pi.on("user_bash", (event) => {
|
|
236
|
+
const local = createLocalBashOperations();
|
|
237
|
+
return {
|
|
238
|
+
operations: {
|
|
239
|
+
exec(command, cwd, options) {
|
|
240
|
+
return local.exec(`source ~/.profile\n${command}`, cwd, options);
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
```
|
|
151
246
|
|
|
152
|
-
|
|
247
|
+
For transparent LLM bash tool rewriting, use a bash spawn hook instead.
|
|
153
248
|
|
|
154
|
-
|
|
249
|
+
## Bash Spawn Hook
|
|
155
250
|
|
|
156
|
-
|
|
251
|
+
`createBashTool(cwd, { spawnHook })` creates a bash tool that rewrites command/cwd/env before execution. Registering a tool named `bash` overrides the built-in bash tool.
|
|
157
252
|
|
|
158
253
|
```typescript
|
|
159
|
-
import { createBashTool, type
|
|
254
|
+
import { createBashTool, type BashSpawnContext } from "@earendil-works/pi-coding-agent";
|
|
160
255
|
|
|
161
|
-
export default function (pi: ExtensionAPI) {
|
|
256
|
+
export default function hooksExtension(pi: ExtensionAPI) {
|
|
162
257
|
const bashTool = createBashTool(process.cwd(), {
|
|
163
258
|
spawnHook: ({ command, cwd, env }: BashSpawnContext): BashSpawnContext => ({
|
|
164
259
|
command: command.replace(/^npm /, "pnpm "),
|
|
165
260
|
cwd,
|
|
166
|
-
env: { ...env,
|
|
261
|
+
env: { ...env, CI: "1" },
|
|
167
262
|
}),
|
|
168
263
|
});
|
|
169
264
|
|
|
@@ -171,77 +266,55 @@ export default function (pi: ExtensionAPI) {
|
|
|
171
266
|
}
|
|
172
267
|
```
|
|
173
268
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
The spawn hook receives and returns a `BashSpawnContext`:
|
|
269
|
+
Use spawn hooks for clear rewrites or env injection. Use `tool_call` blocking for safety gates and confirmation dialogs.
|
|
177
270
|
|
|
178
|
-
|
|
179
|
-
interface BashSpawnContext {
|
|
180
|
-
command: string; // The shell command to execute
|
|
181
|
-
cwd: string; // Working directory
|
|
182
|
-
env: NodeJS.ProcessEnv; // Environment variables
|
|
183
|
-
}
|
|
184
|
-
```
|
|
271
|
+
Key points:
|
|
185
272
|
|
|
186
|
-
|
|
273
|
+
- Prompt metadata is not inherited when overriding built-ins. Re-declare `promptSnippet`/`promptGuidelines` if needed.
|
|
274
|
+
- Tool-call blockers run before the spawn hook.
|
|
275
|
+
- Prefer parsed shell rewrites over broad regex replacements.
|
|
187
276
|
|
|
188
|
-
|
|
277
|
+
## Provider Request Hooks
|
|
189
278
|
|
|
190
|
-
|
|
191
|
-
- **Transparent to the agent.** The agent sees the original command in the tool call UI but gets the output of the rewritten command.
|
|
192
|
-
- **Execution order with tool_call hooks.** `tool_call` event hooks (blockers) run first. If a blocker returns `{ block: true }`, the spawn hook never fires. This means you can combine blocking hooks for commands that should be stopped entirely with spawn hooks for commands that should be rewritten.
|
|
193
|
-
- **Prefer AST-based rewrites over regex.** A false positive rewrite corrupts a command silently. Use `@aliou/sh` or similar shell parsers to identify command names in the AST, then do surgical string replacement at the identified positions. If the parse fails, return the command unchanged.
|
|
194
|
-
- **Compose multiple rewriters.** Chain rewriter functions that each transform the context:
|
|
279
|
+
Use these for debugging serialization, proxies, or cache behavior.
|
|
195
280
|
|
|
196
281
|
```typescript
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const spawnHook = (ctx: BashSpawnContext) => {
|
|
203
|
-
let result = ctx;
|
|
204
|
-
for (const rewrite of rewriters) {
|
|
205
|
-
result = rewrite(result);
|
|
206
|
-
}
|
|
207
|
-
return result;
|
|
208
|
-
};
|
|
282
|
+
pi.on("before_provider_request", (event) => {
|
|
283
|
+
console.log(JSON.stringify(event.payload, null, 2));
|
|
284
|
+
// return { ...event.payload, temperature: 0 };
|
|
285
|
+
});
|
|
209
286
|
|
|
210
|
-
|
|
211
|
-
|
|
287
|
+
pi.on("after_provider_response", (event) => {
|
|
288
|
+
if (event.status === 429) {
|
|
289
|
+
console.log("rate limited", event.headers["retry-after"]);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
212
292
|
```
|
|
213
293
|
|
|
214
|
-
|
|
294
|
+
Payload-level rewrites are provider-specific and are not reflected by `ctx.getSystemPrompt()`.
|
|
215
295
|
|
|
216
|
-
|
|
217
|
-
|---|---|---|
|
|
218
|
-
| `tool_call` hook + `{ block: true }` | Command must be stopped entirely | Block reason (retries with correct command) |
|
|
219
|
-
| `tool_call` hook + `ctx.ui.confirm()` | User confirmation needed | Block reason if denied |
|
|
220
|
-
| Spawn hook (command rewrite) | Clear 1:1 rewrite target exists | Output of rewritten command (transparent) |
|
|
221
|
-
| Spawn hook (env injection) | Need to set env vars for specific commands | Output with injected env (transparent) |
|
|
296
|
+
## Mode Awareness
|
|
222
297
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Multiple extensions can register handlers for the same event. They execute in registration order. For blocking events (`tool_call`, `session_before_switch`, etc.), the first handler to return a blocking/cancelling result wins.
|
|
226
|
-
|
|
227
|
-
## Mode Awareness in Hooks
|
|
228
|
-
|
|
229
|
-
Always consider what happens in Print mode when your hook uses dialog methods. See `references/modes.md` for the full behavior matrix.
|
|
230
|
-
|
|
231
|
-
Common pattern for `tool_call` handlers:
|
|
298
|
+
Dialog methods that gate behavior need a safe no-UI default. Fire-and-forget methods are safe without guards.
|
|
232
299
|
|
|
233
300
|
```typescript
|
|
234
301
|
pi.on("tool_call", async (event, ctx) => {
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const choice = await ctx.ui.select("Allow?", ["Yes", "No"]);
|
|
241
|
-
if (choice !== "Yes") {
|
|
242
|
-
return { block: true, reason: "Blocked by user" };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return undefined;
|
|
302
|
+
if (!shouldConfirm(event)) return;
|
|
303
|
+
if (!ctx.hasUI) return { block: true, reason: "No UI to confirm" };
|
|
304
|
+
|
|
305
|
+
const choice = await ctx.ui.select("Allow?", ["Allow", "Block"]);
|
|
306
|
+
if (choice !== "Allow") return { block: true, reason: "Blocked" };
|
|
246
307
|
});
|
|
247
308
|
```
|
|
309
|
+
|
|
310
|
+
Read `references/modes.md` before adding UI to hooks.
|
|
311
|
+
|
|
312
|
+
## Checklist
|
|
313
|
+
|
|
314
|
+
- [ ] Event return shape matches current Pi docs.
|
|
315
|
+
- [ ] Blocking hooks have safe defaults when `ctx.hasUI` is false.
|
|
316
|
+
- [ ] `input` hooks return `{ action: ... }`, not raw strings.
|
|
317
|
+
- [ ] `before_agent_start` returns `{ systemPrompt }`; it does not call `ctx.setSystemPrompt()`.
|
|
318
|
+
- [ ] Nested async work uses `ctx.signal` when available.
|
|
319
|
+
- [ ] Session replacement cleanup/rebuild is split between `session_shutdown` and `session_start`.
|
|
320
|
+
- [ ] Built-in tool overrides re-declare prompt metadata if needed.
|