diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 72c008b69..92726ebc2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Added `ctx.mode` to extension contexts so extensions can distinguish TUI, RPC, JSON, and print mode. +- Added `ctx.getSystemPromptOptions()` for extension commands to inspect the current base system prompt inputs. ### Fixed diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index 64b1b87b9..a8b453a63 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -982,6 +982,19 @@ pi.on("before_agent_start", (event, ctx) => { Command handlers receive `ExtensionCommandContext`, which extends `ExtensionContext` with session control methods. These are only available in commands because they can deadlock if called from event handlers. +### ctx.getSystemPromptOptions() + +Returns the base inputs Pi currently uses to build the system prompt. + +```typescript +const options = ctx.getSystemPromptOptions(); +const contextPaths = options.contextFiles?.map((file) => file.path) ?? []; +``` + +This has the same shape and mutability as `before_agent_start` `event.systemPromptOptions`: custom prompt, active tools, tool snippets, prompt guidelines, appended system prompt text, cwd, loaded context files, and loaded skills. It may include full context file contents, so treat it as sensitive extension-local data and avoid exposing it through command lists, logs, or autocomplete metadata. + +This reports the current base prompt inputs. It does not include per-turn `before_agent_start` chained system-prompt changes, later `context` event message mutations, or `before_provider_request` payload rewrites. + ### ctx.waitForIdle() Wait for the agent to finish streaming: diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 93916496f..556ca20e2 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -2264,6 +2264,7 @@ export class AgentSession { })(); }, getSystemPrompt: () => this.systemPrompt, + getSystemPromptOptions: () => this._baseSystemPromptOptions, }, { registerProvider: (name, config) => { diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index ed6f3b23a..59412dcbb 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -240,6 +240,7 @@ export class ExtensionRunner { private getContextUsageFn: () => ContextUsage | undefined = () => undefined; private compactFn: (options?: CompactOptions) => void = () => {}; private getSystemPromptFn: () => string = () => ""; + private getSystemPromptOptionsFn: () => BuildSystemPromptOptions = () => ({ cwd: this.cwd }); private newSessionHandler: NewSessionHandler = async () => ({ cancelled: false }); private forkHandler: ForkHandler = async () => ({ cancelled: false }); private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false }); @@ -299,6 +300,7 @@ export class ExtensionRunner { this.getContextUsageFn = contextActions.getContextUsage; this.compactFn = contextActions.compact; this.getSystemPromptFn = contextActions.getSystemPrompt; + this.getSystemPromptOptionsFn = contextActions.getSystemPromptOptions ?? (() => ({ cwd: this.cwd })); // Flush provider registrations queued during extension loading for (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) { @@ -648,6 +650,10 @@ export class ExtensionRunner { {}, Object.getOwnPropertyDescriptors(this.createContext()), ) as ExtensionCommandContext; + context.getSystemPromptOptions = () => { + this.assertActive(); + return this.getSystemPromptOptionsFn(); + }; context.waitForIdle = () => { this.assertActive(); return this.waitForIdleFn(); diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index db2a8f77b..a78a0657c 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -335,6 +335,9 @@ export interface ExtensionContext { * Includes session control methods only safe in user-initiated commands. */ export interface ExtensionCommandContext extends ExtensionContext { + /** Get the current base system-prompt construction options. */ + getSystemPromptOptions(): BuildSystemPromptOptions; + /** Wait for the agent to finish streaming */ waitForIdle(): Promise; @@ -1506,6 +1509,7 @@ export interface ExtensionContextActions { getContextUsage: () => ContextUsage | undefined; compact: (options?: CompactOptions) => void; getSystemPrompt: () => string; + getSystemPromptOptions?: () => BuildSystemPromptOptions; } /** diff --git a/packages/coding-agent/test/suite/agent-session-model-extension.test.ts b/packages/coding-agent/test/suite/agent-session-model-extension.test.ts index f6469884b..9c15ecf64 100644 --- a/packages/coding-agent/test/suite/agent-session-model-extension.test.ts +++ b/packages/coding-agent/test/suite/agent-session-model-extension.test.ts @@ -2,7 +2,7 @@ import type { AgentTool, ThinkingLevel } from "@earendil-works/pi-agent-core"; import { fauxAssistantMessage, fauxToolCall, type Model } from "@earendil-works/pi-ai"; import { Type } from "typebox"; import { afterEach, describe, expect, it } from "vitest"; -import type { ExtensionAPI } from "../../src/index.ts"; +import type { BuildSystemPromptOptions, ExtensionAPI } from "../../src/index.ts"; import { createHarness, getAssistantTexts, type Harness } from "./harness.ts"; describe("AgentSession model and extension characterization", () => { @@ -260,6 +260,34 @@ describe("AgentSession model and extension characterization", () => { expect(extensionApi).toBeDefined(); }); + it("allows extension commands to inspect live system prompt options", async () => { + const seenOptions: BuildSystemPromptOptions[] = []; + const harness = await createHarness({ + extensionFactories: [ + (pi) => { + pi.registerCommand("inspect-options", { + description: "Inspect system prompt options", + handler: async (_args, ctx) => { + const options = ctx.getSystemPromptOptions(); + seenOptions.push(options); + options.selectedTools?.push("mutated_tool"); + }, + }); + }, + ], + }); + harnesses.push(harness); + + await harness.session.prompt("/inspect-options"); + await harness.session.prompt("/inspect-options"); + + expect(seenOptions).toHaveLength(2); + expect(seenOptions[0]).toBe(seenOptions[1]); + expect(seenOptions[0]?.cwd).toBe(harness.tempDir); + expect(seenOptions[0]?.selectedTools).toContain("read"); + expect(seenOptions[1]?.selectedTools).toContain("mutated_tool"); + }); + it("allows before_agent_start handlers to inject custom messages and modify the system prompt", async () => { const harness = await createHarness({ extensionFactories: [