opencode-command-hooks 0.1.0 → 0.1.2
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 +216 -571
- package/dist/config/agent.d.ts.map +1 -1
- package/dist/config/agent.js +2 -1
- package/dist/config/agent.js.map +1 -1
- package/dist/config/global.d.ts +6 -15
- package/dist/config/global.d.ts.map +1 -1
- package/dist/config/global.js +56 -53
- package/dist/config/global.js.map +1 -1
- package/dist/config/merge.d.ts.map +1 -1
- package/dist/config/merge.js +3 -1
- package/dist/config/merge.js.map +1 -1
- package/dist/execution/shell.d.ts +3 -0
- package/dist/execution/shell.d.ts.map +1 -1
- package/dist/execution/shell.js +4 -7
- package/dist/execution/shell.js.map +1 -1
- package/dist/executor.d.ts +2 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +19 -10
- package/dist/executor.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -33
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +18 -15
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -1
- package/dist/schemas.js.map +1 -1
- package/dist/types/hooks.d.ts +18 -3
- package/dist/types/hooks.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,65 +1,42 @@
|
|
|
1
|
-
# 🪝OpenCode Command Hooks
|
|
1
|
+
# 🪝 OpenCode Command Hooks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use simple configs to easily integrate shell command hooks with tool/subagent invocations. With a single line of configuration, you can inject a hook's output directly into context for your agent to read.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Example use cases: run tests after a subagent finishes a task, auto-lint after writes, etc. You can also configure the hooks to run only when specified arguments are passed to a given tool.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
{
|
|
9
|
-
"tool": [
|
|
10
|
-
{
|
|
11
|
-
"id": "auto-validate",
|
|
12
|
-
"when": {
|
|
13
|
-
"phase": "after",
|
|
14
|
-
"tool": "task",
|
|
15
|
-
"toolArgs": { "subagent_type": "engineer" },
|
|
16
|
-
},
|
|
17
|
-
"run": ["npm run typecheck", "npm test"],
|
|
18
|
-
"inject": "✅ Validation: {exitCode, select, 0 {Passed} other {Failed - fix errors below}}\n```\n{stdout}\n```",
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
}
|
|
22
|
-
````
|
|
23
|
-
|
|
24
|
-
Every time the engineer finishes, tests run automatically and results flow back to the orchestrator. Failed validations trigger self-healing—no manual intervention, no token costs.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Simplified Agent Markdown Hooks
|
|
29
|
-
|
|
30
|
-
Define hooks directly in your agent's markdown file for maximum simplicity. No need for global configs, `id` fields, `when` clauses, or `tool` specifications—everything is auto-configured when you use subagents via the `task` tool.
|
|
7
|
+
## Markdown Frontmatter Hooks
|
|
31
8
|
|
|
32
|
-
|
|
9
|
+
Define hooks in just a couple lines of markdown frontmatter. Putting them here is also really nice because you can see your entire agent's config in one place.
|
|
33
10
|
|
|
34
|
-
|
|
11
|
+
````yaml
|
|
35
12
|
---
|
|
36
|
-
description:
|
|
13
|
+
description: Analyzes the codebase and implements code changes.
|
|
37
14
|
mode: subagent
|
|
38
15
|
hooks:
|
|
39
|
-
before:
|
|
40
|
-
- run: "echo 'Starting...'"
|
|
41
16
|
after:
|
|
42
|
-
- run:
|
|
43
|
-
inject: "
|
|
44
|
-
- run: "npm test"
|
|
45
|
-
toast:
|
|
46
|
-
message: "Tests {exitCode, select, 0 {passed} other {failed}}"
|
|
17
|
+
- run: "npm run test"
|
|
18
|
+
inject: "Test Output:\n{stdout}\n{stderr}"
|
|
47
19
|
---
|
|
48
|
-
|
|
49
|
-
```
|
|
20
|
+
````
|
|
50
21
|
|
|
51
22
|
### How It Works
|
|
52
23
|
|
|
53
|
-
1. **
|
|
54
|
-
2. **
|
|
55
|
-
3. **
|
|
56
|
-
4. **
|
|
57
|
-
|
|
58
|
-
|
|
24
|
+
1. **Runs automatically** on the configured event
|
|
25
|
+
2. **Executes shell commands** (sequentially, if you pass an array)
|
|
26
|
+
3. **Captures output** (truncated to configured limit, default 30,000 characters)
|
|
27
|
+
4. **Optionally reports results** via `inject` (to the session) and/or `toast` (to the UI).
|
|
28
|
+
|
|
29
|
+
## Why?
|
|
59
30
|
|
|
60
|
-
|
|
31
|
+
When working with a fleet of subagents, automatic validation of the state of your codebase is really useful. By setting up quality gates (lint/typecheck/test/etc.) or other automation, you can catch and prevent errors quickly and reliably.
|
|
61
32
|
|
|
62
|
-
|
|
33
|
+
Doing this by asking your orchestrator agent to use the bash tool (or call a validator subagent) is non-deterministic and can cost a lot of tokens over time. You could always write your own custom plugin to achieve this automatic validation behavior, but I found myself writing the same boilerplate, error handling, output capture, and session injection logic over and over again.
|
|
34
|
+
|
|
35
|
+
Though this plugin is mostly a wrapper around accessing hooks that Opencode already exposes, it provides basic plumbing that reduces overhead, giving you a simple, opinionated system for integrating command hooks into your Opencode workflow. I also just like having hooks/config for my agents all colocated in one place (markdown files) and thought that maybe somebody else would like this too.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### JSON Config
|
|
63
40
|
|
|
64
41
|
```jsonc
|
|
65
42
|
{
|
|
@@ -71,488 +48,282 @@ hooks:
|
|
|
71
48
|
"tool": "task",
|
|
72
49
|
"toolArgs": { "subagent_type": "engineer" },
|
|
73
50
|
},
|
|
74
|
-
"run": ["npm run typecheck", "npm test"],
|
|
75
|
-
"inject": "Validation
|
|
76
|
-
"toast": {
|
|
77
|
-
"title": "Validation Complete",
|
|
78
|
-
"message": "{exitCode, select, 0 {✓ Passed} other {✗ Failed}}",
|
|
79
|
-
},
|
|
51
|
+
"run": ["npm run lint", "npm run typecheck", "npm test"],
|
|
52
|
+
"inject": "Validation Results (exit {exitCode}): \n`{stdout}`\n`{stderr}`",
|
|
80
53
|
},
|
|
81
54
|
],
|
|
82
55
|
}
|
|
83
56
|
```
|
|
84
57
|
|
|
85
|
-
|
|
58
|
+
### Markdown Frontmatter Config
|
|
86
59
|
|
|
87
60
|
```yaml
|
|
88
61
|
hooks:
|
|
89
62
|
after:
|
|
90
|
-
- run: ["npm run typecheck", "npm test"]
|
|
91
|
-
inject: "Validation
|
|
63
|
+
- run: ["npm run lint", "npm run typecheck", "npm test"]
|
|
64
|
+
inject: "Validation Results (exit {exitCode}): \n`{stdout}`\n`{stderr}`"
|
|
92
65
|
toast:
|
|
93
|
-
message: "
|
|
66
|
+
message: "Validation Complete"
|
|
94
67
|
```
|
|
95
68
|
|
|
96
|
-
**Reduction: 60% less boilerplate!**
|
|
97
|
-
|
|
98
69
|
### Hook Configuration Options
|
|
99
70
|
|
|
100
|
-
| Option | Type |
|
|
101
|
-
| -------- | ---------------------- |
|
|
102
|
-
| `run` | `string` \| `string[]` |
|
|
103
|
-
| `inject` | `string` |
|
|
104
|
-
| `toast` | `object` |
|
|
71
|
+
| Option | Type | Description |
|
|
72
|
+
| -------- | ---------------------- | --------------------------------- |
|
|
73
|
+
| `run` | `string` \| `string[]` | Command(s) to execute |
|
|
74
|
+
| `inject` | `string` | Message injected into the session |
|
|
75
|
+
| `toast` | `object` | Toast notification configuration |
|
|
105
76
|
|
|
106
77
|
### Toast Configuration
|
|
107
78
|
|
|
108
79
|
```yaml
|
|
109
80
|
toast:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
81
|
+
title: "Build" # optional
|
|
82
|
+
message: "exit {exitCode}"
|
|
83
|
+
variant: "info" # optional- one of: info, success, warning, error
|
|
84
|
+
duration: 5000 # optional (milliseconds)
|
|
113
85
|
```
|
|
114
86
|
|
|
115
|
-
### Template Variables
|
|
116
|
-
|
|
117
|
-
Agent markdown hooks support the same template variables as global config:
|
|
87
|
+
### Inject String Template Variables
|
|
118
88
|
|
|
119
|
-
- `{
|
|
120
|
-
- `{
|
|
121
|
-
- `{
|
|
89
|
+
- `{id}` - Hook ID
|
|
90
|
+
- `{agent}` - Agent name (if available)
|
|
91
|
+
- `{tool}` - Tool name (tool hooks only)
|
|
122
92
|
- `{cmd}` - Executed command
|
|
93
|
+
- `{stdout}` - Command stdout (truncated)
|
|
94
|
+
- `{stderr}` - Command stderr (truncated)
|
|
95
|
+
- `{exitCode}` - Command exit code
|
|
123
96
|
|
|
124
97
|
### Complete Example
|
|
125
98
|
|
|
126
|
-
````
|
|
99
|
+
````markdown
|
|
127
100
|
---
|
|
128
101
|
description: Engineer Agent
|
|
129
102
|
mode: subagent
|
|
130
103
|
hooks:
|
|
131
104
|
before:
|
|
132
|
-
- run: "echo '
|
|
105
|
+
- run: "echo 'Engineer starting...'"
|
|
106
|
+
toast:
|
|
107
|
+
message: "Engineer starting"
|
|
108
|
+
variant: "info"
|
|
133
109
|
after:
|
|
134
110
|
- run: ["npm run typecheck", "npm run lint"]
|
|
135
|
-
inject:
|
|
136
|
-
## Validation Results
|
|
137
|
-
|
|
138
|
-
**TypeCheck:** {exitCode, select, 0 {✓ Passed} other {✗ Failed}}
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
{stdout}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
{exitCode, select, 0 {} other {⚠️ Please fix validation errors before proceeding.}}
|
|
145
|
-
toast:
|
|
146
|
-
message: "TypeCheck & Lint: {exitCode, select, 0 {✓ Passed} other {✗ Failed}}"
|
|
147
|
-
variant: "{exitCode, select, 0 {success} other {error}}"
|
|
148
|
-
- run: "npm test -- --coverage --passWithNoTests"
|
|
149
|
-
inject: "Test Coverage: {stdout}%"
|
|
150
|
-
toast:
|
|
151
|
-
message: "Tests {exitCode, select, 0 {✓ Passed} other {✗ Failed}}"
|
|
152
|
-
variant: "{exitCode, select, 0 {success} other {error}}"
|
|
111
|
+
inject: "Typecheck + lint (exit {exitCode}) ``` {stdout} ```"
|
|
153
112
|
---
|
|
154
|
-
|
|
155
|
-
|
|
113
|
+
|
|
114
|
+
<your subagent instructions>
|
|
156
115
|
````
|
|
157
116
|
|
|
158
117
|
---
|
|
159
118
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
**The Problem:** You want your engineer/validator subagents to automatically run tests/linters and self-heal when validation fails—but asking agents to run validation costs tokens, isn't guaranteed, and requires complex native plugin code with manual error handling.
|
|
163
|
-
|
|
164
|
-
**The Solution:** This plugin lets you attach validation commands to subagent completions via simple config. Results automatically inject back to the orchestrator, enabling autonomous quality gates with zero tokens spent.
|
|
119
|
+
### Automatic Context Injection
|
|
165
120
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Custom OpenCode plugins require TypeScript setup, manual error handling, and build processes. For routine automation tasks, this creates unnecessary complexity. This plugin provides shell hook functionality through configuration files—no tokens spent prompting agents to run repetitive commands.
|
|
169
|
-
|
|
170
|
-
**Native Plugin (Requires TypeScript):**
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
// .opencode/plugin/my-plugin.ts
|
|
174
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
121
|
+
If `inject` is set, the command output is posted into the session, so your agents can react to failures.
|
|
175
122
|
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
"tool.execute.after": async (input) => {
|
|
179
|
-
if (input.tool === "write") {
|
|
180
|
-
try {
|
|
181
|
-
await $`npm run lint`;
|
|
182
|
-
} catch (e) {
|
|
183
|
-
console.error(e); // UI spam or crashes
|
|
184
|
-
// Agent won't see what failed unless you inject it back
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
};
|
|
190
|
-
```
|
|
123
|
+
### Filter by Tool Arguments
|
|
191
124
|
|
|
192
|
-
|
|
125
|
+
You can set up tool hooks to only trigger on specific arguments via `when.toolArgs`.
|
|
193
126
|
|
|
194
127
|
```jsonc
|
|
195
128
|
{
|
|
196
|
-
"
|
|
197
|
-
{
|
|
198
|
-
"id": "lint-ts",
|
|
199
|
-
"when": { "tool": "write" },
|
|
200
|
-
"run": ["npm run lint"],
|
|
201
|
-
"inject": {
|
|
202
|
-
"as": "system",
|
|
203
|
-
"template": "Lint results:\n{stdout}\n{stderr}",
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### 2. Automatic Context Injection
|
|
211
|
-
|
|
212
|
-
Command output returns to the agent automatically without manual SDK calls. The agent can see and react to errors, warnings, or other results:
|
|
213
|
-
|
|
214
|
-
```jsonc
|
|
215
|
-
{
|
|
216
|
-
"tool": [
|
|
217
|
-
{
|
|
218
|
-
"id": "typecheck",
|
|
219
|
-
"when": { "tool": "write", "toolArgs": { "path": "*.ts" } },
|
|
220
|
-
"run": ["npm run typecheck"],
|
|
221
|
-
"inject": {
|
|
222
|
-
"as": "system",
|
|
223
|
-
"template": "TypeScript check:\n{stdout}\n{stderr}",
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
],
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
For background tasks where the agent doesn't need context, use toast notifications instead:
|
|
231
|
-
|
|
232
|
-
```jsonc
|
|
233
|
-
{
|
|
234
|
-
"tool": [
|
|
235
|
-
{
|
|
236
|
-
"id": "build-status",
|
|
237
|
-
"when": { "tool": "write", "phase": "after" },
|
|
238
|
-
"run": ["npm run build"],
|
|
239
|
-
"toast": {
|
|
240
|
-
"title": "Build Status",
|
|
241
|
-
"message": "Build {exitCode, select, 0 {succeeded} other {failed}}",
|
|
242
|
-
"variant": "{exitCode, select, 0 {success} other {error}}",
|
|
243
|
-
"duration": 5000,
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
],
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### 3. Filter Tools by Any Argument
|
|
251
|
-
|
|
252
|
-
Run different hooks based on any tool argument—not just task subagent types. Filter by file paths, API endpoints, model names, or custom parameters.
|
|
253
|
-
|
|
254
|
-
```jsonc
|
|
255
|
-
// Filter by multiple subagent types
|
|
256
|
-
{
|
|
257
|
-
"when": {
|
|
258
|
-
"tool": "task",
|
|
259
|
-
"toolArgs": { "subagent_type": ["validator", "reviewer", "tester"] }
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Filter write tool by file extension
|
|
264
|
-
{
|
|
129
|
+
"id": "playwright-access-localhost",
|
|
265
130
|
"when": {
|
|
266
|
-
"
|
|
267
|
-
"
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"
|
|
274
|
-
"
|
|
275
|
-
"toolArgs": { "url": "*/api/validate" }
|
|
131
|
+
"phase": "after",
|
|
132
|
+
"tool": "playwright_browser_navigate",
|
|
133
|
+
"toolArgs": { "url": "http://localhost:3000]" }
|
|
134
|
+
},
|
|
135
|
+
"run": [
|
|
136
|
+
"osascript -e 'display notification \"Agent triggered playwright\"'"
|
|
137
|
+
],
|
|
138
|
+
"toast": {
|
|
139
|
+
"message": "Agent used the playwright {tool} tool"
|
|
276
140
|
}
|
|
277
141
|
}
|
|
278
142
|
```
|
|
279
143
|
|
|
280
|
-
### 4. Reliable Execution
|
|
281
|
-
|
|
282
|
-
- **Non-blocking**: Hook failures don't crash the agent
|
|
283
|
-
- **Automatic error handling**: Failed hooks inject error messages automatically
|
|
284
|
-
- **Memory safe**: Output truncated to prevent memory issues
|
|
285
|
-
- **Sequential execution**: Commands run in order, even if earlier ones fail
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
144
|
## Features
|
|
290
145
|
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
-
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
-
|
|
298
|
-
-
|
|
146
|
+
- Tool hooks (`before`/`after`) and session hooks (`start`/`idle`)
|
|
147
|
+
- Hooks are **non-blocking**: failures don’t crash the session/tool.
|
|
148
|
+
- Commands run **sequentially**, even if earlier ones fail.
|
|
149
|
+
- `inject`/`toast` interpolate using the **last command’s** output if `run` is an array.
|
|
150
|
+
- Match by tool name, calling agent, slash command, and tool arguments
|
|
151
|
+
- Optional session injection and toast notifications
|
|
152
|
+
- Output truncation to keep memory bounded
|
|
153
|
+
- Debug logging via `OPENCODE_HOOKS_DEBUG=1`
|
|
299
154
|
|
|
300
155
|
---
|
|
301
156
|
|
|
302
157
|
## Installation
|
|
303
158
|
|
|
304
|
-
|
|
305
|
-
|
|
159
|
+
Add to your `opencode.json`:
|
|
160
|
+
|
|
161
|
+
```jsonc
|
|
162
|
+
{
|
|
163
|
+
"plugin": ["opencode-command-hooks"]
|
|
164
|
+
}
|
|
306
165
|
```
|
|
307
166
|
|
|
308
167
|
---
|
|
309
168
|
|
|
310
169
|
## Configuration
|
|
311
170
|
|
|
312
|
-
###
|
|
171
|
+
### JSON Config
|
|
313
172
|
|
|
314
|
-
Create `.opencode/command-hooks.jsonc` in your project
|
|
173
|
+
Create `.opencode/command-hooks.jsonc` in your project (the plugin searches upward from the current working directory):
|
|
315
174
|
|
|
316
175
|
```jsonc
|
|
317
176
|
{
|
|
177
|
+
"truncationLimit": 30000,
|
|
318
178
|
"tool": [
|
|
319
|
-
//
|
|
179
|
+
// Tool hooks
|
|
320
180
|
],
|
|
321
181
|
"session": [
|
|
322
|
-
//
|
|
182
|
+
// Session hooks
|
|
323
183
|
],
|
|
324
184
|
}
|
|
325
185
|
```
|
|
326
186
|
|
|
187
|
+
#### JSON Config Options
|
|
188
|
+
|
|
189
|
+
| Option | Type | Description |
|
|
190
|
+
| ------ | ---- | ----------- |
|
|
191
|
+
| `truncationLimit` | `number` | Maximum characters to capture from command output. Defaults to 30,000 (matching OpenCode's bash tool). Must be a positive integer. |
|
|
192
|
+
| `tool` | `ToolHook[]` | Array of tool execution hooks |
|
|
193
|
+
| `session` | `SessionHook[]`| Array of session lifecycle hooks |
|
|
194
|
+
|
|
327
195
|
### Markdown Frontmatter
|
|
328
196
|
|
|
329
|
-
|
|
197
|
+
Use `hooks:` in agent markdown for the simplified format:
|
|
330
198
|
|
|
331
199
|
```markdown
|
|
332
200
|
---
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
201
|
+
description: Engineer agent
|
|
202
|
+
mode: subagent
|
|
203
|
+
hooks:
|
|
204
|
+
before:
|
|
205
|
+
- run: "echo 'Starting engineering work...'"
|
|
206
|
+
after:
|
|
207
|
+
- run: "npm run lint"
|
|
208
|
+
inject: "Lint output:\n{stdout}\n{stderr}"
|
|
338
209
|
---
|
|
339
|
-
|
|
340
|
-
# Your markdown content
|
|
341
210
|
```
|
|
342
211
|
|
|
343
212
|
### Configuration Precedence
|
|
344
213
|
|
|
345
|
-
1.
|
|
346
|
-
2.
|
|
347
|
-
3.
|
|
348
|
-
4.
|
|
214
|
+
1. Hooks are loaded from `.opencode/command-hooks.jsonc`
|
|
215
|
+
2. Markdown hooks are converted to normal hooks with auto-generated IDs
|
|
216
|
+
3. If a markdown hook and a global hook share the same `id`, the markdown hook wins
|
|
217
|
+
4. Duplicate IDs within the same source are errors
|
|
218
|
+
5. Global config is cached to avoid repeated file reads
|
|
349
219
|
|
|
350
220
|
---
|
|
351
221
|
|
|
352
222
|
## Examples
|
|
353
223
|
|
|
354
|
-
###
|
|
224
|
+
### Autonomous Quality Gates (After `task`)
|
|
355
225
|
|
|
356
|
-
|
|
226
|
+
Run validation after certain subagents complete, inject results back into the session, and show a small toast.
|
|
357
227
|
|
|
358
|
-
|
|
228
|
+
```jsonc
|
|
359
229
|
{
|
|
360
230
|
"tool": [
|
|
361
231
|
{
|
|
362
|
-
"id": "validate-
|
|
232
|
+
"id": "validate-after-task",
|
|
363
233
|
"when": {
|
|
364
234
|
"phase": "after",
|
|
365
235
|
"tool": "task",
|
|
366
236
|
"toolArgs": { "subagent_type": ["engineer", "debugger"] },
|
|
367
237
|
},
|
|
368
|
-
"run": [
|
|
369
|
-
|
|
370
|
-
"npm run lint",
|
|
371
|
-
"npm test -- --coverage --passWithNoTests",
|
|
372
|
-
],
|
|
373
|
-
"inject": "🔍 Validation Results:\n\n**TypeCheck:** {exitCode, select, 0 {✓ Passed} other {✗ Failed}}\n\n```\n{stdout}\n```\n\n{exitCode, select, 0 {} other {⚠️ The code you just wrote has validation errors. Please fix them before proceeding.}}",
|
|
238
|
+
"run": ["npm run typecheck", "npm run lint", "npm test"],
|
|
239
|
+
"inject": "Validation (exit {exitCode})\n\n{stdout}\n{stderr}",
|
|
374
240
|
"toast": {
|
|
375
|
-
"title": "
|
|
376
|
-
"message": "{exitCode
|
|
377
|
-
"variant": "
|
|
241
|
+
"title": "Validation",
|
|
242
|
+
"message": "exit {exitCode}",
|
|
243
|
+
"variant": "info",
|
|
378
244
|
"duration": 5000,
|
|
379
245
|
},
|
|
380
246
|
},
|
|
381
|
-
{
|
|
382
|
-
"id": "verify-test-coverage",
|
|
383
|
-
"when": {
|
|
384
|
-
"phase": "after",
|
|
385
|
-
"tool": "task",
|
|
386
|
-
"toolArgs": { "subagent_type": "engineer" },
|
|
387
|
-
},
|
|
388
|
-
"run": [
|
|
389
|
-
"npm test -- --coverage --json > coverage.json && node -p 'JSON.parse(require(\"fs\").readFileSync(\"coverage.json\")).coverageMap.total.lines.pct'",
|
|
390
|
-
],
|
|
391
|
-
"inject": "📊 Test Coverage: {stdout}%\n\n{stdout, select, ^[89]\\d|100$ {} other {⚠️ Coverage is below 80%. Please add more tests.}}",
|
|
392
|
-
},
|
|
393
247
|
],
|
|
394
248
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
**What makes this powerful:**
|
|
398
|
-
|
|
399
|
-
1. **Zero-Token Enforcement** - Quality gates run automatically after engineer/debugger subagents without consuming tokens to prompt validation
|
|
400
|
-
2. **Intelligent Filtering** - Uses `toolArgs.subagent_type` to target specific subagents (impossible with native plugins without SDK access)
|
|
401
|
-
3. **Context Injection** - Validation results automatically flow back to the orchestrator/agent, enabling self-healing workflows
|
|
402
|
-
4. **Non-Blocking** - Failed validations don't crash the session; the agent sees errors and can fix them
|
|
403
|
-
5. **Dual Feedback** - Users get instant toast notifications while agents receive detailed error context
|
|
404
|
-
6. **Sequential Commands** - Multiple validation steps run in order, even if earlier ones fail
|
|
405
|
-
7. **Template Power** - Conditional messages using ICU MessageFormat syntax (`{exitCode, select, ...}`)
|
|
406
|
-
|
|
407
|
-
**Real-world impact:** A single hook configuration replaces 50+ lines of TypeScript plugin code with error handling, session.prompt calls, and manual filtering logic. The agent becomes self-validating without you spending a single token to ask it to run tests.
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
### Basic Examples
|
|
249
|
+
```
|
|
412
250
|
|
|
413
|
-
|
|
251
|
+
### Run Tests After Any `task` (subagent creation toolcall)
|
|
414
252
|
|
|
415
|
-
|
|
253
|
+
```jsonc
|
|
416
254
|
{
|
|
417
255
|
"tool": [
|
|
418
256
|
{
|
|
419
|
-
"id": "
|
|
420
|
-
"when": {
|
|
421
|
-
"phase": "after",
|
|
422
|
-
"tool": ["task"],
|
|
423
|
-
},
|
|
257
|
+
"id": "tests-after-task",
|
|
258
|
+
"when": { "phase": "after", "tool": "task" },
|
|
424
259
|
"run": ["npm test"],
|
|
425
|
-
"inject": "
|
|
260
|
+
"inject": "Tests (exit {exitCode})\n\n{stdout}\n{stderr}",
|
|
426
261
|
},
|
|
427
262
|
],
|
|
428
263
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
#### Multi-Stage Validation Pipeline
|
|
264
|
+
```
|
|
432
265
|
|
|
433
|
-
|
|
434
|
-
{
|
|
435
|
-
"tool": [
|
|
436
|
-
{
|
|
437
|
-
"id": "validator-security-scan",
|
|
438
|
-
"when": {
|
|
439
|
-
"phase": "after",
|
|
440
|
-
"tool": "task",
|
|
441
|
-
"toolArgs": { "subagent_type": "validator" },
|
|
442
|
-
},
|
|
443
|
-
"run": [
|
|
444
|
-
"npm audit --audit-level=moderate",
|
|
445
|
-
"git diff --name-only | xargs grep -l 'API_KEY\\|SECRET\\|PASSWORD' || true",
|
|
446
|
-
],
|
|
447
|
-
"inject": "🔒 Security Scan:\n\n**Audit:** {exitCode, select, 0 {No vulnerabilities} other {⚠️ Vulnerabilities found}}\n\n```\n{stdout}\n```\n\nPlease address security issues before deployment.",
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
}
|
|
451
|
-
````
|
|
266
|
+
### Enforce Linting After a Specific `write`
|
|
452
267
|
|
|
453
|
-
|
|
268
|
+
Tool-arg matching is exact. This example runs only when the tool arg `path` equals `src/index.ts`.
|
|
454
269
|
|
|
455
270
|
```jsonc
|
|
456
271
|
{
|
|
457
272
|
"tool": [
|
|
458
273
|
{
|
|
459
|
-
"id": "lint-
|
|
274
|
+
"id": "lint-src-index",
|
|
460
275
|
"when": {
|
|
461
276
|
"phase": "after",
|
|
462
|
-
"tool":
|
|
277
|
+
"tool": "write",
|
|
278
|
+
"toolArgs": { "path": "src/index.ts" },
|
|
463
279
|
},
|
|
464
|
-
"run": ["npm run lint
|
|
465
|
-
"inject": "
|
|
280
|
+
"run": ["npm run lint"],
|
|
281
|
+
"inject": "Lint (exit {exitCode})\n\n{stdout}\n{stderr}",
|
|
466
282
|
},
|
|
467
283
|
],
|
|
468
284
|
}
|
|
469
285
|
```
|
|
470
286
|
|
|
471
|
-
###
|
|
472
|
-
|
|
473
|
-
#### Toast Notifications for Build Status
|
|
287
|
+
### Toast Notifications for Build Status
|
|
474
288
|
|
|
475
|
-
|
|
289
|
+
```jsonc
|
|
476
290
|
{
|
|
477
291
|
"tool": [
|
|
478
292
|
{
|
|
479
|
-
"id": "build-
|
|
480
|
-
"when": {
|
|
481
|
-
"phase": "after",
|
|
482
|
-
"tool": ["write"],
|
|
483
|
-
"toolArgs": { "path": "*.ts" },
|
|
484
|
-
},
|
|
293
|
+
"id": "build-toast",
|
|
294
|
+
"when": { "phase": "after", "tool": "write" },
|
|
485
295
|
"run": ["npm run build"],
|
|
486
296
|
"toast": {
|
|
487
|
-
"title": "
|
|
488
|
-
"message": "
|
|
489
|
-
"variant": "
|
|
297
|
+
"title": "Build",
|
|
298
|
+
"message": "exit {exitCode}",
|
|
299
|
+
"variant": "info",
|
|
490
300
|
"duration": 3000,
|
|
491
301
|
},
|
|
492
|
-
"inject": {
|
|
493
|
-
"as": "system",
|
|
494
|
-
"template": "Build output:\n```\n{stdout}\n```",
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
],
|
|
498
|
-
}
|
|
499
|
-
````
|
|
500
|
-
|
|
501
|
-
#### Filter by Multiple Tool Arguments
|
|
502
|
-
|
|
503
|
-
```jsonc
|
|
504
|
-
{
|
|
505
|
-
"tool": [
|
|
506
|
-
// Run for specific file types
|
|
507
|
-
{
|
|
508
|
-
"id": "test-js-files",
|
|
509
|
-
"when": {
|
|
510
|
-
"tool": "write",
|
|
511
|
-
"toolArgs": {
|
|
512
|
-
"path": ["*.js", "*.jsx", "*.ts", "*.tsx"],
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
"run": ["npm test -- --testPathPattern={toolArgs.path}"],
|
|
516
|
-
},
|
|
517
|
-
// Filter by multiple subagent types
|
|
518
|
-
{
|
|
519
|
-
"id": "validate-subagents",
|
|
520
|
-
"when": {
|
|
521
|
-
"tool": "task",
|
|
522
|
-
"toolArgs": {
|
|
523
|
-
"subagent_type": ["validator", "reviewer", "tester"],
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
"run": ["echo 'Validating {toolArgs.subagent_type} subagent'"],
|
|
527
302
|
},
|
|
528
303
|
],
|
|
529
304
|
}
|
|
530
305
|
```
|
|
531
306
|
|
|
532
|
-
|
|
307
|
+
### Handle Async Tool Completion (`tool.result`)
|
|
533
308
|
|
|
534
309
|
```jsonc
|
|
535
310
|
{
|
|
536
311
|
"tool": [
|
|
537
312
|
{
|
|
538
|
-
"id": "task-
|
|
313
|
+
"id": "task-result-hook",
|
|
539
314
|
"when": {
|
|
540
|
-
"
|
|
541
|
-
"tool":
|
|
315
|
+
"phase": "after",
|
|
316
|
+
"tool": "task",
|
|
542
317
|
"toolArgs": { "subagent_type": "code-writer" },
|
|
543
318
|
},
|
|
544
319
|
"run": ["npm run validate-changes"],
|
|
545
|
-
"toast": {
|
|
546
|
-
"title": "Code Writer Complete",
|
|
547
|
-
"message": "Validation: {exitCode, select, 0 {Passed} other {Failed}}",
|
|
548
|
-
"variant": "{exitCode, select, 0 {success} other {warning}}",
|
|
549
|
-
},
|
|
320
|
+
"toast": { "title": "Code Writer", "message": "exit {exitCode}" },
|
|
550
321
|
},
|
|
551
322
|
],
|
|
552
323
|
}
|
|
553
324
|
```
|
|
554
325
|
|
|
555
|
-
|
|
326
|
+
### Session Lifecycle Hooks
|
|
556
327
|
|
|
557
328
|
```jsonc
|
|
558
329
|
{
|
|
@@ -561,11 +332,7 @@ This example demonstrates the plugin's killer feature: **automatic validation in
|
|
|
561
332
|
"id": "session-start",
|
|
562
333
|
"when": { "event": "session.start" },
|
|
563
334
|
"run": ["echo 'New session started'"],
|
|
564
|
-
"toast": {
|
|
565
|
-
"title": "Session Started",
|
|
566
|
-
"message": "Ready to assist!",
|
|
567
|
-
"variant": "info",
|
|
568
|
-
},
|
|
335
|
+
"toast": { "title": "Session", "message": "started", "variant": "info" },
|
|
569
336
|
},
|
|
570
337
|
{
|
|
571
338
|
"id": "session-idle",
|
|
@@ -580,217 +347,95 @@ This example demonstrates the plugin's killer feature: **automatic validation in
|
|
|
580
347
|
|
|
581
348
|
## Template Placeholders
|
|
582
349
|
|
|
583
|
-
All templates support these placeholders:
|
|
584
|
-
|
|
585
|
-
| Placeholder | Description | Example |
|
|
586
|
-
| ------------ | ---------------------------------------- | -------------------------- |
|
|
587
|
-
| `{id}` | Hook ID | `lint-ts` |
|
|
588
|
-
| `{agent}` | Calling agent name | `orchestrator` |
|
|
589
|
-
| `{tool}` | Tool name | `write` |
|
|
590
|
-
| `{cmd}` | Executed command | `npm run lint` |
|
|
591
|
-
| `{stdout}` | Command stdout (truncated to 4000 chars) | `Linting complete` |
|
|
592
|
-
| `{stderr}` | Command stderr (truncated to 4000 chars) | `Error: missing semicolon` |
|
|
593
|
-
| `{exitCode}` | Command exit code | `0` or `1` |
|
|
594
|
-
|
|
595
|
-
**Advanced Usage:**
|
|
596
|
-
|
|
597
|
-
```jsonc
|
|
598
|
-
{
|
|
599
|
-
"inject": {
|
|
600
|
-
"template": "Command '{cmd}' exited with code {exitCode}\n\nStdout:\n{stdout}\n\nStderr:\n{stderr}",
|
|
601
|
-
},
|
|
602
|
-
}
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
---
|
|
606
|
-
|
|
607
|
-
## Debugging
|
|
608
|
-
|
|
609
|
-
Enable detailed debug logging:
|
|
610
|
-
|
|
611
|
-
```bash
|
|
612
|
-
export OPENCODE_HOOKS_DEBUG=1
|
|
613
|
-
opencode start
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
This logs:
|
|
617
|
-
|
|
618
|
-
- Command execution details
|
|
619
|
-
- Template interpolation
|
|
620
|
-
- Hook matching logic
|
|
621
|
-
- Error handling
|
|
622
|
-
|
|
623
|
-
**Example Debug Output:**
|
|
624
|
-
|
|
625
|
-
```
|
|
626
|
-
[DEBUG] Hook matched: lint-ts
|
|
627
|
-
[DEBUG] Executing: npm run lint
|
|
628
|
-
[DEBUG] Template interpolated: Exit code: 0
|
|
629
|
-
[DEBUG] Toast shown: Lint Complete
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
---
|
|
633
|
-
|
|
634
|
-
## Event Types
|
|
350
|
+
All inject/toast string templates support these placeholders:
|
|
635
351
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
- **`session.idle`** - Session became idle
|
|
646
|
-
- **`session.end`** - Session ended
|
|
352
|
+
| Placeholder | Description | Example |
|
|
353
|
+
| ------------ | -------------------------- | -------------------------- |
|
|
354
|
+
| `{id}` | Hook ID | `lint-ts` |
|
|
355
|
+
| `{agent}` | Calling agent name | `orchestrator` |
|
|
356
|
+
| `{tool}` | Tool name | `write` |
|
|
357
|
+
| `{cmd}` | Executed command | `npm run lint` |
|
|
358
|
+
| `{stdout}` | Command stdout (truncated) | `Linting complete` |
|
|
359
|
+
| `{stderr}` | Command stderr (truncated) | `Error: missing semicolon` |
|
|
360
|
+
| `{exitCode}` | Command exit code | `0` or `1` |
|
|
647
361
|
|
|
648
362
|
---
|
|
649
363
|
|
|
650
|
-
##
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
- Can filter by tool name, calling agent, slash command, tool arguments
|
|
656
|
-
- Access to tool-specific context (tool name, call ID, args)
|
|
657
|
-
- **Best for**: Linting after writes, tests after code changes, validation
|
|
658
|
-
|
|
659
|
-
### Session Hooks
|
|
660
|
-
|
|
661
|
-
- Run on session lifecycle events
|
|
662
|
-
- Can only filter by agent name (session events lack tool context)
|
|
663
|
-
- **Best for**: Bootstrapping, cleanup, periodic checks
|
|
664
|
-
|
|
665
|
-
---
|
|
364
|
+
## Why Use This Plugin?
|
|
365
|
+
**It lets you easily set up bash hooks with ~3-5 lines of YAML which are cleanly colocated with your subagent configuration.**
|
|
366
|
+
Conversely, rolling your own looks something like this (for each project and set of hooks you want to set up):
|
|
367
|
+
```ts
|
|
368
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
666
369
|
|
|
667
|
-
|
|
370
|
+
export const MyHooks: Plugin = async ({ $, client }) => {
|
|
371
|
+
const argsCache = new Map();
|
|
668
372
|
|
|
669
|
-
|
|
373
|
+
return {
|
|
374
|
+
"tool.execute.before": async (input, output) => {
|
|
375
|
+
if (input.tool === "task") {
|
|
376
|
+
argsCache.set(input.callID, output.args);
|
|
377
|
+
}
|
|
378
|
+
},
|
|
670
379
|
|
|
671
|
-
|
|
380
|
+
"tool.execute.after": async (input, output) => {
|
|
381
|
+
if (!output && input.tool === "task") return;
|
|
672
382
|
|
|
673
|
-
|
|
674
|
-
|
|
383
|
+
const args = argsCache.get(input.callID);
|
|
384
|
+
argsCache.delete(input.callID);
|
|
675
385
|
|
|
676
|
-
|
|
677
|
-
return {
|
|
678
|
-
"tool.execute.after": async (input) => {
|
|
386
|
+
// Filter by tool and subagent type
|
|
679
387
|
if (input.tool !== "task") return;
|
|
680
|
-
|
|
681
|
-
// No way to filter by toolArgs.subagent_type without complex parsing
|
|
388
|
+
if (!["engineer", "debugger"].includes(args?.subagent_type)) return;
|
|
682
389
|
|
|
683
390
|
try {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
391
|
+
// Run commands sequentially, even if they fail
|
|
392
|
+
let lastResult = { exitCode: 0, stdout: "", stderr: "" };
|
|
393
|
+
|
|
394
|
+
for (const cmd of ["npm run typecheck", "npm run lint"]) {
|
|
395
|
+
try {
|
|
396
|
+
const result = await $`sh -c ${cmd}`.nothrow().quiet();
|
|
397
|
+
const stdout = result.stdout?.toString() || "";
|
|
398
|
+
const stderr = result.stderr?.toString() || "";
|
|
399
|
+
|
|
400
|
+
// Truncate to 30k chars to match OpenCode's bash tool
|
|
401
|
+
lastResult = {
|
|
402
|
+
exitCode: result.exitCode ?? 0,
|
|
403
|
+
stdout: stdout.length > 30000
|
|
404
|
+
? stdout.slice(0, 30000) + "\n[Output truncated: exceeded 30000 character limit]"
|
|
405
|
+
: stdout,
|
|
406
|
+
stderr: stderr.length > 30000
|
|
407
|
+
? stderr.slice(0, 30000) + "\n[Output truncated: exceeded 30000 character limit]"
|
|
408
|
+
: stderr,
|
|
409
|
+
};
|
|
410
|
+
} catch (err) {
|
|
411
|
+
lastResult = { exitCode: 1, stdout: "", stderr: String(err) };
|
|
412
|
+
}
|
|
691
413
|
}
|
|
692
414
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const test = await $`npm test -- --coverage --passWithNoTests`.text();
|
|
702
|
-
results.push(`Tests: ${test}`);
|
|
703
|
-
} catch (e: any) {
|
|
704
|
-
results.push(`Tests failed: ${e.stderr || e.message}`);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const output = results.join("\n\n");
|
|
708
|
-
const exitCode = results.some((r) => r.includes("failed")) ? 1 : 0;
|
|
709
|
-
const message = `🔍 Validation Results:\n\n${output}\n\n${
|
|
710
|
-
exitCode !== 0
|
|
711
|
-
? "⚠️ The code you just wrote has validation errors. Please fix them before proceeding."
|
|
712
|
-
: ""
|
|
713
|
-
}`;
|
|
714
|
-
|
|
715
|
-
await client.session.prompt({
|
|
716
|
-
sessionID: input.sessionID,
|
|
717
|
-
message,
|
|
415
|
+
// Inject results into session (noReply prevents LLM response)
|
|
416
|
+
const message = `Validation (exit ${lastResult.exitCode})\n\n${lastResult.stdout}\n${lastResult.stderr}`;
|
|
417
|
+
await client.session.promptAsync({
|
|
418
|
+
path: { id: input.sessionID },
|
|
419
|
+
body: {
|
|
420
|
+
noReply: true,
|
|
421
|
+
parts: [{ type: "text", text: message }],
|
|
422
|
+
},
|
|
718
423
|
});
|
|
719
424
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
425
|
+
// Show toast notification
|
|
426
|
+
await client.tui.showToast({
|
|
427
|
+
body: {
|
|
428
|
+
title: "Validation",
|
|
429
|
+
message: `exit ${lastResult.exitCode}`,
|
|
430
|
+
variant: "info",
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.error("Hook failed:", err);
|
|
725
435
|
}
|
|
726
436
|
},
|
|
727
437
|
};
|
|
728
438
|
};
|
|
729
439
|
```
|
|
730
440
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
**This Plugin: 15 lines of JSON**
|
|
734
|
-
|
|
735
|
-
````jsonc
|
|
736
|
-
{
|
|
737
|
-
"id": "validate-engineer-work",
|
|
738
|
-
"when": {
|
|
739
|
-
"phase": "after",
|
|
740
|
-
"tool": "task",
|
|
741
|
-
"toolArgs": { "subagent_type": ["engineer", "debugger"] },
|
|
742
|
-
},
|
|
743
|
-
"run": [
|
|
744
|
-
"npm run typecheck",
|
|
745
|
-
"npm run lint",
|
|
746
|
-
"npm test -- --coverage --passWithNoTests",
|
|
747
|
-
],
|
|
748
|
-
"inject": "🔍 Validation Results:\n\n**TypeCheck:** {exitCode, select, 0 {✓ Passed} other {✗ Failed}}\n\n```\n{stdout}\n```\n\n{exitCode, select, 0 {} other {⚠️ Please fix validation errors.}}",
|
|
749
|
-
"toast": {
|
|
750
|
-
"title": "Code Validation",
|
|
751
|
-
"message": "{exitCode, select, 0 {All checks passed ✓} other {Validation failed}}",
|
|
752
|
-
"variant": "{exitCode, select, 0 {success} other {error}}",
|
|
753
|
-
},
|
|
754
|
-
}
|
|
755
|
-
````
|
|
756
|
-
|
|
757
|
-
---
|
|
758
|
-
|
|
759
|
-
### Feature Comparison
|
|
760
|
-
|
|
761
|
-
| Feature | Native Plugin | This Plugin |
|
|
762
|
-
| ------------------------ | --------------------------------------- | ---------------------------- |
|
|
763
|
-
| **Setup** | TypeScript, build steps, error handling | JSON/YAML config |
|
|
764
|
-
| **Error Handling** | Manual try/catch required | Automatic, non-blocking |
|
|
765
|
-
| **User Feedback** | Console logs (UI spam) | Toast notifications |
|
|
766
|
-
| **Context Injection** | Manual SDK calls | Automatic |
|
|
767
|
-
| **Tool Filtering** | Basic tool name only | Tool name + ANY arguments |
|
|
768
|
-
| **Subagent Targeting** | Complex parsing required | Native `toolArgs` filter |
|
|
769
|
-
| **Guaranteed Execution** | Depends on agent | Always runs |
|
|
770
|
-
| **Token Cost** | Variable | Zero tokens |
|
|
771
|
-
| **Hot Reload** | Requires rebuild | Edit config, works instantly |
|
|
772
|
-
| **Debugging** | Console.log | OPENCODE_HOOKS_DEBUG=1 |
|
|
773
|
-
|
|
774
|
-
---
|
|
775
|
-
|
|
776
|
-
## Known Limitations
|
|
777
|
-
|
|
778
|
-
### Session Hooks Cannot Filter by Agent
|
|
779
|
-
|
|
780
|
-
Session lifecycle events (`session.start`, `session.idle`, `session.end`) don't include the calling agent name. You **cannot** use the `agent` filter field in session hook conditions—it matches all agents.
|
|
781
|
-
|
|
782
|
-
**Workaround:** Use `tool.execute.after` events instead, which provide agent context.
|
|
783
|
-
|
|
784
|
-
---
|
|
785
|
-
|
|
786
|
-
## Development
|
|
787
|
-
|
|
788
|
-
```bash
|
|
789
|
-
bun install
|
|
790
|
-
bun run build
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
TODO:
|
|
794
|
-
|
|
795
|
-
- Implement max-length output using tail
|
|
796
|
-
- Add more template functions (date formatting, etc.)
|
|
441
|
+
The plugin handles: sequential execution with failure recovery, output truncation, exit code capture, template interpolation, session injection, toast notifications, toolArgs filtering, async tool support, and non-blocking error handling.
|