Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1414142
feat(coding-agent): add async-by-default bash() to the kernel runtime
snimu Aug 21, 2026
d4fc6ab
chore(coding-agent): tighten bash() comments and changelog fragment
snimu Aug 21, 2026
8a04223
Merge remote-tracking branch 'origin/main' into repl/01-bash-tool
snimu Aug 23, 2026
b00a176
fix(coding-agent): address bash() review findings — exact tail retent…
snimu Aug 23, 2026
dcc7328
fix(coding-agent): tree-kill bash() process trees on Windows and make…
snimu Aug 23, 2026
07ace49
fix(coding-agent): carry bash() status pipe as stdin so strict-POSIX …
snimu Aug 24, 2026
2f9b740
fix(coding-agent): close status fds when wake-pipe creation fails and…
snimu Aug 24, 2026
938e68c
Merge remote-tracking branch 'origin/main' into repl/01-bash-tool
snimu Aug 24, 2026
f4b4196
fix(coding-agent): kill full process trees in the Windows orphan reap…
snimu Aug 24, 2026
be2f706
fix(coding-agent): kill one-shot await bash() on cancel, resolve Wind…
snimu Aug 24, 2026
4d57859
fix(coding-agent): confirm group exit before a cancelled one-shot bas…
snimu Aug 24, 2026
161474b
fix(coding-agent): shield the cancelled-await bash() confirm-exit fro…
snimu Aug 24, 2026
b83e09b
fix(coding-agent): flag the pump's read-to-commit window so the bash(…
snimu Aug 24, 2026
b8c9368
Merge remote-tracking branch 'origin/main' into repl/01-bash-tool
snimu Aug 25, 2026
69c8b4a
fix(coding-agent): resolve taskkill via absolute System32 path in the…
snimu Aug 25, 2026
b244459
Merge remote-tracking branch 'origin/main' into repl/01-bash-tool
snimu Aug 25, 2026
1dda76b
fix(coding-agent): write the inactive orphan-journal record only afte…
snimu Aug 25, 2026
60a0ca3
fix(coding-agent): count an already-exited leader as delivered in the…
snimu Aug 25, 2026
e2ffa3a
fix(coding-agent): resolve trusted absolute shell host-side and close…
snimu Aug 25, 2026
fb61ffc
fix(coding-agent): best-effort-kill pid-only orphan records in daemon…
snimu Aug 25, 2026
486d2e4
fix(coding-agent): resolve the kernel bash shell only from trusted ab…
snimu Aug 25, 2026
2c6f451
fix(coding-agent): contain Windows bash children with kill-on-close j…
snimu Aug 25, 2026
2ca2e03
fix(coding-agent): fall back to taskkill when job termination fails
snimu Aug 25, 2026
a0f185e
fix(coding-agent): start Windows bash children suspended and fail clo…
snimu Aug 26, 2026
933f12b
Merge remote-tracking branch 'origin/main' into repl/01-bash-tool
snimu Aug 26, 2026
8d9e4b7
fix(coding-agent): kill the whole tree for enriched orphan records on…
snimu Aug 26, 2026
22f878d
fix(coding-agent): skip the liveness check when the orphan identity i…
snimu Aug 26, 2026
01cd753
fix(coding-agent): journal Windows bash() children before job assignm…
snimu Aug 26, 2026
ec78cd7
fix(coding-agent): pre-create the Windows bash() job before spawn and…
snimu Aug 26, 2026
7d0fe21
fix(coding-agent): create Windows bash() children atomically inside t…
snimu Aug 26, 2026
4e68837
chore(coding-agent): compress the atomic-job-association comments to …
snimu Aug 26, 2026
e31c652
fix(coding-agent): retain the Windows bash() process handle through j…
snimu Aug 26, 2026
242b34c
fix(coding-agent): serialize Windows bash() abort cleanup under the k…
snimu Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/coding-agent/.changes/repl-01-bash-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Added an async-by-default `bash()` callable to the kernel runtime: it returns a live handle immediately (pid/tail/poll/kill/await), bounds in-memory output, and enrolls children in the orphan-process journal so kernel teardown reaps them.
- Fixed bash() orphan-journal writes marking a child inactive even when the kill signal was not delivered; the record now stays active on delivery failure so the host reaper still owns the process (on Windows a shell that already exited counts as delivered, so clean exits still retire their record).
8 changes: 8 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [Unreleased]

