Native Extension Patterns for Pi and OMP in i-have-adhd
The i-have-adhd repository implements a runtime-agnostic native extension for both Pi and Oh-My-Pi (OMP) by declaring dual runtime targets in package.json, exporting a default function that receives the ExtensionAPI, and using a compatibility layer to normalize session-manager API differences between the two environments.
The i-have-adhd project demonstrates how to build cross-platform AI coding agent extensions that inject specialized conversation rules and UI controls. By leveraging a single TypeScript module with runtime-specific declarations and a thin abstraction shim, the extension operates identically on Pi and OMP without requiring platform-specific branches.
Declaring Dual Runtime Support in package.json
The native extension pattern begins with explicit runtime registration in package.json. The extension declares which file each runtime should load by defining separate pi and omp configuration keys that both reference the same entry point.
In package.json (lines 9-14), the extensions array under both runtime configurations points to ./extensions/i-have-adhd.ts:
{
"pi": {
"extensions": ["./extensions/i-have-adhd.ts"]
},
"omp": {
"extensions": ["./extensions/i-have-adhd.ts"]
}
}
This dual declaration instructs both Pi and OMP to load the identical TypeScript module as a native extension, establishing the foundation for the runtime-agnostic architecture.
The Extension Entry Point Pattern
The core implementation resides in extensions/i-have-adhd.ts, which exports a default function receiving the ExtensionAPI object. This function implements six key responsibilities that work uniformly across both runtimes:
- Registers the boolean flag (
adhd) for feature toggling - Registers the slash-command (
/i-have-adhd) for user interaction - Persists state via the session-manager entry
i-have-adhd-state - Injects the rule set loaded from
skills/i-have-adhd/SKILL.mdas a custom message - Updates the UI status line to display "ADHD ON"
- Listens to lifecycle events (
input,session_start,session_tree,session_compact) to maintain synchronization
The default export follows this signature structure:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function iHaveAdhdExtension(pi: ExtensionAPI) {
// Register persistent flag
pi.registerFlag("adhd", {
description: "Enable ADHD assistance mode",
type: "boolean",
default: false,
});
// Register user-facing command
pi.registerCommand("i-have-adhd", {
description: "Toggle ADHD mode",
handler: (args, ctx) => {
// Toggle implementation
},
});
}
Event-Driven State Management
The extension utilizes Pi and OMP's event system to maintain conversation context across turns. It registers handlers for session_start to restore previous activation states from persistent storage and session_compact to re-inject rules after context summarization. The input event handler processes specific trigger phrases—such as /skill:i-have-adhd to enable the mode and predefined stop phrases to disable it—returning { action: "continue" } to allow the conversation to proceed.
Abstracting Runtime Differences with context-compat.ts
To maintain a single codebase across both runtimes, the extension implements a compatibility shim in extensions/context-compat.ts. This module normalizes differences in how Pi and OMP expose session-manager context, specifically abstracting the buildSessionContext (Pi) versus buildContextEntries (OMP) APIs.
The shim exports two critical utilities imported by the main extension (lines 10-13):
contextMessages: Unifies reading session context from either runtime's specific APIlatestMarkerIsActive: Detects whether the most recent custom marker represents an active rules injection or a disable marker
The main extension uses these helpers to determine when to inject or withdraw the ADHD rule set (lines 90-99):
import { contextMessages, latestMarkerIsActive } from "./context-compat";
// Usage within event handlers
const messages = contextMessages(ctx);
const isActive = latestMarkerIsActive(messages);
if (isActive) {
ctx.session.addContextMessage({
role: "system",
content: loadedRules
});
}
Practical Implementation Example
A minimal native extension following this pattern requires the package.json declaration and a default-exported function implementing the ExtensionAPI interface:
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
import { contextMessages } from "./context-compat";
export default function myCrossPlatformExtension(pi: ExtensionAPI) {
// Register feature flag
pi.registerFlag("my-feature", {
description: "Enable custom assistance",
type: "boolean",
default: false,
});
// Register slash command
pi.registerCommand("toggle-my-feature", {
description: "Toggle assistance mode",
handler: (args, ctx) => {
const current = ctx.session.getFlag("my-feature");
ctx.session.setFlag("my-feature", !current);
return { message: `Feature ${!current ? "enabled" : "disabled"}` };
},
});
// Listen for conversation start to restore state
pi.on("session_start", async (_, ctx) => {
const state = ctx.session.get("my-extension-state");
if (state?.enabled) {
ctx.session.addContextMessage({
role: "system",
content: "Custom rules active"
});
}
});
}
Summary
- Dual runtime declaration: Configure both
pi.extensionsandomp.extensionsinpackage.jsonto point to the same TypeScript entry point for cross-platform support. - Default export function: Export a function receiving
ExtensionAPIto register flags, commands, and event listeners that work identically in Pi and OMP. - Compatibility abstraction: Use a shim module like
context-compat.tsto normalize runtime-specific APIs such asbuildSessionContextversusbuildContextEntries. - State persistence: Store activation state using the session-manager's key-value API under a custom key like
i-have-adhd-stateto survive conversation lifecycle events. - Rule injection: Inject system instructions via
ctx.session.addContextMessage()and manage their presence using marker detection utilities.
Frequently Asked Questions
How does i-have-adhd handle API differences between Pi and OMP session managers?
The extension uses extensions/context-compat.ts to normalize differences between runtime APIs. It exports contextMessages to read session context from either buildSessionContext (Pi) or buildContextEntries (OMP), and latestMarkerIsActive to detect the current injection state. This abstraction allows the main logic in extensions/i-have-adhd.ts to remain identical across both runtimes.
What specific events does the extension monitor to maintain state?
According to the source code in extensions/i-have-adhd.ts, the extension registers handlers for input (processing toggle commands like /skill:i-have-adhd), session_start (restoring persisted state), session_tree (handling conversation branching), and session_compact (re-injecting rules after context summarization). These events ensure the UI status bar and injected rules remain synchronized with the conversation.
Can one TypeScript file realistically serve both Pi and OMP without platform detection?
Yes. The repository demonstrates this architecture by pointing both pi.extensions and omp.extensions to ./extensions/i-have-adhd.ts in package.json. The extension loads successfully in both environments because the compatibility shim handles runtime-specific variations, while the core logic relies solely on the standardized ExtensionAPI interface provided by both platforms.
Where are the ADHD conversation rules stored and how are they injected?
The extension loads rules from skills/i-have-adhd/SKILL.md and injects them as custom messages via ctx.session.addContextMessage(). The extension persists an activation marker in the session state under the key i-have-adhd-state, enabling it to restore and re-inject rules after compaction events that might otherwise clear temporary context entries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →