Extension Hooks Reference¶
Extension hooks allow plugins to intercept and modify core application behaviors. This is the Level 3 plugin capability that enables deep customization without forking the codebase.
Code Self-Healing Hooks¶
The code self-healing system emits events at key points during error recovery, allowing plugins to:
- Prevent or modify self-healing behavior
- Add custom error analysis
- Provide alternative fixing strategies
- Enhance error messages with context
selfheal:before¶
Emitted before code self-healing begins.
Event data:
{
nodeId: string, // Code node being healed
code: string, // Current code content
attemptNum: number, // Current attempt (1-based)
maxAttempts: number, // Maximum attempts allowed
originalPrompt: string, // User's original prompt
model: string, // LLM model in use
context: Object, // Code generation context
}
Cancellable: Yes. Call event.preventDefault() to skip self-healing.
Use cases:
- Skip self-healing for specific error types
- Add pre-execution validation
- Log self-healing attempts for analytics
Example:
getEventSubscriptions() {
return {
'selfheal:before': (event) => {
const { code, attemptNum } = event.data;
// Don't retry if code has syntax errors
if (code.includes('SyntaxError') && attemptNum > 1) {
event.preventDefault();
console.log('Skipping self-heal: syntax error');
}
},
};
}
selfheal:error¶
Emitted when code execution fails during self-healing.
Event data:
{
nodeId: string, // Code node with error
code: string, // Failed code
error: string, // Error message
attemptNum: number, // Current attempt
maxAttempts: number, // Maximum attempts
originalPrompt: string, // User's original prompt
model: string, // LLM model in use
context: Object, // Code generation context
}
Cancellable: No. Error has already occurred.
Use cases:
- Analyze error patterns
- Add context to error messages
- Track failure metrics
- Send alerts for critical errors
Example:
getEventSubscriptions() {
return {
'selfheal:error': (event) => {
const { error, attemptNum } = event.data;
// Log common errors for analysis
if (error.includes('ImportError')) {
console.log(`Import error on attempt ${attemptNum}:`, error);
// Could send to analytics service
}
},
};
}
selfheal:fix¶
Emitted before LLM is asked to fix code error.
Event data:
{
nodeId: string, // Code node being fixed
failedCode: string, // Code that failed
errorMessage: string, // Error from execution
originalPrompt: string, // User's original prompt
model: string, // LLM model in use
context: Object, // Code generation context
attemptNum: number, // Current attempt
maxAttempts: number, // Maximum attempts
fixPrompt: string, // Prompt that will be sent to LLM
}
Cancellable: Yes. Call event.preventDefault() and set event.data.customFixPrompt to replace the fix strategy.
Use cases:
- Customize fix prompts for specific error types
- Add domain-specific context to fixes
- Implement custom fixing strategies (e.g., use different model)
- Skip LLM call and apply manual fixes
Example:
getEventSubscriptions() {
return {
'selfheal:fix': (event) => {
const { errorMessage, failedCode } = event.data;
// Custom handling for import errors
if (errorMessage.includes('ImportError: No module named')) {
event.preventDefault();
// Extract missing module name
const match = errorMessage.match(/No module named '(\w+)'/);
if (match) {
const module = match[1];
// Provide custom fix prompt with package installation
event.data.customFixPrompt = `
The code is missing the package "${module}".
Failed code:
\`\`\`python
${failedCode}
\`\`\`
Please fix by:
1. Adding a pip install cell: # %pip install ${module}
2. Then the original code
Output ONLY the corrected Python code.`;
}
}
},
};
}
selfheal:success¶
Emitted when self-healing successfully fixes and executes code.
Event data:
{
nodeId: string, // Code node that succeeded
code: string, // Working code
attemptNum: number, // Successful attempt number
originalPrompt: string, // User's original prompt
model: string, // LLM model used
result: Object, // Execution result (stdout, html, etc.)
}
Cancellable: No. Success has already occurred.
Use cases:
- Track success rates by error type
- Log successful fixes for learning
- Auto-commit working code
- Show success notifications
Example:
getEventSubscriptions() {
return {
'selfheal:success': (event) => {
const { attemptNum, code } = event.data;
if (attemptNum > 1) {
console.log(`✅ Self-healing succeeded after ${attemptNum} attempts`);
// Could save successful fix pattern for future reference
}
},
};
}
selfheal:failed¶
Emitted when self-healing exhausts all retry attempts.
Event data:
{
nodeId: string, // Code node that failed
code: string, // Last attempted code
error: string, // Final error message
attemptNum: number, // Final attempt number (equals maxAttempts)
maxAttempts: number, // Maximum attempts allowed
originalPrompt: string, // User's original prompt
model: string, // LLM model used
}
Cancellable: No. All attempts have been exhausted.
Use cases:
- Show detailed error reports
- Suggest alternative approaches
- Escalate to human review
- Track permanently failing patterns
Example:
getEventSubscriptions() {
return {
'selfheal:failed': (event) => {
const { error, attemptNum } = event.data;
console.log(`🛑 Self-healing failed after ${attemptNum} attempts`);
console.log('Final error:', error);
// Could show user-friendly explanation
this.showToast?.('Code generation failed. Try rephrasing your prompt.', 'error');
},
};
}
Event Emission Conventions¶
Cancellable Events¶
Events with preventDefault() support use the CancellableEvent class:
import { CancellableEvent } from './plugin-events.js';
const event = new CancellableEvent('selfheal:before', { nodeId, code, ... });
await this.emitter.emit('selfheal:before', event);
if (event.defaultPrevented) {
// Plugin cancelled the operation
return;
}
Data Mutation¶
Plugins can modify event data to change behavior:
// In plugin:
getEventSubscriptions() {
return {
'selfheal:fix': (event) => {
// Replace the fix prompt
event.data.customFixPrompt = 'Custom prompt...';
event.preventDefault(); // Use custom prompt instead
},
};
}
// In app code:
const event = new CancellableEvent('selfheal:fix', { fixPrompt, ... });
await this.emitter.emit('selfheal:fix', event);
if (event.defaultPrevented && event.data.customFixPrompt) {
// Use plugin's custom prompt
fixPrompt = event.data.customFixPrompt;
}
Hook Ordering¶
When multiple plugins subscribe to the same hook:
- Plugins execute in registration order (first registered, first executed)
- Priority only affects slash command conflicts, not event order
- First plugin to call
preventDefault()wins - Later plugins still execute but can't un-prevent
Note: Unlike slash commands (where higher priority wins), event subscribers are called in the order they were registered. If you need specific ordering, register plugins in that order.
Best Practices¶
Do¶
- ✅ Check specific conditions before preventing default
- ✅ Add console logs for debugging
- ✅ Preserve original data when possible
- ✅ Document why you're preventing default
Don't¶
- ❌ Prevent default without checking conditions (breaks everything)
- ❌ Modify event data without preventing default (confusing behavior)
- ❌ Throw exceptions in event handlers (breaks event chain)
- ❌ Perform long-running operations synchronously (blocks UI)
Example: Well-behaved extension¶
class SmartFixPlugin extends FeaturePlugin {
async onLoad() {
console.log('[SmartFixPlugin] Loaded');
}
getEventSubscriptions() {
return {
'selfheal:fix': this.onFix.bind(this),
};
}
onFix(event) {
const { errorMessage, failedCode } = event.data;
// Only intervene for specific cases
if (!errorMessage.includes('ImportError')) {
return; // Let default behavior handle it
}
console.log('[SmartFixPlugin] Handling ImportError');
// Provide custom fix strategy
event.data.customFixPrompt = this.buildImportFixPrompt(failedCode, errorMessage);
event.preventDefault();
console.log('[SmartFixPlugin] Custom fix prompt applied');
}
buildImportFixPrompt(code, error) {
// Implementation...
}
}
Testing Extension Hooks¶
Use PluginTestHarness to test extension behavior:
import { PluginTestHarness } from './plugin-test-harness.js';
import { CancellableEvent } from './plugin-events.js';
const harness = new PluginTestHarness();
await harness.loadPlugin({
id: 'my-extension',
feature: MyExtensionPlugin,
priority: PRIORITY.COMMUNITY,
});
// Simulate event emission
const event = new CancellableEvent('selfheal:fix', {
errorMessage: 'ImportError: No module named "pandas"',
failedCode: 'import pandas as pd',
});
await harness.emitEvent('selfheal:fix', event);
// Verify behavior
assertTrue(event.defaultPrevented, 'Should prevent default for ImportError');
assertTrue(event.data.customFixPrompt !== undefined, 'Should provide custom prompt');
Migration Guide¶
Adding Hooks to Existing Features¶
- Identify hook points - Where should plugins intervene?
- Import event classes -
import { CancellableEvent } from './plugin-events.js' - Emit events -
await this.featureRegistry.emitEvent('hook:name', event) - Handle preventDefault - Check
event.defaultPreventedafter emission - Document - Add hook to this reference doc
- Test - Write tests for hook behavior
Example: Adding a new hook¶
// 1. Before performing operation
const event = new CancellableEvent('matrix:before-fill', {
matrixId,
cellId,
prompt,
});
// 2. Emit to plugins
await this.featureRegistry.emitEvent('matrix:before-fill', event);
// 3. Check if prevented
if (event.defaultPrevented) {
console.log('Matrix fill prevented by plugin');
return;
}
// 4. Continue with operation
await this.fillMatrixCell(matrixId, cellId, prompt);