Normalized extension custom-message payloads before session state or persistence, including bare string sendMessage shorthands. Skipped legacy bare custom_message entries during context rebuilds and dropped malformed custom/hook messages before LLM conversion. Added regression coverage for the poisoned-session resume crash.\n\nFixes #4345
`discoverExtensionPaths` loaded the extension-module capability across all
registered providers (native, claude, codex, gemini, opencode) and then
discarded every item whose `_source.provider !== "native"`. Four foreign
directory walks ran on every session startup only for their results to be
dropped — worst on Windows.
Scope the load to the native provider via the existing `LoadOptions.providers`
filter: `loadCapability(extensionModuleCapability.id, { ...loadOptions,
providers: ["native"] })`. The hook-capability load in the same function is
unchanged (hooks legitimately span providers). Output is identical.
Regression test in `extensions-discovery.test.ts` spies on each extension-module
provider's `load()` and asserts only "native" is invoked.
Fixes#4198
- Added `process.reallyExit` to the hard-exit API patch in `withExitGuard` to prevent bypass.
- Integrated `withExitGuard` wrapper into extension and hook factory invocation sites.
- Improved `ExtensionExitError` to provide dynamic reporting of the intercepted exit method.
Codex discovery surfaced every `~/.codex/hooks/*.{ts,js}` file as an OMP
hook (silently defaulting untyped names to `pre:<basename>`), and
`discoverExtensionPaths` then handed those paths to `loadExtension` for
dynamic import. A standalone Codex hook script with a top-level
`process.exit(0)` terminated the host CLI cleanly — `try/catch` around
`await import()` cannot intercept a synchronous exit, so OMP died at the
`loadExtensions:start` startup marker with no error surface.
Two-layer fix:
- `packages/coding-agent/src/extensibility/utils.ts`: new
`withExitGuard` helper patches `process.exit` for the duration of a
guarded callback so an exit raises `ExtensionExitError` instead of
terminating the process; nested and concurrent guards restore correctly
via depth counter.
- `extensibility/extensions/loader.ts`, `extensibility/hooks/loader.ts`,
and `extensibility/plugins/manager.ts` wrap their dynamic-import sites
in `withExitGuard` so the existing per-module `try/catch` records the
intercepted exit as a load error and OMP keeps starting.
- `discovery/codex.ts:loadHooks` no longer treats arbitrary files as
OMP hooks: only `pre-<tool>.{ts,js}` and `post-<tool>.{ts,js}` are
registered. Files like `memory-bank-reminder.ts` are silently skipped
rather than imported as extension factories.
Adds regression coverage:
- `test/extension-loader-process-exit.test.ts` — `loadExtensions` /
`loadHooks` return errors and leave `process.exit` restored when a
module exits at import time; sibling modules still load.
- `test/discovery/codex-hooks-discovery.test.ts` — codex provider
registers `pre-*` / `post-*` files and drops everything else.
Fixes#3680
- Imported and injected `zod/v4` under `zod` in runtime environments for custom commands, custom tools, extensions, and hooks.
- Added corresponding Type definitions to `CustomCommandAPI`, `CustomToolAPI`, `ExtensionAPI`, and `HookAPI` interfaces.
- Re-exported Zod and its alias from the main package entry point to expand the public API surface.
- Refactored the internal Typebox validation shim to support parsing via standard `.safeParse()` and `.toJSON()` methods.
- Advanced Typebox shim utility capabilities with support for dual-key record validation and enhanced schema structure handling.
- Migrated all wire protocol, schema definitions, and tools validation from Zod to ArkType across multiple packages.
- Updated extension runtimes, custom tools loader, and TypeBox compatibility shim to expose and use ArkType instances.
- Added a comprehensive ArkType migration guide, validation parity tests, and helper utilities.
- Removed redundant PDF asset routing and parsing implementations from the read tool.
Preserved Settings.disabledExtensions fallback when extension discovery is called without an explicit disabled list and kept hookCapability paths out of extension-module basename filtering.
Fixes#2796
Included JS and TS hookCapability results in extension path discovery so hooks under hooks/pre and hooks/post bind to the extension runner without explicit settings entries.
Fixes#2796
- Switched extensibility loader imports from namespace-style `zod` imports to named `z` imports in `zod/v4`.
- Updated extensibility type interfaces to use `typeof z` for injected `zod` modules in hook, extension, tool, and command APIs.
Same shape of bug the reviewer flagged for custom tools: forwarding
`LoadExtensionsResult` from parent to subagent reused Extension instances
whose factories closed over the parent's `ExtensionAPI` — cwd, eventBus,
and runtime all pointed at the parent. Any tool/handler/command that
referenced `api.exec()`, `api.events`, or `api.runtime` still acted on the
parent session/worktree from inside an isolated subagent.
Forward only the path list; each session rebuilds extensions through
`loadExtensions` so factories see the right `ExtensionAPI`.
- `extensibility/extensions/loader.ts`: extract `discoverExtensionPaths`
(FS scan only) from `discoverAndLoadExtensions`. The combined helper now
composes the two. New export added to the package barrel.
- `sdk.ts`:
- Add `discoverSessionExtensionPaths()` (the `disableExtensionDiscovery`-aware
path-only counterpart of `loadSessionExtensions`).
- Add `preloadedExtensionPaths?: string[]` to `CreateAgentSessionOptions`.
Three loader branches: `preloadedExtensions` (CLI same-process reuse,
still shallow-cloned), `preloadedExtensionPaths` (subagent: skip scan,
reload locally), or full discovery.
- Document `preloadedExtensions` as same-process-only; subagent
forwarding MUST use `preloadedExtensionPaths`.
- `tools/index.ts`: `ToolSession.extensionsResult` → `extensionPaths:
string[]` for the same reason.
- `task/executor.ts` and `task/index.ts`: forward `extensionPaths`. Drop
the forward for the isolated `runSubprocess` branch — worktree cwd ≠
parent cwd, so the subagent re-discovers extensions against its own
tree.
- New `test/sdk-extensions-per-session-binding.test.ts` pins the contract:
two `loadExtensions` calls on the same path with different `cwd` and
different `EventBus` instances yield distinct Extension + runtime
objects whose factories close over the per-call bindings.
- Updated `executor-pass-through` and `sdk-preloaded-extensions-isolation`
tests for the new option name and comment context.
Refs PR review on #2193
- Added canonical `pi.zod` schema API exports and removed TypeBox package exports/imports.
- Migrated Tool schema typing from TypeBox to shared `TSchema`/Zod flow with legacy TypeBox compatibility.
- Updated AI provider adapters and MCP/agent builders to convert tool params through `toolWireSchema()`.
- Reworked schema validation from AJV to Zod-safe parsing with `fromTypeBox`, `toolWireSchema`, and meta schema checks.
Some extensions/plugins still import the legacy @mariozechner/pi-* package names (pi-agent-core, pi-ai, pi-coding-agent, pi-tui) instead of @oh-my-pi/pi-*. Register a single Bun.plugin on first plugin/extension load that rewrites those specifiers to the current @oh-my-pi/pi-* equivalents, including the @mariozechner/pi-coding-agent/extensibility/{extensions,hooks} sub-exports. No filesystem mutation, no symlinks, no proxy files.
Fixes#973
- Document session name getter/setter methods in extensions.md
- Add stub methods to ExtensionProxy that throw if called before init
- Add delegating implementations to ExtensionProxyInit
- Wire up getSessionName and setSessionName in extension runtime
- Add method signatures to ExtensionContext interface and type
- Add getSessionName to session manager API
- Add getSessionName and setSessionName to all mode contexts:
- ACP agent
- Extension UI controller (also updates terminal title)
- Print mode
- RPC mode
- Remove pi-ref.ts deferred barrel import (no longer needed after circular dep fix)
- Update extension/hook/custom-tool/custom-command loaders to use direct imports
- Fix warmPythonEnvironment mock return type in python tool tests (add docs field)
The four extension/hook/custom-tool/custom-command loaders statically
imported the package barrel `@oh-my-pi/pi-coding-agent` to expose it as
`pi` to user code. This created a self-referential cycle:
tools/index -> task -> sdk -> custom-commands/loader
-> @oh-my-pi/pi-coding-agent (package barrel)
-> modes/components -> tool-execution -> renderers
-> tools/read (TDZ on readToolRenderer)
Triggered at startup by 'omp --help' / 'omp stats --help' depending on
ESM evaluation order, manifesting as:
ReferenceError: Cannot access 'readToolRenderer' before initialization
Introduce `extensibility/pi-ref.ts` that resolves the barrel lazily via
`require` on first call. All four loaders use `getPiRef()` instead of a
static `import * as piCodingAgent`. The barrel is fully initialized by
the time any loader function actually runs, so the lookup is safe.
- Added `toExtensionId()` method to all capability types for granular extension identification and disabling.
- Added `disabledExtensions` and `includeDisabled` options to LoadOptions for filtering disabled capabilities by extension ID.
- Added plugin manifest support for `extensions` entry points with automatic discovery from installed plugins.
- Fixed skill loading to properly respect disabled skill names from custom directories.
- Improved cross-platform path handling by using `path.basename()` instead of string splitting in context file and state manager.
- Refactored capability index loading to validate source metadata and filter disabled extensions before processing.
- Introduced Effort enum and ThinkingConfig metadata for per-model reasoning capabilities with min/max effort levels.
- Migrated thinking level API from string-based ThinkingLevel to structured Effort enum across agent and AI packages.
- Added model-thinking module with effort mapping, policy application, and semantic versioning utilities for provider-specific thinking modes.
- Removed supportsXhigh() function and replaced effort clamping with model-aware validation using ThinkingConfig metadata.
- Expanded models.json with thinking configuration objects for 50+ models including Claude, Gemini, and OpenAI variants.
- Added Python analysis scripts for edit tool usage patterns and tool invocation stream processing.
- Extracted thinking module with ThinkingEffort, ThinkingLevel, and ThinkingMode types to centralize reasoning configuration across packages.
- Migrated ThinkingLevel type from pi-agent-core to pi-ai package with new validation functions parseThinkingLevel() and getAvailableThinkingLevel().
- Consolidated thinking level constants and descriptions into reusable exports (ALL_THINKING_LEVELS, THINKING_MODE_DESCRIPTIONS) for consistent UI display.
- Removed local thinking-effort-label utility and replaced formatThinkingEffortLabel() with centralized formatThinking() function from pi-ai.
- Refactored thinking mode handling to distinguish ThinkingSelector (user-facing with 'off' option) from ThinkingEffort (provider-level).
- Extracted credential storage to shared @oh-my-pi/pi-ai package with AuthCredentialStore and AuthStorage classes.
- Consolidated UI formatting logic from ToolUIKit class into standalone utility functions across render-utils and output-meta modules.
- Moved utility functions (parseCommandArgs, substituteArgs, expandPath, normalizeUnicode) to dedicated modules for improved code reuse.
- Extracted JTD type definitions and type guards to jtd-utils module for shared use across schema conversion tools.
- Updated Claude model pricing and added cache read costs in models.json for accurate billing calculations.
- Refactored agent-storage to delegate credential management to AuthCredentialStore instead of direct SQLite operations.
- Fixes deferred `--model` resolution to match extension-provided models before fallback
- Fixes CLI `--api-key` handling to support deferred model selection
- Adds OAuth provider support for extensions with source-scoped registration cleanup
- Adds custom API registration helpers with built-in collision checks
- Expands `Api` type to support extension-defined identifiers
- Adds tests for runtime provider registration and model selection
packages/ai:
- feat: added custom API provider registry (api-registry.ts)
- feat: stream() and streamSimple() check custom registry before built-in switch
packages/coding-agent:
- feat: added registerProvider() to ExtensionAPI for registering custom model providers
- feat: added ProviderConfig and ProviderModelConfig types
- feat: ModelRegistry.registerProvider() registers models and custom streaming functions
- feat: extension loader queues provider registrations, runner processes them on initialize
This enables extensions to register custom LLM providers with their own streaming
implementations. For example, a Vertex AI Claude extension can register a provider
that uses @anthropic-ai/vertex-sdk for Google Cloud ADC authentication:
pi.registerProvider("google-vertex-claude", {
baseUrl: "https://us-east5-aiplatform.googleapis.com",
apiKey: "GOOGLE_CLOUD_PROJECT",
api: "vertex-claude-api",
models: [...],
streamSimple: streamVertexClaude,
});
Backported from pi-mono's extension provider registration system.
- Converted class properties to TypeScript parameter properties across 51 files to reduce boilerplate code.
- Removed explicit field declarations and manual assignments in constructors by using TypeScript's parameter property syntax with access modifiers.
- Applied consistent pattern of declaring private readonly and public readonly properties directly in constructor parameters.
- Modified setThinkingLevel() API to accept optional persist parameter with default value of false, allowing callers to control whether thinking level changes are persisted to settings.
- Updated SetThinkingLevelHandler type signature to include optional persist parameter for consistency across extension handlers.
- Simplified setThinkingLevel method signature from accepting options object to accepting boolean persist parameter, improving API clarity.
- Added default generic type parameter to Model interface, allowing Model to be used without explicit type argument.
- Removed explicit <any> generic type parameters from Model type annotations throughout codebase, leveraging new default parameter.
- Removed Prettier configuration files (.prettierignore and .prettierrc) and migrated formatting to Biome.
- Updated Biome configuration from version 2.3.11 to 2.3.12 and changed arrowParentheses rule from 'always' to 'asNeeded'.
- Pinned @biomejs/biome dependency to exact version 2.3.12 in package.json and bun.lock.
- Applied consistent arrow function formatting across 489 files by removing unnecessary parentheses around single parameters.
- Removed blank lines after comment blocks and reorganized imports for consistency across the codebase.
- Converted named imports from node modules (fs, path, os) to namespace imports across all packages.
- Extended extension loader error handling with isEacces and hasFsCode type guards.
- Converted readdirSync, readFileSync, and statSync to async readdir, readFile, stat across skills and agent discovery.
- Made scanDirectoryForSkills async and refactored custom directory scanning to use Promise.all for concurrent processing.
- Updated agent discovery to use fs/promises for async file reading and refactored helper patterns.
- Added AgentParsingError exception class for better error handling during agent parsing.
- Added filesystem error type guards (isEnoent, isEacces, isPerm, etc.) to pi-utils for safe error checking.
- Added color manipulation utilities to pi-utils for accessibility features.
- Added color-blind mode setting to settings manager.
- Migrated plugins, settings, and config modules from sync to async file operations.
- Updated error handling to use new pi-utils type guards for type-safe checking.
- Removed WASM generation script; use Bun `wasm?raw` loader for imports.
- Added bunfig.toml with loaders for `.md`, `.py`, and `.wasm?raw` text imports.
- Added types/assets/index.d.ts for global TypeScript module declarations.
- Unified TypeScript configuration with tsgo-based checking across monorepo.
- Removed build and WASM steps from install and publish pipelines.
- Added tsconfig.publish.json files to all packages with optimized publish-time configuration.
- Updated all package.json scripts with prepublishOnly hooks for correct type checking during publish.
- Added @oh-my-pi/omp-stats path mappings to root tsconfig.json for consistent imports.
- Added WASM generation script for photon module and integrated into install:dev script.