- Fixed bash() cells failing under strict-POSIX shells (dash) when the status pipe landed on a multi-digit fd.
- Fixed two bash() spawn races: status-channel fds no longer leak when pipe creation fails mid-setup, and a status-socket gate keeps the command from starting until its pid is journaled (a kernel kill in that window now stops the child instead of orphaning it past the reaper).
- Fixed the Windows orphan reaper killing only the journaled bash() shell pid; it now uses taskkill /T so descendants die with the tree, matching the in-kernel bash() kill paths, and resolves taskkill via an absolute System32 path (with NoDefaultCurrentDirectoryInExePath) so a planted CWD taskkill.exe cannot hijack cleanup.
- Hardened bash(): cancelling `await bash(cmd)` now kills the command's process group (background handles are unaffected), Windows helper binaries resolve via absolute System32 paths, kill() retries taskkill for already-reaped Windows trees, and orphan-journal enrollment fails closed when configured.
- Hardened bash() further: the host now injects an absolute default shell into the kernel (no PATH lookup; /bin/bash else /bin/sh on POSIX), macOS start-id lookup uses /bin/ps, Windows journal records are written pid-first so a kernel crash during the start-id query cannot leak the child (crash reapers best-effort-kill such records), and Windows worker-teardown orphan kills go through hardened taskkill /T.

## [0.8.0] - 2026-08-21

