diff --git a/packages/docs/src/content/docs/config/models.mdx b/packages/docs/src/content/docs/config/models.mdx index 870a1662b..6acbc5fc5 100644 --- a/packages/docs/src/content/docs/config/models.mdx +++ b/packages/docs/src/content/docs/config/models.mdx @@ -58,6 +58,20 @@ Warden mirrors these to the native `{PROVIDER}_API_KEY` expected by each SDK at If you already have a native provider key set (e.g. `OPENAI_API_KEY`), Warden will use it directly and the `WARDEN_`-prefixed form is not required. +### Pi Extensions + +By default the Pi runtime loads **no** [Pi extensions](https://pi.dev/docs/extensions), +neither project-local nor global ones, so runs are hermetic. To load specific extension +files (for example an LLM proxy router or a tracing hook), set +`WARDEN_PI_EXTENSION_PATHS` to a comma-separated list of extension file paths: + +```yaml title="GitHub Actions" +env: + WARDEN_PI_EXTENSION_PATHS: /home/runner/warden-extensions/tracing.ts +``` + +Only the listed files are loaded; extension auto-discovery stays disabled. + ## Claude Runtime Models When `runtime = "claude"`, use the model IDs accepted by Claude Code: diff --git a/packages/warden/src/sdk/runtimes/pi.test.ts b/packages/warden/src/sdk/runtimes/pi.test.ts index 089533a08..15f9fca2d 100644 --- a/packages/warden/src/sdk/runtimes/pi.test.ts +++ b/packages/warden/src/sdk/runtimes/pi.test.ts @@ -219,6 +219,27 @@ describe('piRuntime.runSkill', () => { }); }); + it('loads no Pi extensions by default', async () => { + await piRuntime.runSkill(baseSkillRequest()); + + expect(piMocks.resourceLoaderOptions[0]).toEqual(expect.objectContaining({ noExtensions: true })); + expect(piMocks.resourceLoaderOptions[0]).not.toHaveProperty('additionalExtensionPaths'); + }); + + it('threads WARDEN_PI_EXTENSION_PATHS into the resource loader', async () => { + vi.stubEnv('WARDEN_PI_EXTENSION_PATHS', '/ext/tracing.ts, /ext/routing.ts,'); + try { + await piRuntime.runSkill(baseSkillRequest()); + } finally { + vi.unstubAllEnvs(); + } + + expect(piMocks.resourceLoaderOptions[0]).toEqual(expect.objectContaining({ + noExtensions: true, + additionalExtensionPaths: ['/ext/tracing.ts', '/ext/routing.ts'], + })); + }); + it('records Pi tool execution spans when trace capture is active', async () => { const toolResult = { role: 'toolResult', diff --git a/packages/warden/src/sdk/runtimes/pi.ts b/packages/warden/src/sdk/runtimes/pi.ts index 144f58ca3..0bd1082f0 100644 --- a/packages/warden/src/sdk/runtimes/pi.ts +++ b/packages/warden/src/sdk/runtimes/pi.ts @@ -343,6 +343,13 @@ async function promptWithTimeout( } } +function piExtensionPathsFromEnv(env: NodeJS.ProcessEnv = process.env): string[] { + return (env['WARDEN_PI_EXTENSION_PATHS'] ?? '') + .split(',') + .map((path) => path.trim()) + .filter((path) => path.length > 0); +} + async function runPiPrompt(options: PiPromptOptions): Promise { const warnings: string[] = []; bridgeWardenProviderApiKeyEnv(); @@ -350,11 +357,13 @@ async function runPiPrompt(options: PiPromptOptions): Promise { const model = resolvePiModel(options.model, modelRuntime); const settingsManager = buildSettingsManager(options.timeout, options.maxRetries); const agentDir = getAgentDir(); + const additionalExtensionPaths = piExtensionPathsFromEnv(); const resourceLoader = new DefaultResourceLoader({ cwd: options.cwd, agentDir, settingsManager, noExtensions: true, + ...(additionalExtensionPaths.length > 0 ? { additionalExtensionPaths } : {}), noSkills: true, noPromptTemplates: true, noThemes: true,