fix(coding-agent): scoped python kernel cleanup to sessions
scoped retained-kernel ownership to agent sessions and cleaned it up on session disposal cleaned up warmed python owners on session startup failure and rejected new direct and tool-based python starts during disposal, including async hook, preflight, and warmup races
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import { describe, expect, it, vi } from "bun:test";
|
||||
import { afterEach, describe, expect, it, vi } from "bun:test";
|
||||
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import * as pythonExecutor from "@oh-my-pi/pi-coding-agent/ipy/executor";
|
||||
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
||||
import { PythonTool } from "@oh-my-pi/pi-coding-agent/tools/python";
|
||||
import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
|
||||
function createSession(cwd: string): ToolSession {
|
||||
function createSession(cwd: string, kernelOwnerId?: string): ToolSession {
|
||||
return {
|
||||
cwd,
|
||||
hasUI: false,
|
||||
getSessionFile: () => `${cwd}/session-file.jsonl`,
|
||||
getSessionSpawns: () => "*",
|
||||
getPythonKernelOwnerId: () => kernelOwnerId ?? null,
|
||||
settings: Settings.isolated({
|
||||
"lsp.formatOnWrite": true,
|
||||
"bashInterceptor.enabled": true,
|
||||
@@ -21,9 +22,15 @@ function createSession(cwd: string): ToolSession {
|
||||
}
|
||||
|
||||
describe("python tool execution", () => {
|
||||
it("passes kernel options from settings and args", async () => {
|
||||
afterEach(() => {
|
||||
pythonExecutor.resetPreludeDocsCache();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("passes kernel owner and kernel options from settings and args", async () => {
|
||||
const tempDir = TempDir.createSync("@python-tool-");
|
||||
vi.spyOn(pythonExecutor, "warmPythonEnvironment").mockResolvedValue({ ok: true, docs: [] });
|
||||
vi.spyOn(pythonExecutor, "getPreludeDocs").mockReturnValue([]);
|
||||
const warmupSpy = vi.spyOn(pythonExecutor, "warmPythonEnvironment").mockResolvedValue({ ok: true, docs: [] });
|
||||
const executeSpy = vi.spyOn(pythonExecutor, "executePython").mockResolvedValue({
|
||||
output: "ok",
|
||||
exitCode: 0,
|
||||
@@ -37,7 +44,8 @@ describe("python tool execution", () => {
|
||||
stdinRequested: false,
|
||||
});
|
||||
|
||||
const tool = new PythonTool(createSession(tempDir.path()));
|
||||
const kernelOwnerId = "owner-123";
|
||||
const tool = new PythonTool(createSession(tempDir.path(), kernelOwnerId));
|
||||
const result = await tool.execute(
|
||||
"call-id",
|
||||
{ cells: [{ code: "print('hi')" }], timeout: 5, cwd: tempDir.path(), reset: true },
|
||||
@@ -46,6 +54,14 @@ describe("python tool execution", () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(warmupSpy).toHaveBeenCalledWith(
|
||||
tempDir.path(),
|
||||
`session:${tempDir.path()}/session-file.jsonl:cwd:${tempDir.path()}`,
|
||||
true,
|
||||
`${tempDir.path()}/session-file.jsonl`,
|
||||
kernelOwnerId,
|
||||
expect.any(AbortSignal),
|
||||
);
|
||||
expect(executeSpy).toHaveBeenCalledWith(
|
||||
"print('hi')",
|
||||
expect.objectContaining({
|
||||
@@ -54,6 +70,7 @@ describe("python tool execution", () => {
|
||||
signal: expect.any(AbortSignal),
|
||||
sessionFile: `${tempDir.path()}/session-file.jsonl`,
|
||||
sessionId: `session:${tempDir.path()}/session-file.jsonl:cwd:${tempDir.path()}`,
|
||||
kernelOwnerId,
|
||||
kernelMode: "per-call",
|
||||
useSharedGateway: true,
|
||||
reset: true,
|
||||
@@ -62,7 +79,6 @@ describe("python tool execution", () => {
|
||||
const text = result.content.find(item => item.type === "text")?.text;
|
||||
expect(text).toBe("ok");
|
||||
|
||||
executeSpy.mockRestore();
|
||||
tempDir.removeSync();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user