refactor: remove pi-ref lazy barrel import, fix test mocks for Bun 1.3.11
- 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)
This commit is contained in:
@@ -10,7 +10,6 @@ import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils
|
||||
import * as typebox from "@sinclair/typebox";
|
||||
import { getConfigDirs } from "../../config";
|
||||
import { execCommand } from "../../exec/exec";
|
||||
import { getPiRef, initPiRef } from "../pi-ref";
|
||||
import { GreenCommand } from "./bundled/ci-green";
|
||||
import { ReviewCommand } from "./bundled/review";
|
||||
import type {
|
||||
@@ -169,7 +168,6 @@ function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[]
|
||||
* Discover and load custom commands from standard locations.
|
||||
*/
|
||||
export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}): Promise<CustomCommandsLoadResult> {
|
||||
await initPiRef();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
const agentDir = options.agentDir ?? getAgentDir();
|
||||
|
||||
@@ -185,7 +183,7 @@ export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}
|
||||
exec: (command: string, args: string[], execOptions) =>
|
||||
execCommand(command, args, execOptions?.cwd ?? cwd, execOptions),
|
||||
typebox,
|
||||
pi: getPiRef(),
|
||||
pi: await import("@oh-my-pi/pi-coding-agent"),
|
||||
};
|
||||
|
||||
// 1. Load bundled commands first (lowest priority - can be overridden)
|
||||
|
||||
@@ -14,7 +14,6 @@ import { execCommand } from "../../exec/exec";
|
||||
import type { HookUIContext } from "../../extensibility/hooks/types";
|
||||
import { getAllPluginToolPaths } from "../../extensibility/plugins/loader";
|
||||
import type { PendingActionStore } from "../../tools/pending-action";
|
||||
import { getPiRef, initPiRef } from "../pi-ref";
|
||||
import { createNoOpUIContext, resolvePath } from "../utils";
|
||||
import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
|
||||
|
||||
@@ -85,7 +84,12 @@ export class CustomToolLoader {
|
||||
#sharedApi: CustomToolAPI;
|
||||
#seenNames: Set<string>;
|
||||
|
||||
constructor(cwd: string, builtInToolNames: string[], pendingActionStore?: PendingActionStore) {
|
||||
constructor(
|
||||
pi: typeof import("@oh-my-pi/pi-coding-agent"),
|
||||
cwd: string,
|
||||
builtInToolNames: string[],
|
||||
pendingActionStore?: PendingActionStore,
|
||||
) {
|
||||
this.#sharedApi = {
|
||||
cwd,
|
||||
exec: (command: string, args: string[], options?: ExecOptions) =>
|
||||
@@ -94,7 +98,7 @@ export class CustomToolLoader {
|
||||
hasUI: false,
|
||||
logger,
|
||||
typebox,
|
||||
pi: getPiRef(),
|
||||
pi,
|
||||
pushPendingAction: action => {
|
||||
if (!pendingActionStore) {
|
||||
throw new Error("Pending action store unavailable for custom tools in this runtime.");
|
||||
@@ -157,8 +161,12 @@ export async function loadCustomTools(
|
||||
builtInToolNames: string[],
|
||||
pendingActionStore?: PendingActionStore,
|
||||
) {
|
||||
await initPiRef();
|
||||
const loader = new CustomToolLoader(cwd, builtInToolNames, pendingActionStore);
|
||||
const loader = new CustomToolLoader(
|
||||
await import("@oh-my-pi/pi-coding-agent"),
|
||||
cwd,
|
||||
builtInToolNames,
|
||||
pendingActionStore,
|
||||
);
|
||||
await loader.load(pathsWithSources);
|
||||
return {
|
||||
tools: loader.tools,
|
||||
@@ -185,7 +193,6 @@ export async function discoverAndLoadCustomTools(
|
||||
builtInToolNames: string[],
|
||||
pendingActionStore?: PendingActionStore,
|
||||
) {
|
||||
await initPiRef();
|
||||
const allPathsWithSources: ToolPathWithSource[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import type { ExecOptions } from "../../exec/exec";
|
||||
import { execCommand } from "../../exec/exec";
|
||||
import type { CustomMessage } from "../../session/messages";
|
||||
import { EventBus } from "../../utils/event-bus";
|
||||
import { getPiRef, initPiRef } from "../pi-ref";
|
||||
import { getAllPluginExtensionPaths } from "../plugins/loader";
|
||||
import { resolvePath } from "../utils";
|
||||
import type {
|
||||
@@ -102,7 +101,6 @@ export class ExtensionRuntime implements IExtensionRuntime {
|
||||
class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
||||
readonly logger = logger;
|
||||
readonly typebox = TypeBox;
|
||||
readonly pi = getPiRef();
|
||||
readonly flagValues = new Map<string, boolean | string>();
|
||||
readonly pendingProviderRegistrations: Array<{
|
||||
name: string;
|
||||
@@ -111,6 +109,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
||||
}> = [];
|
||||
|
||||
constructor(
|
||||
public readonly pi: typeof import("@oh-my-pi/pi-coding-agent"),
|
||||
private readonly extension: Extension,
|
||||
private readonly runtime: IExtensionRuntime,
|
||||
private readonly cwd: string,
|
||||
@@ -265,7 +264,13 @@ async function loadExtension(
|
||||
}
|
||||
|
||||
const extension = createExtension(extensionPath, resolvedPath);
|
||||
const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
|
||||
const api = new ConcreteExtensionAPI(
|
||||
await import("@oh-my-pi/pi-coding-agent"),
|
||||
extension,
|
||||
runtime,
|
||||
cwd,
|
||||
eventBus,
|
||||
);
|
||||
await factory(api);
|
||||
|
||||
return { extension, error: null };
|
||||
@@ -285,9 +290,8 @@ export async function loadExtensionFromFactory(
|
||||
runtime: IExtensionRuntime,
|
||||
name = "<inline>",
|
||||
): Promise<Extension> {
|
||||
await initPiRef();
|
||||
const extension = createExtension(name, name);
|
||||
const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
|
||||
const api = new ConcreteExtensionAPI(await import("@oh-my-pi/pi-coding-agent"), extension, runtime, cwd, eventBus);
|
||||
await factory(api);
|
||||
return extension;
|
||||
}
|
||||
@@ -296,7 +300,6 @@ export async function loadExtensionFromFactory(
|
||||
* Load extensions from paths.
|
||||
*/
|
||||
export async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {
|
||||
await initPiRef();
|
||||
const extensions: Extension[] = [];
|
||||
const errors: Array<{ path: string; error: string }> = [];
|
||||
const resolvedEventBus = eventBus ?? new EventBus();
|
||||
@@ -455,7 +458,6 @@ export async function discoverAndLoadExtensions(
|
||||
eventBus?: EventBus,
|
||||
disabledExtensionIds: string[] = [],
|
||||
): Promise<LoadExtensionsResult> {
|
||||
await initPiRef();
|
||||
const allPaths: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const disabled = new Set(disabledExtensionIds);
|
||||
|
||||
@@ -9,7 +9,6 @@ import type { Hook } from "../../discovery";
|
||||
import { loadCapability } from "../../discovery";
|
||||
import type { HookMessage } from "../../session/messages";
|
||||
import type { SessionManager } from "../../session/session-manager";
|
||||
import { getPiRef, initPiRef } from "../pi-ref";
|
||||
import { resolvePath } from "../utils";
|
||||
import { execCommand } from "./runner";
|
||||
import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
|
||||
@@ -87,16 +86,16 @@ export interface LoadHooksResult {
|
||||
* Create a HookAPI instance that collects handlers, renderers, and commands.
|
||||
* Returns the API, maps, and functions to set handlers later.
|
||||
*/
|
||||
function createHookAPI(
|
||||
async function createHookAPI(
|
||||
handlers: Map<string, HandlerFn[]>,
|
||||
cwd: string,
|
||||
): {
|
||||
): Promise<{
|
||||
api: HookAPI;
|
||||
messageRenderers: Map<string, HookMessageRenderer>;
|
||||
commands: Map<string, RegisteredCommand>;
|
||||
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
||||
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
||||
} {
|
||||
}> {
|
||||
let sendMessageHandler: SendMessageHandler | null = null;
|
||||
let appendEntryHandler: AppendEntryHandler | null = null;
|
||||
const messageRenderers = new Map<string, HookMessageRenderer>();
|
||||
@@ -137,7 +136,7 @@ function createHookAPI(
|
||||
},
|
||||
logger,
|
||||
typebox,
|
||||
pi: getPiRef(),
|
||||
pi: await import("@oh-my-pi/pi-coding-agent"),
|
||||
} as HookAPI;
|
||||
|
||||
return {
|
||||
@@ -170,7 +169,7 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
||||
|
||||
// Create handlers map and API
|
||||
const handlers = new Map<string, HandlerFn[]>();
|
||||
const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = createHookAPI(
|
||||
const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = await createHookAPI(
|
||||
handlers,
|
||||
cwd,
|
||||
);
|
||||
@@ -202,7 +201,6 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
||||
* @param cwd - Current working directory for resolving relative paths
|
||||
*/
|
||||
export async function loadHooks(paths: string[], cwd: string): Promise<LoadHooksResult> {
|
||||
await initPiRef();
|
||||
const hooks: LoadedHook[] = [];
|
||||
const errors: Array<{ path: string; error: string }> = [];
|
||||
|
||||
@@ -232,7 +230,6 @@ export async function loadHooks(paths: string[], cwd: string): Promise<LoadHooks
|
||||
* Plus any explicitly configured paths from settings.
|
||||
*/
|
||||
export async function discoverAndLoadHooks(configuredPaths: string[], cwd: string): Promise<LoadHooksResult> {
|
||||
await initPiRef();
|
||||
const allPaths: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Lazy reference to the package barrel.
|
||||
*
|
||||
* Extension/hook/custom-tool/custom-command loaders expose the full
|
||||
* `@oh-my-pi/pi-coding-agent` namespace to user code. A static
|
||||
* `import * as piCodingAgent from "@oh-my-pi/pi-coding-agent"` from any of
|
||||
* those loaders creates a self-referential cycle during module init:
|
||||
*
|
||||
* tools/index -> task -> sdk -> <loader> -> package barrel
|
||||
* -> modes/components -> tool-execution -> renderers -> tools/read
|
||||
*
|
||||
* Combined with top-level await transitively pulled in by lru-cache, ESM
|
||||
* interleaves the cyclic branches and leaves `readToolRenderer` in its
|
||||
* temporal dead zone when `renderers.ts` reaches the `read` entry.
|
||||
*
|
||||
* This module breaks the cycle by deferring the barrel import.
|
||||
* `initPiRef()` must be awaited once, after the package has finished its
|
||||
* own initialization, before any loader reads the reference via
|
||||
* `getPiRef()`. `createAgentSession` (and `discoverCustomTSCommands`) are
|
||||
* responsible for this single call; both run entirely after module eval.
|
||||
*/
|
||||
import type * as PiCodingAgentNs from "@oh-my-pi/pi-coding-agent";
|
||||
|
||||
type PiCodingAgent = typeof PiCodingAgentNs;
|
||||
|
||||
let cached: PiCodingAgent | undefined;
|
||||
|
||||
/**
|
||||
* Resolve the package barrel once. Safe to call from any async entry point
|
||||
* that runs after all modules have finished evaluating.
|
||||
*/
|
||||
export async function initPiRef(): Promise<void> {
|
||||
if (cached) return;
|
||||
cached = (await import("@oh-my-pi/pi-coding-agent")) as PiCodingAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cached package barrel. `initPiRef()` must have resolved
|
||||
* before this is called; loaders that run inside `createAgentSession`
|
||||
* or `discoverCustomTSCommands` satisfy that requirement automatically.
|
||||
*/
|
||||
export function getPiRef(): PiCodingAgent {
|
||||
if (!cached) {
|
||||
throw new Error("pi-ref not initialized; await initPiRef() before instantiating loaders that expose `pi`.");
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
@@ -58,7 +58,6 @@ import {
|
||||
type ToolDefinition,
|
||||
wrapRegisteredTools,
|
||||
} from "./extensibility/extensions";
|
||||
import { initPiRef } from "./extensibility/pi-ref";
|
||||
import { loadSkills as loadSkillsInternal, type Skill, type SkillWarning } from "./extensibility/skills";
|
||||
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./extensibility/slash-commands";
|
||||
import {
|
||||
@@ -350,7 +349,6 @@ export async function discoverSlashCommands(cwd?: string): Promise<FileSlashComm
|
||||
* Discover custom commands (TypeScript slash commands) from cwd and agentDir.
|
||||
*/
|
||||
export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
|
||||
await initPiRef();
|
||||
const resolvedCwd = cwd ?? getProjectDir();
|
||||
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
||||
|
||||
@@ -642,7 +640,6 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
|
||||
* ```
|
||||
*/
|
||||
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
||||
await initPiRef();
|
||||
const cwd = options.cwd ?? getProjectDir();
|
||||
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
||||
const eventBus = options.eventBus ?? new EventBus();
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("python tool settings", () => {
|
||||
});
|
||||
|
||||
it("passes kernel mode from settings to executor", async () => {
|
||||
vi.spyOn(pythonExecutor, "warmPythonEnvironment").mockResolvedValue({ ok: true });
|
||||
vi.spyOn(pythonExecutor, "warmPythonEnvironment").mockResolvedValue({ ok: true, docs: [] });
|
||||
const executeSpy = vi.spyOn(pythonExecutor, "executePython").mockResolvedValue({
|
||||
output: "ok",
|
||||
exitCode: 0,
|
||||
|
||||
@@ -23,7 +23,7 @@ function createSession(cwd: string): ToolSession {
|
||||
describe("python tool execution", () => {
|
||||
it("passes kernel options from settings and args", async () => {
|
||||
const tempDir = TempDir.createSync("@python-tool-");
|
||||
vi.spyOn(pythonExecutor, "warmPythonEnvironment").mockResolvedValue({ ok: true });
|
||||
vi.spyOn(pythonExecutor, "warmPythonEnvironment").mockResolvedValue({ ok: true, docs: [] });
|
||||
const executeSpy = vi.spyOn(pythonExecutor, "executePython").mockResolvedValue({
|
||||
output: "ok",
|
||||
exitCode: 0,
|
||||
|
||||
Reference in New Issue
Block a user