feat(coding-agent): expose session metadata to bash tools (#6967)
This commit is contained in:
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user