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