From bb3d7d399c06e5fe284f34eb66b15b037ab18649 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 22 Jul 2026 20:15:02 +0200 Subject: [PATCH] feat(coding-agent): expose session metadata to bash tools (#6967) --- packages/coding-agent/CHANGELOG.md | 4 + packages/coding-agent/README.md | 13 +++ .../docs/environment-variables.md | 88 +++++++++++++++++++ packages/coding-agent/docs/extensions.md | 14 ++- packages/coding-agent/docs/index.md | 1 + packages/coding-agent/docs/usage.md | 13 --- .../src/core/extensions/runner.ts | 4 + .../coding-agent/src/core/extensions/types.ts | 2 + .../coding-agent/src/core/system-prompt.ts | 2 +- packages/coding-agent/src/core/tools/bash.ts | 47 ++++++++-- .../src/core/tools/tool-definition-wrapper.ts | 4 +- .../src/modes/interactive/interactive-mode.ts | 1 + .../test/agent-session-dynamic-tools.test.ts | 71 +++++++++++++++ .../test/sdk-session-manager.test.ts | 36 ++++++++ .../coding-agent/test/system-prompt.test.ts | 1 + .../test/tool-execution-component.test.ts | 4 +- 16 files changed, 278 insertions(+), 27 deletions(-) create mode 100644 packages/coding-agent/docs/environment-variables.md diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 549e9660..41cad4f7 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Exposed `PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL`, and `PI_REASONING_LEVEL` to commands run by built-in and factory-created bash tools. + ## [0.81.1] - 2026-07-21 ### New Features diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 2198d724..a6845476 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -662,6 +662,7 @@ pi --thinking high "Solve this complex problem" | Variable | Description | |----------|-------------| +| `PI_CODING_AGENT` | Set to `true` by the CLI and RPC entry points so child processes can detect that they run inside Pi | | `PI_CODING_AGENT_DIR` | Override config directory (default: `~/.pi/agent`) | | `PI_CODING_AGENT_SESSION_DIR` | Override session storage directory (overridden by `--session-dir`) | | `PI_PACKAGE_DIR` | Override package directory (useful for Nix/Guix where store paths tokenize poorly) | @@ -671,6 +672,18 @@ pi --thinking high "Solve this complex problem" | `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache (Anthropic: 1h, OpenAI: 24h) | | `VISUAL`, `EDITOR` | Fallback external editor for Ctrl+G when `externalEditor` is unset; defaults to Notepad on Windows and `nano` elsewhere | +Commands run by the LLM-callable bash tool also receive current session metadata: + +| Variable | Description | +|----------|-------------| +| `PI_SESSION_ID` | Current session ID | +| `PI_SESSION_FILE` | Absolute session JSONL path; unset for ephemeral sessions | +| `PI_PROVIDER` | Currently selected model provider | +| `PI_MODEL` | Currently selected model ID | +| `PI_REASONING_LEVEL` | Current effective reasoning level | + +These values are resolved when each command starts. See [Environment Variables](docs/environment-variables.md#bash-tool-session-environment) for semantics, examples, and custom-tool opt-out. + --- ## Contributing & Development diff --git a/packages/coding-agent/docs/environment-variables.md b/packages/coding-agent/docs/environment-variables.md new file mode 100644 index 00000000..072744ca --- /dev/null +++ b/packages/coding-agent/docs/environment-variables.md @@ -0,0 +1,88 @@ +# Environment Variables + +Pi uses environment variables in three ways: + +- Variables such as `PI_OFFLINE` configure the Pi process. +- Pi sets `PI_CODING_AGENT` so child processes can detect that they run inside Pi. +- Commands run by the LLM-callable bash tool receive `PI_*` variables describing the current session. + +Provider API-key variables are documented separately in [Providers](providers.md#environment-variables-or-auth-file). + +## Process Marker + +The CLI and RPC entry points set `PI_CODING_AGENT=true`. Child processes inherit it and can use it to detect that they run inside Pi. It is not session-specific and is not set automatically when Pi is embedded through the SDK. + +## Bash Tool Session Environment + +Commands run by the bash tool receive the current Pi session state: + +| Variable | Description | +|----------|-------------| +| `PI_SESSION_ID` | Current session ID | +| `PI_SESSION_FILE` | Absolute path to the current session JSONL file; unset for ephemeral sessions | +| `PI_PROVIDER` | Currently selected model provider | +| `PI_MODEL` | Currently selected model ID | +| `PI_REASONING_LEVEL` | Current effective reasoning level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max` | + +The values are resolved when each command starts. Switching models or changing the reasoning level therefore affects the next bash command without restarting Pi. `PI_PROVIDER` and `PI_MODEL` identify the selected Pi model, not a different upstream model that a router may choose internally. + +When asked which model or provider is running, inspect these variables instead of inferring the answer from the system prompt: + +```bash +printf '%s/%s\n' "$PI_PROVIDER" "$PI_MODEL" +printf 'reasoning=%s session=%s\n' "$PI_REASONING_LEVEL" "$PI_SESSION_ID" +``` + +The session file can be inspected directly when the session is persistent: + +```bash +if [ -n "$PI_SESSION_FILE" ]; then + tail -n 1 "$PI_SESSION_FILE" +fi +``` + +These variables are injected into the LLM-callable bash tool. They are not injected into user-entered `!` or `!!` commands. + +### Custom Bash Tools + +Bash tools created with `createBashTool()` expose the session environment by default when registered with Pi. Injection happens before `spawnHook`, so a hook receives the variables in `ctx.env`: + +```typescript +const bashTool = createBashTool(cwd, { + spawnHook: (ctx) => ({ + ...ctx, + env: { ...ctx.env, CI: "1" }, + }), +}); +``` + +Disable session metadata independently of the spawn hook: + +```typescript +const bashTool = createBashTool(cwd, { + exposeSessionEnvironment: false, + spawnHook: (ctx) => ctx, +}); +``` + +When disabled, Pi removes inherited values for these variables so nested Pi processes do not expose stale parent-session metadata. + +## Pi Process Configuration + +These variables are read by Pi itself: + +| Variable | Description | +|----------|-------------| +| `PI_CODING_AGENT_DIR` | Override the config directory; default is `~/.pi/agent` | +| `PI_CODING_AGENT_SESSION_DIR` | Override session storage; overridden by `--session-dir` | +| `PI_PACKAGE_DIR` | Override the package directory, useful for Nix/Guix store paths | +| `PI_OFFLINE` | Disable startup network operations, including update checks, package updates, and install/update telemetry | +| `PI_SKIP_VERSION_CHECK` | Disable the `pi.dev` latest-version request | +| `PI_TELEMETRY` | Override install/update telemetry and provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no` | +| `PI_CACHE_RETENTION` | Set to `long` for extended provider prompt caching where supported | +| `PI_SHARE_VIEWER_URL` | Override the base URL used by `/share` | +| `PI_HARDWARE_CURSOR` | Set to `1` to show the hardware cursor; see [Terminal setup](terminal-setup.md) | +| `VISUAL`, `EDITOR` | External editor fallback when `externalEditor` is unset | +| `HTTP_PROXY`, `HTTPS_PROXY` | Proxy outbound HTTP requests | + +Provider credentials such as `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and cloud-provider configuration are listed in [Providers](providers.md#environment-variables-or-auth-file). diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index 6aec1196..1622ca6f 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -982,9 +982,9 @@ ctx.sessionManager.buildContextEntries() // Active branch entries with compac ctx.sessionManager.getLeafId() // Current leaf entry ID ``` -### ctx.modelRegistry / ctx.model +### ctx.modelRegistry / ctx.model / ctx.thinkingLevel -Access to models, providers, and resolved authentication. `ctx.modelRegistry.getProvider(id)` returns the effective pi-ai provider, while `getProviderAuth(id)` resolves its current API key, headers, base URL, and provider-scoped environment without requiring a loaded model. `ctx.model` is the active model. +Access to models, providers, and resolved authentication. `ctx.modelRegistry.getProvider(id)` returns the effective pi-ai provider, while `getProviderAuth(id)` resolves its current API key, headers, base URL, and provider-scoped environment without requiring a loaded model. `ctx.model` is the active model, and `ctx.thinkingLevel` is its current effective thinking level. ### ctx.signal @@ -2096,7 +2096,15 @@ const bashTool = createBashTool(cwd, { }); ``` -See [examples/extensions/ssh.ts](../examples/extensions/ssh.ts) for a complete SSH example with `--ssh` flag. +`createBashTool()` exposes the current session to commands through `PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL`, and `PI_REASONING_LEVEL`. Injection happens before `spawnHook`, so hooks receive these values in `env` and preserve them when they spread the existing environment as above. Set `exposeSessionEnvironment: false` to disable them: + +```typescript +const bashTool = createBashTool(cwd, { + exposeSessionEnvironment: false, +}); +``` + +See [Bash tool session environment](environment-variables.md#bash-tool-session-environment) for variable semantics. See [examples/extensions/ssh.ts](../examples/extensions/ssh.ts) for a complete SSH example with `--ssh` flag. ### Output Truncation diff --git a/packages/coding-agent/docs/index.md b/packages/coding-agent/docs/index.md index 6b1e419e..c09cfc9a 100644 --- a/packages/coding-agent/docs/index.md +++ b/packages/coding-agent/docs/index.md @@ -68,6 +68,7 @@ For the full first-run flow, see [Quickstart](quickstart.md). ## Reference +- [Environment variables](environment-variables.md) - Pi process configuration and session metadata available to bash tools. - [Session format](session-format.md) - JSONL session file format, entry types, and SessionManager API. ## Platform setup diff --git a/packages/coding-agent/docs/usage.md b/packages/coding-agent/docs/usage.md index c9df34ef..8c0aa565 100644 --- a/packages/coding-agent/docs/usage.md +++ b/packages/coding-agent/docs/usage.md @@ -289,19 +289,6 @@ pi --tools read,grep,find,ls -p "Review the code" pi --exclude-tools ask_question ``` -### Environment Variables - -| Variable | Description | -|----------|-------------| -| `PI_CODING_AGENT_DIR` | Override config directory; default is `~/.pi/agent` | -| `PI_CODING_AGENT_SESSION_DIR` | Override session storage directory; overridden by `--session-dir` | -| `PI_PACKAGE_DIR` | Override package directory, useful for Nix/Guix store paths | -| `PI_OFFLINE` | Disable startup network operations, including update checks, package update checks, and install/update telemetry | -| `PI_SKIP_VERSION_CHECK` | Skip the Pi version update check at startup. This prevents the `pi.dev` latest-version request | -| `PI_TELEMETRY` | Override install/update telemetry and provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no`. This does not disable update checks | -| `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache where supported | -| `VISUAL`, `EDITOR` | Fallback external editor for Ctrl+G when `externalEditor` is unset; defaults to Notepad on Windows and `nano` elsewhere | - ## Design Principles Pi keeps the core small and pushes workflow-specific behavior into extensions, skills, prompt templates, and packages. diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index 3c320e14..e0119fed 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -694,6 +694,10 @@ export class ExtensionRunner { runner.assertActive(); return getModel(); }, + get thinkingLevel() { + runner.assertActive(); + return runner.runtime.getThinkingLevel(); + }, isIdle: () => { runner.assertActive(); return runner.isIdleFn(); diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index e8bf2627..28617296 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -317,6 +317,8 @@ export interface ExtensionContext { modelRegistry: ModelRegistry; /** Current model (may be undefined) */ model: Model | undefined; + /** Current thinking level, when provided by the session runtime. */ + thinkingLevel?: ThinkingLevel; /** Whether the agent is idle (not streaming) */ isIdle(): boolean; /** Whether project-local trust is active for this context. */ diff --git a/packages/coding-agent/src/core/system-prompt.ts b/packages/coding-agent/src/core/system-prompt.ts index d34ff88d..35f4ca40 100644 --- a/packages/coding-agent/src/core/system-prompt.ts +++ b/packages/coding-agent/src/core/system-prompt.ts @@ -133,7 +133,7 @@ Pi documentation (read only when the user asks about pi itself, its SDK, extensi - Additional docs: ${docsPath} - Examples: ${examplesPath} (extensions, custom tools, SDK) - When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory -- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md) +- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md) - When working on pi topics, read the docs and examples, and follow .md cross-references before implementing - Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`; diff --git a/packages/coding-agent/src/core/tools/bash.ts b/packages/coding-agent/src/core/tools/bash.ts index b0bd5022..1c245cbb 100644 --- a/packages/coding-agent/src/core/tools/bash.ts +++ b/packages/coding-agent/src/core/tools/bash.ts @@ -15,7 +15,7 @@ import { trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.ts"; -import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.ts"; +import type { ExtensionContext, ToolDefinition, ToolRenderResultOptions } from "../extensions/types.ts"; import { OutputAccumulator } from "./output-accumulator.ts"; import { getTextOutput, invalidArgText, str } from "./render-utils.ts"; import { wrapToolDefinition } from "./tool-definition-wrapper.ts"; @@ -155,8 +155,31 @@ export interface BashSpawnContext { export type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext; -function resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext { - const baseContext: BashSpawnContext = { command, cwd, env: { ...getShellEnv() } }; +function resolveSpawnContext( + command: string, + cwd: string, + spawnHook: BashSpawnHook | undefined, + exposeSessionEnvironment: boolean, + ctx: ExtensionContext | undefined, +): BashSpawnContext { + const env = { ...getShellEnv() }; + delete env.PI_SESSION_ID; + delete env.PI_SESSION_FILE; + delete env.PI_PROVIDER; + delete env.PI_MODEL; + delete env.PI_REASONING_LEVEL; + if (exposeSessionEnvironment && ctx) { + const model = ctx.model; + env.PI_SESSION_ID = ctx.sessionManager.getSessionId(); + const sessionFile = ctx.sessionManager.getSessionFile(); + if (sessionFile) env.PI_SESSION_FILE = sessionFile; + if (model) { + env.PI_PROVIDER = model.provider; + env.PI_MODEL = model.id; + } + if (ctx.thinkingLevel) env.PI_REASONING_LEVEL = ctx.thinkingLevel; + } + const baseContext: BashSpawnContext = { command, cwd, env }; return spawnHook ? spawnHook(baseContext) : baseContext; } @@ -167,6 +190,8 @@ export interface BashToolOptions { commandPrefix?: string; /** Optional explicit shell path from settings */ shellPath?: string; + /** Expose current Pi session metadata as PI_* environment variables. Default: true */ + exposeSessionEnvironment?: boolean; /** Hook to adjust command, cwd, or env before execution */ spawnHook?: BashSpawnHook; } @@ -294,22 +319,26 @@ export function createBashToolDefinition( ): ToolDefinition { const ops = options?.operations ?? createLocalBashOperations({ shellPath: options?.shellPath }); const commandPrefix = options?.commandPrefix; + const exposeSessionEnvironment = options?.exposeSessionEnvironment ?? true; const spawnHook = options?.spawnHook; return { name: "bash", label: "bash", description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`, promptSnippet: "Execute bash commands (ls, grep, find, etc.)", + promptGuidelines: exposeSessionEnvironment + ? ["Inspect PI_* environment variables for current model and session details."] + : undefined, parameters: bashSchema, async execute( _toolCallId, { command, timeout }: { command: string; timeout?: number }, signal?: AbortSignal, onUpdate?, - _ctx?, + ctx?, ) { const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command; - const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook); + const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook, exposeSessionEnvironment, ctx); const output = new OutputAccumulator({ tempFilePrefix: "pi-bash" }); let acceptingOutput = true; let updateTimer: NodeJS.Timeout | undefined; @@ -466,5 +495,11 @@ export function createBashToolDefinition( } export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool { - return wrapToolDefinition(createBashToolDefinition(cwd, options)); + const definition = createBashToolDefinition(cwd, options); + const tool = wrapToolDefinition(definition); + Object.assign(tool, { + promptSnippet: definition.promptSnippet, + promptGuidelines: definition.promptGuidelines, + }); + return tool; } diff --git a/packages/coding-agent/src/core/tools/tool-definition-wrapper.ts b/packages/coding-agent/src/core/tools/tool-definition-wrapper.ts index c9da525a..295ba904 100644 --- a/packages/coding-agent/src/core/tools/tool-definition-wrapper.ts +++ b/packages/coding-agent/src/core/tools/tool-definition-wrapper.ts @@ -13,8 +13,8 @@ export function wrapToolDefinition( parameters: definition.parameters, prepareArguments: definition.prepareArguments, executionMode: definition.executionMode, - execute: (toolCallId, params, signal, onUpdate) => - definition.execute(toolCallId, params, signal, onUpdate, ctxFactory?.() as ExtensionContext), + execute: (toolCallId, params, signal, onUpdate, ctx?: ExtensionContext) => + definition.execute(toolCallId, params, signal, onUpdate, ctx ?? (ctxFactory?.() as ExtensionContext)), }; } diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 294d60b9..4285297e 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -1783,6 +1783,7 @@ export class InteractiveMode { sessionManager: this.sessionManager, modelRegistry: extensionRunner.getModelRegistry(), model: this.session.model, + thinkingLevel: this.session.thinkingLevel, isIdle: () => this.session.isIdle, isProjectTrusted: () => this.settingsManager.isProjectTrusted(), signal: this.session.agent.signal, diff --git a/packages/coding-agent/test/agent-session-dynamic-tools.test.ts b/packages/coding-agent/test/agent-session-dynamic-tools.test.ts index 88871ac8..719550fe 100644 --- a/packages/coding-agent/test/agent-session-dynamic-tools.test.ts +++ b/packages/coding-agent/test/agent-session-dynamic-tools.test.ts @@ -8,6 +8,7 @@ import { DefaultResourceLoader } from "../src/core/resource-loader.ts"; import { createAgentSession } from "../src/core/sdk.ts"; import { SessionManager } from "../src/core/session-manager.ts"; import { SettingsManager } from "../src/core/settings-manager.ts"; +import { createBashTool } from "../src/core/tools/bash.ts"; describe("AgentSession dynamic tool registration", () => { let tempDir: string; @@ -25,6 +26,76 @@ describe("AgentSession dynamic tool registration", () => { } }); + it("exposes session state before custom bash spawn hooks and supports opting out", async () => { + const settingsManager = SettingsManager.create(tempDir, agentDir); + const sessionManager = SessionManager.create(tempDir, join(agentDir, "sessions"), { id: "bash-env-test" }); + let sessionEnv: NodeJS.ProcessEnv | undefined; + let optedOutEnv: NodeJS.ProcessEnv | undefined; + const resourceLoader = new DefaultResourceLoader({ + cwd: tempDir, + agentDir, + settingsManager, + extensionFactories: [ + (pi) => { + pi.registerTool( + createBashTool(tempDir, { + spawnHook: (ctx) => { + sessionEnv = ctx.env; + return ctx; + }, + }), + ); + pi.registerTool({ + ...createBashTool(tempDir, { + exposeSessionEnvironment: false, + spawnHook: (ctx) => { + optedOutEnv = ctx.env; + return ctx; + }, + }), + name: "bash_without_session_env", + label: "bash without session env", + }); + }, + ], + }); + await resourceLoader.reload(); + + const model = getModel("anthropic", "claude-sonnet-4-5")!; + const { session } = await createAgentSession({ + cwd: tempDir, + agentDir, + model, + thinkingLevel: "high", + settingsManager, + sessionManager, + resourceLoader, + }); + + const bashTool = session.agent.state.tools.find((tool) => tool.name === "bash")!; + expect(session.systemPrompt).toContain( + "Inspect PI_* environment variables for current model and session details.", + ); + await bashTool.execute("bash-env", { command: "printf ok" }); + expect(sessionEnv).toMatchObject({ + PI_SESSION_ID: session.sessionId, + PI_SESSION_FILE: session.sessionFile, + PI_PROVIDER: model.provider, + PI_MODEL: model.id, + PI_REASONING_LEVEL: session.thinkingLevel, + }); + + const optedOutBashTool = session.agent.state.tools.find((tool) => tool.name === "bash_without_session_env")!; + await optedOutBashTool.execute("bash-no-env", { command: "printf ok" }); + expect(optedOutEnv).not.toHaveProperty("PI_SESSION_ID"); + expect(optedOutEnv).not.toHaveProperty("PI_SESSION_FILE"); + expect(optedOutEnv).not.toHaveProperty("PI_PROVIDER"); + expect(optedOutEnv).not.toHaveProperty("PI_MODEL"); + expect(optedOutEnv).not.toHaveProperty("PI_REASONING_LEVEL"); + + session.dispose(); + }); + it("refreshes tool registry when tools are registered after initialization", async () => { const settingsManager = SettingsManager.create(tempDir, agentDir); const sessionManager = SessionManager.inMemory(); diff --git a/packages/coding-agent/test/sdk-session-manager.test.ts b/packages/coding-agent/test/sdk-session-manager.test.ts index 9cdf7774..d60ea9d2 100644 --- a/packages/coding-agent/test/sdk-session-manager.test.ts +++ b/packages/coding-agent/test/sdk-session-manager.test.ts @@ -92,4 +92,40 @@ describe("createAgentSession session manager defaults", () => { session.dispose(); }); + + it("exposes current session state to the built-in bash tool", async () => { + const model = getModel("anthropic", "claude-sonnet-4-5"); + expect(model).toBeTruthy(); + + const { session } = await createAgentSession({ + cwd, + agentDir, + model: model!, + thinkingLevel: "high", + }); + expect(session.sessionFile).toBeTruthy(); + expect(session.systemPrompt).toContain( + "Inspect PI_* environment variables for current model and session details.", + ); + + const bashTool = session.agent.state.tools.find((tool) => tool.name === "bash"); + expect(bashTool).toBeTruthy(); + const result = await bashTool!.execute("test", { + command: `printf '%s\\n' "$PI_SESSION_ID" "$PI_SESSION_FILE" "$PI_PROVIDER" "$PI_MODEL" "$PI_REASONING_LEVEL"`, + }); + const output = result.content + .filter((item): item is { type: "text"; text: string } => item.type === "text") + .map((item) => item.text) + .join(""); + + expect(output.trim().split("\n")).toEqual([ + session.sessionId, + session.sessionFile, + model!.provider, + model!.id, + session.thinkingLevel, + ]); + + session.dispose(); + }); }); diff --git a/packages/coding-agent/test/system-prompt.test.ts b/packages/coding-agent/test/system-prompt.test.ts index 82def227..6e38fcb0 100644 --- a/packages/coding-agent/test/system-prompt.test.ts +++ b/packages/coding-agent/test/system-prompt.test.ts @@ -56,6 +56,7 @@ describe("buildSystemPrompt", () => { expect(prompt).toContain( "- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory", ); + expect(prompt).toContain("environment variables (docs/environment-variables.md)"); }); }); diff --git a/packages/coding-agent/test/tool-execution-component.test.ts b/packages/coding-agent/test/tool-execution-component.test.ts index c0588c52..09661710 100644 --- a/packages/coding-agent/test/tool-execution-component.test.ts +++ b/packages/coding-agent/test/tool-execution-component.test.ts @@ -142,7 +142,7 @@ describe("ToolExecutionComponent parity", () => { return { exitCode: 0 }; }, }; - const tool = createBashToolDefinition(process.cwd(), { operations }); + const tool = createBashToolDefinition(process.cwd(), { operations, exposeSessionEnvironment: false }); const promise = tool.execute( "tool-bash-1", { command: "sleep 10" }, @@ -163,7 +163,7 @@ describe("ToolExecutionComponent parity", () => { return { exitCode: 0 }; }, }; - const tool = createBashToolDefinition(process.cwd(), { operations }); + const tool = createBashToolDefinition(process.cwd(), { operations, exposeSessionEnvironment: false }); const result = await tool.execute( "tool-bash-1b", { command: "generate output" },