@aiderdesk/aiderdesk 0.61.0 → 0.61.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/out/resources/skills/extension-creator/SKILL.md +320 -0
- package/out/resources/skills/extension-creator/assets/templates/Component.jsx.template +76 -0
- package/out/resources/skills/extension-creator/assets/templates/ConfigComponent.jsx.template +38 -0
- package/out/resources/skills/extension-creator/assets/templates/folder-extension/config.ts.template +29 -0
- package/out/resources/skills/extension-creator/assets/templates/folder-extension/index.ts.template +42 -0
- package/out/resources/skills/extension-creator/assets/templates/folder-extension/package.json +12 -0
- package/out/resources/skills/extension-creator/assets/templates/folder-extension/tsconfig.json +23 -0
- package/out/resources/skills/extension-creator/assets/templates/folder-extension-with-config/index.ts.template +80 -0
- package/out/resources/skills/extension-creator/assets/templates/single-file.ts.template +30 -0
- package/out/resources/skills/extension-creator/assets/templates/ui-component-external.ts.template +91 -0
- package/out/resources/skills/extension-creator/assets/templates/ui-component.ts.template +79 -0
- package/out/resources/skills/extension-creator/references/command-definition.md +170 -0
- package/out/resources/skills/extension-creator/references/config-components.md +427 -0
- package/out/resources/skills/extension-creator/references/event-types.md +156 -0
- package/out/resources/skills/extension-creator/references/examples-gallery.md +583 -0
- package/out/resources/skills/extension-creator/references/extension-interface.md +240 -0
- package/out/resources/skills/extension-creator/references/extension-types.md +186 -0
- package/out/resources/skills/extension-creator/references/in-repo-flow.md +132 -0
- package/out/resources/skills/extension-creator/references/install-targets.md +96 -0
- package/out/resources/skills/extension-creator/references/project-global-flow.md +76 -0
- package/out/resources/skills/extension-creator/references/ui-components.md +663 -0
- package/package.json +1 -1
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Config Components Reference
|
|
2
|
+
|
|
3
|
+
Config components provide a **per-extension settings UI** that appears in the Extension Settings dialog when users click the gear icon on an extension card. Unlike placement-based UI components (which render inside task pages), config components render in a dedicated settings modal with Save/Cancel buttons.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
User clicks extension gear icon
|
|
9
|
+
→ ExtensionSettingsDialog opens
|
|
10
|
+
→ Calls api.getExtensionConfigComponent(extensionId)
|
|
11
|
+
→ Extension.getConfigComponent(context) returns JSX string
|
|
12
|
+
→ Calls api.getExtensionConfig(extensionId)
|
|
13
|
+
→ Extension.getConfigData(context) returns current config data
|
|
14
|
+
→ Renders JSX with { config, updateConfig } props
|
|
15
|
+
→ User edits values, clicks Save
|
|
16
|
+
→ Calls api.saveExtensionConfig(extensionId, config)
|
|
17
|
+
→ Extension.saveConfigData(configData, context) persists data
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## The Three Methods
|
|
21
|
+
|
|
22
|
+
### `getConfigComponent(context)` — Return Settings JSX
|
|
23
|
+
|
|
24
|
+
Returns a JSX string for the settings UI. The component receives `{ config, updateConfig }` as props:
|
|
25
|
+
|
|
26
|
+
- **`config`** — The current config data loaded from `getConfigData()`
|
|
27
|
+
- **`updateConfig`** — Callback to mutate the in-memory config state
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
getConfigComponent(_context: ExtensionContext): string | undefined {
|
|
31
|
+
// Option A: Inline JSX string
|
|
32
|
+
return `({ config, updateConfig }) => { ... }`;
|
|
33
|
+
|
|
34
|
+
// Option B: Load from external .jsx file (recommended for > 20 lines)
|
|
35
|
+
return readFileSync(join(__dirname, './ConfigComponent.jsx'), 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Return `undefined` if your extension has no settings UI.
|
|
40
|
+
|
|
41
|
+
### `getConfigData(context)` — Load Current Config
|
|
42
|
+
|
|
43
|
+
Called when the settings dialog opens. Returns the persisted configuration data:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
async getConfigData(_context: ExtensionContext): Promise<unknown> {
|
|
47
|
+
try {
|
|
48
|
+
if (existsSync(this.configPath)) {
|
|
49
|
+
const data = readFileSync(this.configPath, 'utf-8');
|
|
50
|
+
const parsed = JSON.parse(data);
|
|
51
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Ignore errors, fall back to defaults
|
|
55
|
+
}
|
|
56
|
+
return { ...DEFAULT_CONFIG };
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `saveConfigData(configData, context)` — Persist Config
|
|
61
|
+
|
|
62
|
+
Called when user clicks **Save** in the dialog:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
async saveConfigData(configData: unknown, _context: ExtensionContext): Promise<unknown> {
|
|
66
|
+
const merged = { ...DEFAULT_CONFIG, ...configData as Partial<MyConfig> };
|
|
67
|
+
writeFileSync(this.configPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Prefer ui Components Over Raw HTML
|
|
73
|
+
|
|
74
|
+
Always use the `ui` prop components (`ui.Input`, `ui.TextArea`, `ui.Select`, `ui.Checkbox`, etc.) instead of raw HTML `<input>`, `<textarea>`, `<select>` elements. This ensures consistent styling, accessibility, and theming across the application.
|
|
75
|
+
|
|
76
|
+
**Do this:**
|
|
77
|
+
```jsx
|
|
78
|
+
({ config, updateConfig, ui }) => {
|
|
79
|
+
const { Input } = ui;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Input
|
|
83
|
+
label="My Setting"
|
|
84
|
+
value={config?.mySetting || ''}
|
|
85
|
+
onChange={(value) => updateConfig({ ...config, mySetting: value })}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Not this:**
|
|
92
|
+
```jsx
|
|
93
|
+
// Avoid raw HTML — inconsistent styling, no theme support
|
|
94
|
+
<input
|
|
95
|
+
type="text"
|
|
96
|
+
value={config?.mySetting || ''}
|
|
97
|
+
onChange={(e) => updateConfig({ ...config, mySetting: e.target.value })}
|
|
98
|
+
className="w-full px-3 py-2 ..."
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `ui` components handle their own labels, styling, focus states, and dark mode automatically.
|
|
103
|
+
|
|
104
|
+
## When Inner State Is (and Isn't) Needed
|
|
105
|
+
|
|
106
|
+
Config components receive `config` that is kept in sync by the parent dialog. **You do NOT need local state for simple form fields.**
|
|
107
|
+
|
|
108
|
+
### No inner state needed (most cases)
|
|
109
|
+
|
|
110
|
+
When your component just reads from `config` and calls `updateConfig` on change — use `config` directly:
|
|
111
|
+
|
|
112
|
+
```jsx
|
|
113
|
+
({ config, updateConfig, ui }) => {
|
|
114
|
+
const { Input } = ui;
|
|
115
|
+
return <Input label="Folders" value={config?.folders || ''} onChange={(v) => updateConfig({ ...config, folders: v })} />;
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This works because `updateConfig` triggers a re-render with the updated `config` prop, so the input always shows the current value.
|
|
120
|
+
|
|
121
|
+
### Inner state IS needed
|
|
122
|
+
|
|
123
|
+
Only introduce local state when you need behavior that goes beyond simple read/write:
|
|
124
|
+
|
|
125
|
+
- **Derived/computed values**: Displaying a transformed version of config (e.g., splitting a comma string into tags)
|
|
126
|
+
- **Transient UI state**: Expand/collapse sections, active tabs, modal visibility
|
|
127
|
+
- **Debounced input**: Delaying updates until user stops typing
|
|
128
|
+
- **Validation state**: Showing per-field error messages
|
|
129
|
+
|
|
130
|
+
```jsx
|
|
131
|
+
({ config, updateConfig, ui }) => {
|
|
132
|
+
const { useState } = React;
|
|
133
|
+
const { Input } = ui;
|
|
134
|
+
|
|
135
|
+
// Only needed because we transform config into tag-like items for editing
|
|
136
|
+
const [tags, setTags] = useState((config?.folders || '').split(',').filter(Boolean));
|
|
137
|
+
|
|
138
|
+
// ...
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Config Component Props
|
|
143
|
+
|
|
144
|
+
The JSX component receives these props:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
{
|
|
148
|
+
// Current config data from getConfigData()
|
|
149
|
+
config: unknown,
|
|
150
|
+
|
|
151
|
+
// Callback to update in-memory config (does NOT persist!)
|
|
152
|
+
updateConfig: (newConfig: unknown) => void,
|
|
153
|
+
|
|
154
|
+
// All standard extension props (from useExtensions())
|
|
155
|
+
ui: UIComponents,
|
|
156
|
+
icons: Record<string, unknown>,
|
|
157
|
+
models: Model[],
|
|
158
|
+
providers: ProviderProfile[],
|
|
159
|
+
projectDir?: string,
|
|
160
|
+
task?: TaskData,
|
|
161
|
+
agentProfile?: AgentProfile,
|
|
162
|
+
mode: string,
|
|
163
|
+
api: ApplicationAPI,
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Important:** `updateConfig` only updates the local React state. Persistence happens only when the user clicks **Save**, which triggers `saveConfigData()`.
|
|
168
|
+
|
|
169
|
+
## Config Component JSX Format
|
|
170
|
+
|
|
171
|
+
Config components follow the same format as all other extension JSX files:
|
|
172
|
+
|
|
173
|
+
- Pure arrow function with destructured props
|
|
174
|
+
- React hooks accessed via `React.useState`, `React.useEffect`, etc.
|
|
175
|
+
- No imports — everything is available via props or globals
|
|
176
|
+
- Use Tailwind CSS for styling
|
|
177
|
+
|
|
178
|
+
### Example: Text Input (no inner state, using ui.Input)
|
|
179
|
+
|
|
180
|
+
```jsx
|
|
181
|
+
({ config, updateConfig, ui }) => {
|
|
182
|
+
const { Input } = ui;
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div className="flex flex-col gap-4">
|
|
186
|
+
<Input
|
|
187
|
+
label="My Setting"
|
|
188
|
+
value={config?.mySetting || ''}
|
|
189
|
+
onChange={(value) => updateConfig({ ...config, mySetting: value })}
|
|
190
|
+
placeholder="Enter value..."
|
|
191
|
+
/>
|
|
192
|
+
<p className="text-xs text-text-secondary -mt-2">
|
|
193
|
+
Description of what this setting does.
|
|
194
|
+
</p>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Example: Checkbox Toggle (no inner state)
|
|
201
|
+
|
|
202
|
+
```jsx
|
|
203
|
+
({ config, updateConfig, ui }) => {
|
|
204
|
+
const { Checkbox } = ui;
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<Checkbox
|
|
208
|
+
label="Enable feature"
|
|
209
|
+
checked={config?.enabled ?? false}
|
|
210
|
+
onChange={(checked) => updateConfig({ ...config, enabled: checked })}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Example: Multiple Fields (no inner state)
|
|
217
|
+
|
|
218
|
+
```jsx
|
|
219
|
+
({ config, updateConfig, ui }) => {
|
|
220
|
+
const { Input, Checkbox } = ui;
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div className="flex flex-col gap-4">
|
|
224
|
+
<Input
|
|
225
|
+
label="Rule Folders"
|
|
226
|
+
value={config?.ruleFolders || ''}
|
|
227
|
+
onChange={(value) => updateConfig({ ...config, ruleFolders: value })}
|
|
228
|
+
placeholder=".custom-rules, .ai/rules"
|
|
229
|
+
/>
|
|
230
|
+
<Input
|
|
231
|
+
label="Max Depth"
|
|
232
|
+
type="number"
|
|
233
|
+
value={String(config?.maxDepth ?? 3)}
|
|
234
|
+
onChange={(value) => updateConfig({ ...config, maxDepth: Number(value) })}
|
|
235
|
+
/>
|
|
236
|
+
<Checkbox
|
|
237
|
+
label="Include hidden directories"
|
|
238
|
+
checked={config?.includeHidden ?? false}
|
|
239
|
+
onChange={(checked) => updateConfig({ ...config, includeHidden: checked })}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Example: With Inner State (derived/transient UI only)
|
|
247
|
+
|
|
248
|
+
Only use local state when you need behavior beyond simple read/write — such as derived display values or transient UI state:
|
|
249
|
+
|
|
250
|
+
```jsx
|
|
251
|
+
({ config, updateConfig, ui }) => {
|
|
252
|
+
const { useState, useMemo } = React;
|
|
253
|
+
const { Input } = ui;
|
|
254
|
+
|
|
255
|
+
// Inner state needed: splitting comma string into editable tag-like items
|
|
256
|
+
const rawValue = config?.folders || '';
|
|
257
|
+
const tags = useMemo(() => rawValue.split(',').map(t => t.trim()).filter(Boolean), [rawValue]);
|
|
258
|
+
const [activeTag, setActiveTag] = useState(null);
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div className="flex flex-col gap-4">
|
|
262
|
+
<Input
|
|
263
|
+
label="Folders (comma-separated)"
|
|
264
|
+
value={rawValue}
|
|
265
|
+
onChange={(value) => updateConfig({ ...config, folders: value })}
|
|
266
|
+
/>
|
|
267
|
+
{/* Tag preview — derived from config, uses inner state for UI interaction */}
|
|
268
|
+
<div className="flex flex-wrap gap-1">
|
|
269
|
+
{tags.map((tag) => (
|
|
270
|
+
<span
|
|
271
|
+
key={tag}
|
|
272
|
+
onClick={() => setActiveTag(tag === activeTag ? null : tag)}
|
|
273
|
+
className={`px-2 py-0.5 rounded text-xs cursor-pointer ${
|
|
274
|
+
tag === activeTag ? 'bg-accent text-white' : 'bg-bg-secondary'
|
|
275
|
+
}`}
|
|
276
|
+
>
|
|
277
|
+
{tag}
|
|
278
|
+
</span>
|
|
279
|
+
))}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## File Structure Pattern
|
|
287
|
+
|
|
288
|
+
Recommended structure for extensions with config components:
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
my-extension/
|
|
292
|
+
├── index.ts # Main extension (loads JSX, implements 3 methods)
|
|
293
|
+
├── ConfigComponent.jsx # Settings UI (pure arrow function)
|
|
294
|
+
└── config.json # Default/persisted config file
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### index.ts Pattern
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
301
|
+
import { join } from 'node:path';
|
|
302
|
+
import type { Extension, ExtensionContext } from '@aiderdesk/extensions';
|
|
303
|
+
|
|
304
|
+
// Load config JSX at module level (read once)
|
|
305
|
+
const configComponentJsx = readFileSync(join(__dirname, './ConfigComponent.jsx'), 'utf-8');
|
|
306
|
+
|
|
307
|
+
interface MyExtensionConfig {
|
|
308
|
+
ruleFolders: string;
|
|
309
|
+
enabled: boolean;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const DEFAULT_CONFIG: MyExtensionConfig = {
|
|
313
|
+
ruleFolders: '',
|
|
314
|
+
enabled: true,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export default class MyExtension implements Extension {
|
|
318
|
+
static metadata = {
|
|
319
|
+
name: 'My Extension',
|
|
320
|
+
version: '1.0.0',
|
|
321
|
+
description: 'Description here',
|
|
322
|
+
author: 'author-name',
|
|
323
|
+
capabilities: ['context'],
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
private configPath: string;
|
|
327
|
+
|
|
328
|
+
constructor() {
|
|
329
|
+
this.configPath = join(__dirname, 'config.json');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async onLoad(context: ExtensionContext): Promise<void> {
|
|
333
|
+
context.log('My Extension loaded', 'info');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
getConfigComponent(_context: ExtensionContext): string {
|
|
337
|
+
return configComponentJsx;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async getConfigData(_context: ExtensionContext): Promise<MyExtensionConfig> {
|
|
341
|
+
try {
|
|
342
|
+
if (existsSync(this.configPath)) {
|
|
343
|
+
const data = readFileSync(this.configPath, 'utf-8');
|
|
344
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
// Ignore errors
|
|
348
|
+
}
|
|
349
|
+
return { ...DEFAULT_CONFIG };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async saveConfigData(configData: unknown, _context: ExtensionContext): Promise<unknown> {
|
|
353
|
+
const merged: MyExtensionConfig = { ...DEFAULT_CONFIG, ...configData as Partial<MyExtensionConfig> };
|
|
354
|
+
writeFileSync(this.configPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
355
|
+
return merged;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### config.json (default)
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"ruleFolders": "",
|
|
365
|
+
"enabled": true
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## How hasConfig is Determined
|
|
370
|
+
|
|
371
|
+
The `InstalledExtension.metadata.hasConfig` flag is set by checking whether `instance.getConfigComponent` exists and returns a truthy (non-empty) JSX string. This means:
|
|
372
|
+
|
|
373
|
+
- If you implement `getConfigComponent` and return a non-empty string → `hasConfig: true` → gear icon appears on the extension card
|
|
374
|
+
- If you don't implement it, or return `undefined`/empty string → `hasConfig: false` → no settings entry point
|
|
375
|
+
|
|
376
|
+
## Differences from Placement-Based UI Components
|
|
377
|
+
|
|
378
|
+
| Aspect | Config Components | Placement UI Components |
|
|
379
|
+
|--------|-------------------|------------------------|
|
|
380
|
+
| **Where they render** | ExtensionSettingsDialog modal | Task page placements |
|
|
381
|
+
| **Registration method** | `getConfigComponent()` | `getUIComponents()` |
|
|
382
|
+
| **Props received** | `{ config, updateConfig, ui, icons, ... }` | `{ data, task, ui, icons, ... }` |
|
|
383
|
+
| **Data loading** | `getConfigData()` always called | `getUIExtensionData()` only if `loadData: true` |
|
|
384
|
+
| **Persistence** | `saveConfigData()` on Save click | Via `executeExtensionAction()` |
|
|
385
|
+
| **Placement enum** | Not applicable | `UIComponentPlacement` enum |
|
|
386
|
+
| **One per extension** | Yes (single) | No (multiple allowed) |
|
|
387
|
+
|
|
388
|
+
## Best Practices
|
|
389
|
+
|
|
390
|
+
### Prefer ui.* components over raw HTML elements
|
|
391
|
+
|
|
392
|
+
Always use components from the `ui` prop (`Input`, `TextArea`, `Select`, `Checkbox`, etc.) instead of raw `<input>`, `<textarea>`, or `<select>` elements. The ui components handle labels, styling, focus states, dark mode, and accessibility automatically.
|
|
393
|
+
|
|
394
|
+
### Avoid unnecessary inner state
|
|
395
|
+
|
|
396
|
+
For simple form fields, read directly from `config` and call `updateConfig` on change — no `useState`/`useEffect` needed. The parent re-renders with updated `config` after each `updateConfig` call. Only introduce local state for derived values, transient UI state (tabs, expand/collapse), debounced input, or validation.
|
|
397
|
+
|
|
398
|
+
### Call updateConfig on every change
|
|
399
|
+
|
|
400
|
+
Don't wait for blur/submit — call `updateConfig` immediately so the Save button persists the latest value:
|
|
401
|
+
|
|
402
|
+
```jsx
|
|
403
|
+
onChange={(e) => {
|
|
404
|
+
setValue(e.target.value);
|
|
405
|
+
updateConfig({ ...config, myValue: e.target.value });
|
|
406
|
+
}}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Merge with defaults in both load and save
|
|
410
|
+
|
|
411
|
+
Always merge incoming data with defaults to handle missing fields gracefully:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// In getConfigData
|
|
415
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
416
|
+
|
|
417
|
+
// In saveConfigData
|
|
418
|
+
const merged = { ...DEFAULT_CONFIG, ...configData };
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Store config in the extension directory
|
|
422
|
+
|
|
423
|
+
Use `join(__dirname, 'config.json')` so config lives alongside the extension code. Never write outside the extension directory.
|
|
424
|
+
|
|
425
|
+
### Keep config serializable
|
|
426
|
+
|
|
427
|
+
Only use primitive types (string, number, boolean, arrays, plain objects) that can be safely JSON-stringified.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Event Types
|
|
2
|
+
|
|
3
|
+
All event types and their payloads for extension event handlers.
|
|
4
|
+
|
|
5
|
+
## Event Modification Modes
|
|
6
|
+
|
|
7
|
+
| Mode | Events | Can Block | Can Modify |
|
|
8
|
+
|------|--------|-----------|------------|
|
|
9
|
+
| Read-Only | onTaskCreated, onTaskClosed, onFileAdded, etc. | No | No |
|
|
10
|
+
| Blocking | onToolApproval, onToolCalled, onPromptSubmitted, onHandleApproval, onSubagentStarted | Yes | Some |
|
|
11
|
+
| Modifying | onToolFinished, onResponseMessageProcessed, onAgentStarted | No | Yes |
|
|
12
|
+
|
|
13
|
+
## Agent Events
|
|
14
|
+
|
|
15
|
+
### AgentStartedEvent (Modifying)
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
interface AgentStartedEvent {
|
|
19
|
+
readonly mode: Mode;
|
|
20
|
+
prompt: string | null;
|
|
21
|
+
agentProfile: AgentProfile;
|
|
22
|
+
providerProfile: ProviderProfile;
|
|
23
|
+
model: string;
|
|
24
|
+
promptContext?: PromptContext;
|
|
25
|
+
systemPrompt: string | undefined;
|
|
26
|
+
contextMessages: ContextMessage[];
|
|
27
|
+
contextFiles: ContextFile[];
|
|
28
|
+
blocked?: boolean;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### AgentFinishedEvent
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
interface AgentFinishedEvent {
|
|
36
|
+
readonly mode: Mode;
|
|
37
|
+
readonly agentProfile: AgentProfile;
|
|
38
|
+
resultMessages: ContextMessage[];
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### AgentStepFinishedEvent (Modifying)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
interface AgentStepFinishedEvent {
|
|
46
|
+
readonly mode: Mode;
|
|
47
|
+
readonly agentProfile: AgentProfile;
|
|
48
|
+
readonly currentResponseId: string;
|
|
49
|
+
readonly stepResult: AgentStepResult;
|
|
50
|
+
finishReason: 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown';
|
|
51
|
+
responseMessages: ContextMessage[];
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Tool Events
|
|
56
|
+
|
|
57
|
+
### ToolCalledEvent (Blocking)
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface ToolCalledEvent {
|
|
61
|
+
readonly tool: string;
|
|
62
|
+
readonly toolInput: Record<string, unknown>;
|
|
63
|
+
readonly agentProfile: AgentProfile;
|
|
64
|
+
blocked?: boolean;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### ToolFinishedEvent (Modifying)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface ToolFinishedEvent {
|
|
72
|
+
readonly tool: string;
|
|
73
|
+
readonly toolInput: Record<string, unknown>;
|
|
74
|
+
readonly agentProfile: AgentProfile;
|
|
75
|
+
toolResult: unknown;
|
|
76
|
+
metadata?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Prompt Events
|
|
81
|
+
|
|
82
|
+
### PromptSubmittedEvent (Blocking/Modifying)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface PromptSubmittedEvent {
|
|
86
|
+
prompt: string;
|
|
87
|
+
mode: Mode;
|
|
88
|
+
promptContext: PromptContext;
|
|
89
|
+
blocked?: boolean;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## File Events
|
|
94
|
+
|
|
95
|
+
### FileAddedEvent
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
interface FileAddedEvent {
|
|
99
|
+
files: string[];
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Subagent Events
|
|
104
|
+
|
|
105
|
+
### SubagentStartedEvent (Blocking/Modifying)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
interface SubagentStartedEvent {
|
|
109
|
+
subagentProfile: AgentProfile;
|
|
110
|
+
prompt: string;
|
|
111
|
+
promptContext?: PromptContext;
|
|
112
|
+
contextMessages: ContextMessage[];
|
|
113
|
+
contextFiles: ContextFile[];
|
|
114
|
+
systemPrompt?: string;
|
|
115
|
+
blocked?: boolean;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Common Patterns
|
|
120
|
+
|
|
121
|
+
### Blocking Execution
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
async onToolCalled(event: ToolCalledEvent): Promise<Partial<ToolCalledEvent>> {
|
|
125
|
+
if (event.tool === 'power---bash' && isDangerous(event.toolInput)) {
|
|
126
|
+
return { blocked: true };
|
|
127
|
+
}
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Modifying Messages
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
async onAgentStarted(event: AgentStartedEvent): Promise<Partial<AgentStartedEvent>> {
|
|
136
|
+
return {
|
|
137
|
+
contextMessages: [
|
|
138
|
+
{ id: 'custom', role: 'user', content: 'Custom instruction' },
|
|
139
|
+
...event.contextMessages
|
|
140
|
+
]
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Modifying Profile
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
async onAgentStarted(event: AgentStartedEvent): Promise<Partial<AgentStartedEvent>> {
|
|
149
|
+
return {
|
|
150
|
+
agentProfile: {
|
|
151
|
+
...event.agentProfile,
|
|
152
|
+
includeRepoMap: false
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
```
|