feat(coding-agent): expose session metadata to bash tools (#6967)

This commit is contained in:
Armin Ronacher
2026-07-22 20:15:02 +02:00
committed by GitHub
parent c55ae2faa5
commit bb3d7d399c
16 changed files with 278 additions and 27 deletions
@@ -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();
@@ -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();
});
});
@@ -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)");
});
});
@@ -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" },