fix(engine): make pi.js imports static to fail fast on partial dist
Replace lazy `await import("./pi.js")` and `require("./pi.js")` calls in
runtime-resolution, agent-session-helpers, agent-heartbeat, and
cron-runner with top-level static imports. These dynamic imports were
documented as plugin-decoupling, but pi.js is already eagerly loaded
through index.ts re-exports and static imports in executor/merger/
reviewer/triage/mission-execution-loop, so the deferral never paid off
in practice.
The deferral did, however, introduce a TOCTOU race: a tsc rebuild that
momentarily emptied dist/pi.js would let the engine load fine and only
fail minutes later when the first session was created (e.g. FN-2860
errored two minutes into execution while pi.js was being rewritten).
With static imports, a missing/half-built dist now fails immediately at
process startup with a clear stack — verified by `mv dist/pi.js
dist/pi.js.bak` reproducing ERR_MODULE_NOT_FOUND on the first import of
runtime-resolution.js.
Also drops the DefaultPiRuntime.describeModelFn cache, which only
existed to paper over the require-on-first-call latency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,9 +26,7 @@ import { AgentLogger } from "./agent-logger.js";
|
|||||||
import { resolveAgentInstructionsWithRatings, buildSystemPromptWithInstructions } from "./agent-instructions.js";
|
import { resolveAgentInstructionsWithRatings, buildSystemPromptWithInstructions } from "./agent-instructions.js";
|
||||||
import { heartbeatLog, formatError } from "./logger.js";
|
import { heartbeatLog, formatError } from "./logger.js";
|
||||||
import { createRunAuditor, type EngineRunContext } from "./run-audit.js";
|
import { createRunAuditor, type EngineRunContext } from "./run-audit.js";
|
||||||
|
import { promptWithFallback } from "./pi.js";
|
||||||
// Lazy import for pi — avoids pulling the pi SDK into the module graph
|
|
||||||
// when heartbeat execution isn't needed.
|
|
||||||
|
|
||||||
/** Resolved per-agent heartbeat config after validation and fallback */
|
/** Resolved per-agent heartbeat config after validation and fallback */
|
||||||
interface ResolvedHeartbeatConfig {
|
interface ResolvedHeartbeatConfig {
|
||||||
@@ -1099,8 +1097,6 @@ export class HeartbeatMonitor {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lazy-load promptWithFallback
|
|
||||||
const { promptWithFallback } = await import("./pi.js");
|
|
||||||
const { createResolvedAgentSession, extractRuntimeHint } = await import("./agent-session-helpers.js");
|
const { createResolvedAgentSession, extractRuntimeHint } = await import("./agent-session-helpers.js");
|
||||||
const { buildSessionSkillContextSync } = await import("./session-skill-context.js");
|
const { buildSessionSkillContextSync } = await import("./session-skill-context.js");
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { PluginRunner } from "./plugin-runner.js";
|
|||||||
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
||||||
import { resolveRuntime, buildRuntimeResolutionContext, type SessionPurpose } from "./runtime-resolution.js";
|
import { resolveRuntime, buildRuntimeResolutionContext, type SessionPurpose } from "./runtime-resolution.js";
|
||||||
import { createLogger } from "./logger.js";
|
import { createLogger } from "./logger.js";
|
||||||
|
import { promptWithFallback, describeModel } from "./pi.js";
|
||||||
|
|
||||||
/** Logger for agent session helpers */
|
/** Logger for agent session helpers */
|
||||||
const sessionLog = createLogger("agent-session");
|
const sessionLog = createLogger("agent-session");
|
||||||
@@ -130,10 +131,7 @@ export async function promptWithAutoRetry(
|
|||||||
prompt: string,
|
prompt: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Dynamic import to get the default runtime's promptWithFallback
|
return promptWithFallback(session, prompt, options);
|
||||||
// This works because the default runtime delegates to the existing implementation
|
|
||||||
const { promptWithFallback: pwf } = await import("./pi.js");
|
|
||||||
return pwf(session, prompt, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,6 +141,5 @@ export async function promptWithAutoRetry(
|
|||||||
* @returns Model description string
|
* @returns Model description string
|
||||||
*/
|
*/
|
||||||
export async function describeAgentModel(session: AgentSession): Promise<string> {
|
export async function describeAgentModel(session: AgentSession): Promise<string> {
|
||||||
const { describeModel: dm } = await import("./pi.js");
|
return describeModel(session);
|
||||||
return dm(session);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { AutomationStore } from "@fusion/core";
|
|||||||
import type { ScheduledTask, AutomationRunResult, AutomationStep, AutomationStepResult, Column, TaskCreateInput } from "@fusion/core";
|
import type { ScheduledTask, AutomationRunResult, AutomationStep, AutomationStepResult, Column, TaskCreateInput } from "@fusion/core";
|
||||||
import { createLogger } from "./logger.js";
|
import { createLogger } from "./logger.js";
|
||||||
import { defaultShell } from "./shell-utils.js";
|
import { defaultShell } from "./shell-utils.js";
|
||||||
|
import { createFnAgent, promptWithFallback } from "./pi.js";
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
const log = createLogger("cron-runner");
|
const log = createLogger("cron-runner");
|
||||||
@@ -628,9 +629,6 @@ const AI_AUTOMATION_SYSTEM_PROMPT = [
|
|||||||
* @returns An AiPromptExecutor function suitable for CronRunnerOptions.
|
* @returns An AiPromptExecutor function suitable for CronRunnerOptions.
|
||||||
*/
|
*/
|
||||||
export async function createAiPromptExecutor(cwd: string): Promise<AiPromptExecutor> {
|
export async function createAiPromptExecutor(cwd: string): Promise<AiPromptExecutor> {
|
||||||
// We import lazily to keep the factory self-contained and to avoid
|
|
||||||
// pulling pi.ts into the module graph when AI execution isn't used.
|
|
||||||
const { createFnAgent, promptWithFallback } = await import("./pi.js");
|
|
||||||
const disposeLog = createLogger("cron-runner");
|
const disposeLog = createLogger("cron-runner");
|
||||||
|
|
||||||
return async (prompt: string, modelProvider?: string, modelId?: string): Promise<string> => {
|
return async (prompt: string, modelProvider?: string, modelId?: string): Promise<string> => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { AgentRuntime, AgentRuntimeOptions, AgentSessionResult } from "./ag
|
|||||||
import type { PluginRunner } from "./plugin-runner.js";
|
import type { PluginRunner } from "./plugin-runner.js";
|
||||||
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
||||||
import { createLogger } from "./logger.js";
|
import { createLogger } from "./logger.js";
|
||||||
|
import { createFnAgent, promptWithFallback, describeModel } from "./pi.js";
|
||||||
|
|
||||||
/** Logger for the runtime resolution subsystem */
|
/** Logger for the runtime resolution subsystem */
|
||||||
const runtimeLog = createLogger("runtime-resolver");
|
const runtimeLog = createLogger("runtime-resolver");
|
||||||
@@ -77,36 +78,16 @@ export class DefaultPiRuntime implements AgentRuntime {
|
|||||||
readonly id = "pi";
|
readonly id = "pi";
|
||||||
readonly name = "Default PI Runtime";
|
readonly name = "Default PI Runtime";
|
||||||
|
|
||||||
// Synchronous cached describeModel function
|
|
||||||
private static describeModelFn: ((session: AgentSession) => string) | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an agent session using the default pi implementation.
|
|
||||||
*/
|
|
||||||
async createSession(options: AgentRuntimeOptions): Promise<AgentSessionResult> {
|
async createSession(options: AgentRuntimeOptions): Promise<AgentSessionResult> {
|
||||||
const { createFnAgent } = await import("./pi.js");
|
|
||||||
return createFnAgent(options);
|
return createFnAgent(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt with automatic retry and compaction.
|
|
||||||
* Delegates to the existing promptWithFallback implementation.
|
|
||||||
*/
|
|
||||||
async promptWithFallback(session: AgentSession, prompt: string, options?: unknown): Promise<void> {
|
async promptWithFallback(session: AgentSession, prompt: string, options?: unknown): Promise<void> {
|
||||||
const { promptWithFallback: pwf } = await import("./pi.js");
|
return promptWithFallback(session, prompt, options);
|
||||||
return pwf(session, prompt, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get model description from session.
|
|
||||||
*/
|
|
||||||
describeModel(session: AgentSession): string {
|
describeModel(session: AgentSession): string {
|
||||||
if (!DefaultPiRuntime.describeModelFn) {
|
return describeModel(session);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
const { describeModel } = require("./pi.js");
|
|
||||||
DefaultPiRuntime.describeModelFn = describeModel;
|
|
||||||
}
|
|
||||||
return DefaultPiRuntime.describeModelFn!(session);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,18 +197,15 @@ function wrapPluginRuntime(
|
|||||||
return adapter.promptWithFallback(session, prompt, options);
|
return adapter.promptWithFallback(session, prompt, options);
|
||||||
}
|
}
|
||||||
// Fallback to default pi promptWithFallback
|
// Fallback to default pi promptWithFallback
|
||||||
const { promptWithFallback: pwf } = await import("./pi.js");
|
return promptWithFallback(session, prompt, options);
|
||||||
return pwf(session, prompt, options);
|
|
||||||
},
|
},
|
||||||
describeModel: (session: AgentSession) => {
|
describeModel: (session: AgentSession) => {
|
||||||
const adapter = instance as Record<string, unknown>;
|
const adapter = instance as Record<string, unknown>;
|
||||||
if (typeof adapter.describeModel === "function") {
|
if (typeof adapter.describeModel === "function") {
|
||||||
return (adapter.describeModel as (s: AgentSession) => string)(session);
|
return (adapter.describeModel as (s: AgentSession) => string)(session);
|
||||||
}
|
}
|
||||||
// Fallback to default pi describeModel - use cached sync function
|
// Fallback to default pi describeModel
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
return describeModel(session);
|
||||||
const { describeModel: dm } = require("./pi.js");
|
|
||||||
return dm(session);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user