- Fixed an OAuth login that finishes after its server was retargeted arming the old-endpoint token against the new URL: credentials are endpoint-bound at issuance, and the host and kernel only use a token bound to the configured endpoint. **Breaking**: generic MCP OAuth credentials stored before this release lack the binding and require one `/mcp login <server>`.
Expand Down
16 changes: 5 additions & 11 deletions packages/coding-agent/src/cli/owned-session-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { AgentSessionRuntime } from "../core/agent-session-runtime.js";
import {
clearOrphanProcessJournal,
isOrphanProcessIdentityCurrent,
killOrphanProcess,
ORPHAN_PROCESS_JOURNAL_ENV,
readActiveOrphanProcesses,
} from "../core/orphan-process-journal.js";
Expand Down Expand Up @@ -297,19 +298,12 @@ export async function runOwnedSessionWorkerFrontend(
}
}
for (const orphan of readActiveOrphanProcesses(orphanProcessJournalPath, workerPid)) {
if (!isOrphanProcessIdentityCurrent(orphan)) {
// Pid-only records = kernel crashed before the start-id landed: best-effort
// kill; blast radius bounded by ownerPid scoping and the journal clear below.
if (orphan.processStartId !== undefined && !isOrphanProcessIdentityCurrent(orphan)) {
continue;
}
const { pid } = orphan;
try {
process.kill(process.platform === "win32" ? pid : -pid, "SIGKILL");
} catch {
try {
process.kill(pid, "SIGKILL");
} catch {
// The detached resource may already have exited.
}
}
killOrphanProcess(orphan.pid);
}
clearOrphanProcessJournal(orphanProcessJournalPath);
};
Expand Down
2 changes: 2 additions & 0 deletions packages/coding-agent/src/core/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8956,6 +8956,8 @@ export class AgentSession {
const notifyRestore = !this._ipythonRuntimeBuilt;
this._ipythonKernelProvisioner = new IpythonKernelProvisioner(this._cwd, {
env: this._rlmKernelEnv(),
commandPrefix: this.settingsManager.getShellCommandPrefix(),
shellPath: this.settingsManager.getShellPath(),
sessionId: this.sessionId,
hostHandlers: this._createKernelHostHandlers(),
pythonSkills,
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/core/kernel/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const REQUIRED_HARNESS_METHODS = [
"delete_prompt_note",
"record_refinement",
];
const RUNTIME_READY_CHECK = `import inspect; import rlm; from rlm import McpIntegration; import rlm.mcp as mcp; from rlm.harness import HarnessEntry; _harness_methods = ${JSON.stringify(REQUIRED_HARNESS_METHODS)}; assert callable(mcp.list_tools); assert callable(mcp.call_tool); assert hasattr(rlm, 'run'); assert callable(rlm); assert hasattr(rlm, 'rlm'); assert callable(rlm.rlm); assert callable(rlm.host_request); assert callable(rlm.find_models); assert callable(rlm.rlm.find_models); assert hasattr(rlm, 'harness'); assert hasattr(rlm, 'get_harness_state'); assert hasattr(rlm.rlm, 'harness'); assert hasattr(rlm.rlm, 'get_harness_state'); assert all(callable(getattr(_harness, _method, None)) for _harness in (rlm.harness, rlm.rlm.harness) for _method in _harness_methods); assert 'reference' in HarnessEntry.__dataclass_fields__; assert 'scope' in HarnessEntry.__dataclass_fields__; assert 'reference' in inspect.signature(rlm.harness.create_skill).parameters; assert 'reference' in inspect.signature(rlm.harness.update_skill).parameters; assert 'global_' in inspect.signature(rlm.harness.create_memory).parameters; assert 'global_' in inspect.signature(rlm.get_harness_state).parameters; assert not hasattr(rlm, 'background'); assert not hasattr(rlm.rlm, 'background')`;
const RUNTIME_READY_CHECK = `import inspect; import rlm; from rlm import McpIntegration; import rlm.mcp as mcp; from rlm.harness import HarnessEntry; _harness_methods = ${JSON.stringify(REQUIRED_HARNESS_METHODS)}; assert callable(mcp.list_tools); assert callable(mcp.call_tool); assert hasattr(rlm, 'run'); assert callable(rlm); assert hasattr(rlm, 'rlm'); assert callable(rlm.rlm); assert callable(rlm.host_request); assert callable(rlm.find_models); assert callable(rlm.rlm.find_models); assert hasattr(rlm, 'harness'); assert hasattr(rlm, 'get_harness_state'); assert hasattr(rlm.rlm, 'harness'); assert hasattr(rlm.rlm, 'get_harness_state'); assert all(callable(getattr(_harness, _method, None)) for _harness in (rlm.harness, rlm.rlm.harness) for _method in _harness_methods); assert 'reference' in HarnessEntry.__dataclass_fields__; assert 'scope' in HarnessEntry.__dataclass_fields__; assert 'reference' in inspect.signature(rlm.harness.create_skill).parameters; assert 'reference' in inspect.signature(rlm.harness.update_skill).parameters; assert 'global_' in inspect.signature(rlm.harness.create_memory).parameters; assert 'global_' in inspect.signature(rlm.get_harness_state).parameters; assert not hasattr(rlm, 'background'); assert not hasattr(rlm.rlm, 'background'); from rlm.bash import BashHandle, BashResult; assert callable(rlm.bash); assert all(callable(getattr(BashHandle, _m, None)) for _m in ('tail', 'output', 'poll', 'kill')); assert {'exit_code', 'output', 'duration'} <= set(BashResult.__dataclass_fields__)`;
const BOOTSTRAP_VERSION_FILE = ".bootstrap-version";
const BOOTSTRAP_LOCK_NAME = ".bootstrap.lock";
const BOOTSTRAP_LOCK_RETRY_MS = 100;
Expand Down
18 changes: 15 additions & 3 deletions packages/coding-agent/src/core/kernel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { setTimeout as sleep } from "node:timers/promises";
import { registerSessionResourceCleanup } from "@earendil-works/pi-ai";
import { v4 as uuid } from "uuid";
import { Dealer, Subscriber } from "zeromq";
import { recordOrphanProcessState } from "../orphan-process-journal.js";
import { reapKernelOrphanProcesses, recordOrphanProcessState } from "../orphan-process-journal.js";
import { ensureKernelPython, type KernelBootstrapProgressHandler, type KernelPythonSkill } from "./bootstrap.js";
import { type ForkedKernelHandle, ForkServerUnavailable, forkKernel, isForkServerEnabled } from "./fork-server.js";
import {
Expand Down Expand Up @@ -708,7 +708,7 @@ export class KernelManager {
cwd: this.options.cwd,
// Applied fresh in the child (the template's env snapshot may be stale).
// No JPY_PARENT_PID: forked children watch the forkserver by getppid().
env: { ...process.env, ...this.options.env },
env: { ...process.env, ...this.options.env, PRIME_AGENT_KERNEL_OWNER_PID: String(process.pid) },
});
if (this.startStale(generation)) {
// Nobody owns this kernel; the protocol kill is id-keyed and safe.
Expand Down Expand Up @@ -742,7 +742,12 @@ export class KernelManager {
const kernel = spawn(python, ["-m", "ipykernel_launcher", "-f", connection.path], {
cwd: this.options.cwd,
// ipykernel's parent poller exits the kernel if this pid dies (covers SIGKILL of the owner).
env: { ...process.env, ...this.options.env, JPY_PARENT_PID: String(process.pid) },
env: {
...process.env,
...this.options.env,
JPY_PARENT_PID: String(process.pid),
PRIME_AGENT_KERNEL_OWNER_PID: String(process.pid),
},
stdio: ["ignore", "pipe", "pipe"],
});
this.kernel = kernel;
Expand Down Expand Up @@ -1542,6 +1547,9 @@ export class KernelManager {
}
// Same rule as the forked branch below: inactive only when the signal proved the pid still ours.
if (directPid !== undefined && signaled) recordOrphanProcessState(directPid, false);
// A killed/crashed kernel cannot run its own shutdown hook, so the host
// reaps the bash() process groups it journaled under this kernel pid.
if (directPid !== undefined) reapKernelOrphanProcesses(directPid);
} else if (this.forkedKernel) {
const forked = this.forkedKernel;
// The journal is raw-pid keyed, so inactive is written only on "signaled"
Expand All @@ -1553,8 +1561,12 @@ export class KernelManager {
.kill(killSignal === "SIGKILL" ? "KILL" : "TERM")
.then((outcome) => {
if (outcome === "signaled") recordOrphanProcessState(forked.pid, false);
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
// Reap again: bash children journaled while the async kill was in flight.
reapKernelOrphanProcesses(forked.pid);
})
.catch(() => this.appendKernelDiagnostic("forkserver kill unconfirmed; leaving orphan record active"));
// Same reaping as the direct branch: bash() children journaled under this kernel pid.
reapKernelOrphanProcesses(forked.pid);
}
this.kernel = undefined;
this.forkedKernel = undefined;
Expand Down
8 changes: 4 additions & 4 deletions packages/coding-agent/src/core/kernel/state-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ def _prime_agent_snapshot_state():
ip = None
ns = ip.user_ns if ip is not None else _b.globals()
hidden = _b.set(_b.getattr(ip, "user_ns_hidden", {}) or {}) if ip is not None else _b.set()
# rlm, mcp, and asyncio are re-created by the kernel bootstrap on every
# start; never snapshot them.
always_skip = {"rlm", "mcp", "asyncio", "In", "Out", "get_ipython", "exit", "quit", "open"}
# rlm, mcp, bash, and asyncio are re-created by the kernel bootstrap on
# every start; never snapshot them.
always_skip = {"rlm", "mcp", "bash", "asyncio", "In", "Out", "get_ipython", "exit", "quit", "open"}

class SnapshotSizeLimitExceeded(_b.Exception):
pass
Expand Down Expand Up @@ -262,7 +262,7 @@ def _prime_agent_list_state_names():
ip = None
ns = ip.user_ns if ip is not None else _b.globals()
hidden = _b.set(_b.getattr(ip, "user_ns_hidden", {}) or {}) if ip is not None else _b.set()
always_skip = {"rlm", "mcp", "asyncio", "In", "Out", "get_ipython", "exit", "quit", "open"}
always_skip = {"rlm", "mcp", "bash", "asyncio", "In", "Out", "get_ipython", "exit", "quit", "open"}
names = []
for name in _b.list(ns.keys()):
if name.startswith("_") or name in hidden or name in always_skip:
Expand Down
82 changes: 77 additions & 5 deletions packages/coding-agent/src/core/orphan-process-journal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { spawnSync } from "node:child_process";
import { closeSync, fsyncSync, openSync, readFileSync, rmSync, writeSync } from "node:fs";
import { win32 } from "node:path";
import { getProcessStartId } from "./session-lease.js";

export const ORPHAN_PROCESS_JOURNAL_ENV = "PRIME_AGENT_INTERNAL_ORPHAN_PROCESS_JOURNAL";
Expand All @@ -7,14 +9,18 @@ interface OrphanProcessRecord {
version: 1;
pid: number;
ownerPid: number;
/** Set on records written by a kernel (e.g. bash() children) so the host can reap per kernel. */
kernelPid?: number;
processStartId?: string;
active: boolean;
recordedAt: string;
}

export interface ActiveOrphanProcess {
pid: number;
processStartId: string;
kernelPid?: number;
/** Missing on pid-only records written before the start-id query completed (Windows pre-enrollment). */
processStartId?: string;
}

export function recordOrphanProcessState(pid: number, active: boolean): void {
Expand Down Expand Up @@ -75,18 +81,84 @@ export function readActiveOrphanProcesses(path: string, ownerPid: number): Activ
// A crash can truncate only the final append.
}
}
// Pid-only actives (no processStartId) come from the Windows pre-enrollment
// window; the enriched record normally supersedes them within seconds.
return [...latest.values()]
.filter(
(record): record is OrphanProcessRecord & { processStartId: string } =>
record.active && typeof record.processStartId === "string",
(record) =>
record.active && (record.processStartId === undefined || typeof record.processStartId === "string"),
)
.map((record) => ({ pid: record.pid, processStartId: record.processStartId }));
.map((record) => ({
pid: record.pid,
...(Number.isInteger(record.kernelPid) ? { kernelPid: record.kernelPid } : {}),
...(typeof record.processStartId === "string" ? { processStartId: record.processStartId } : {}),
}));
}

export function isOrphanProcessIdentityCurrent(orphan: ActiveOrphanProcess): boolean {
return getProcessStartId(orphan.pid) === orphan.processStartId;
// Pid-only records can never claim identity (undefined === undefined must not match).
return orphan.processStartId !== undefined && getProcessStartId(orphan.pid) === orphan.processStartId;
Comment thread
snimu marked this conversation as resolved.
}

export function clearOrphanProcessJournal(path: string): void {
rmSync(path, { force: true });
}

// Kills still-active bash() children journaled by the given kernel pid; sibling kernels' records are untouched.
export function reapKernelOrphanProcesses(kernelPid: number): void {
const path = process.env[ORPHAN_PROCESS_JOURNAL_ENV];
if (!path || !Number.isInteger(kernelPid) || kernelPid <= 0) {
return;
}
let orphans: ActiveOrphanProcess[];
try {
orphans = readActiveOrphanProcesses(path, process.pid);
} catch {
return;
}
for (const orphan of orphans) {
if (orphan.kernelPid !== kernelPid || orphan.pid === kernelPid) {
continue;
}
// Pid-only records = kernel crashed before the start-id landed: best-effort
// kill; blast radius bounded by ownerPid + kernelPid scoping.
if (orphan.processStartId !== undefined && !isOrphanProcessIdentityCurrent(orphan)) {
continue;
}
// Inactive only after a delivered signal; a stale record is neutralized by the startId check.
if (killOrphanProcess(orphan.pid)) {
recordOrphanProcessState(orphan.pid, false);
}
}
}

// Hardened cross-platform tree kill for journaled orphans: absolute System32
// taskkill /T on win32 (a bare name could resolve a planted CWD taskkill.exe),
// process-group then pid SIGKILL elsewhere.
export function killOrphanProcess(pid: number): boolean {
if (process.platform === "win32") {
// In-kernel bash() kill paths use taskkill /T; the reaper must kill the same tree, not just the shell pid.
const result = spawnSync(
win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"),
["/F", "/T", "/PID", String(pid)],
{
stdio: "ignore",
timeout: 10_000,
env: { ...process.env, NoDefaultCurrentDirectoryInExePath: "1" },
},
);
return result.status === 0;
}
try {
process.kill(-pid, "SIGKILL");
return true;
} catch {
try {
process.kill(pid, "SIGKILL");
return true;
} catch {
// The orphan may already have exited.
}
}
return false;
}
2 changes: 2 additions & 0 deletions packages/coding-agent/src/core/prompts/rlm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const IPYTHON_CONTROL_PROMPT = [
"",
"When running shell commands from IPython, use `%%bash` cells. If you use `%%bash`, it must be the first line of the code cell: no comments, spaces, blank lines, imports, or Python statements before it. Avoid `!cmd` shell escapes for project commands so shell behavior is explicit and multi-line commands share one shell context.",
"",
"`bash(command)` starts a shell command in the background and returns a handle immediately: `h = bash('npm test')`. Use `h.pid` / `h.running` for liveness, `h.tail(n)` / `h.output()` for combined stdout+stderr so far, `h.poll()` for a non-blocking result, `h.kill()` to terminate (SIGTERM, escalating to SIGKILL; on Windows kill() uses taskkill /T and detached or reparented descendants may survive), and `await h` (or `await bash('cmd')`) for the completed result with exit_code, output, and duration. Prefer bash() for long-running commands so the turn keeps working; `%%bash` remains the way to run quick inline shell with cell-visible output.",
"",
"Important: do not install dependencies into the IPython kernel just to make an external project import or run there. If a project import, test, script, CLI, or dependency check is needed, run it through that project's own environment and normal command interface. For example, in a Python repo use its documented commands, `uv run ...`, `.venv/bin/python ...`, or the active project interpreter from the repo root. Treat failures from that native environment as the relevant result.",
"",
"Use Python for reading, searching, and editing files — it gives you reusable variables you can slice, filter, and act on without re-reading. Always assign read/search results to named variables so you can revisit them later.",
Expand Down
16 changes: 15 additions & 1 deletion packages/coding-agent/src/core/tools/ipython.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AgentTool } from "@earendil-works/pi-agent-core";
import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
import { type Static, Type } from "typebox";
import { IMAGE_MIME_TYPES } from "../../utils/mime.js";
import { resolveKernelBashShell } from "../../utils/shell.js";
import type { ExtensionContext, ToolDefinition } from "../extensions/types.js";
import { withKernelBootPermit } from "../kernel/boot-gate.js";
import type { KernelBootstrapProgressHandler } from "../kernel/bootstrap.js";
Expand Down Expand Up @@ -36,6 +37,7 @@ except Exception:
try:
import rlm as _prime_agent_rlm_module
rlm = _prime_agent_rlm_module.rlm
bash = _prime_agent_rlm_module.bash
import rlm.mcp as mcp
mcp.install_shutdown_hook()
except Exception as _prime_agent_rlm_error:
Expand Down Expand Up @@ -66,6 +68,9 @@ except Exception as _prime_agent_rlm_error:
return await self.run(prompt, **kwargs)

rlm = _PrimeAgentMissingRlm()

def bash(command):
rlm._raise_missing()
`.trim();

export function buildRlmBootstrapCode(pythonSkills: readonly PythonSkillRuntimeInfo[] = []): string {
Expand Down Expand Up @@ -483,10 +488,19 @@ export class IpythonKernelProvisioner {
);
}
const snapshotDir = this.options?.snapshotDir;
// Always inject an absolute trusted shell (undefined only on win32
// without bash, where the runtime's teaching error fires instead).
const shellPath = resolveKernelBashShell(this.options?.shellPath);
const commandPrefix = this.options?.commandPrefix;
const m = new KernelManager({
python: this.options?.python,
cwd: this.cwd,
env: this.options?.env,
// bash() reads these so it applies the same shell settings as %%bash cells.
env: {
...this.options?.env,
...(shellPath ? { PRIME_AGENT_BASH_SHELL: shellPath } : {}),
...(commandPrefix ? { PRIME_AGENT_BASH_COMMAND_PREFIX: commandPrefix } : {}),
},
sessionId: this.options?.sessionId,
hostHandlers: this.options?.hostHandlers,
pythonSkills: this.options?.pythonSkills,
Expand Down
Loading
Loading