fix(cli): restored piped session persistence

- Treated undefined stdin TTY state as redirected input.

- Honored PI_CODING_AGENT_SESSION_DIR with CLI precedence.

Fixes #7378
This commit is contained in:
roboomp
2026-08-02 11:33:58 +00:00
parent 06343fef42
commit 72d8d4eaca
5 changed files with 47 additions and 4 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed redirected stdin being ignored when Bun reports a pipe with an undefined `isTTY`, so JSON/print sessions now process piped prompts and persist them under `--session-dir` or `PI_CODING_AGENT_SESSION_DIR` as requested ([#7378](https://github.com/can1357/oh-my-pi/issues/7378)).
## [17.2.4] - 2026-08-01
### Added
+2 -1
View File
@@ -1,7 +1,7 @@
/**
* CLI argument parsing and help display
*/
import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
import { $env, APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
import chalk from "chalk";
import { CLI_THINKING_LEVELS, type ConfiguredThinkingLevel, parseCliThinkingLevel } from "../thinking";
import { BUILTIN_TOOL_NAMES, HIDDEN_TOOL_NAMES, normalizeToolNames } from "../tools/builtin-names";
@@ -145,6 +145,7 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
fileArgs: [],
unknownFlags: new Map(),
unrecognizedFlags: [],
sessionDir: $env.PI_CODING_AGENT_SESSION_DIR || undefined,
};
// `--` ends option parsing (POSIX end-of-options). Everything after it is
+3 -2
View File
@@ -188,8 +188,9 @@ function applyAcpDefaultSettingOverrides(targetSettings: Settings = settings): v
applyDefaultSettingOverrides(HOST_DEFAULTED_SETTING_PATHS, targetSettings);
}
async function readPipedInput(): Promise<string | undefined> {
if (process.stdin.isTTY !== false) return undefined;
/** Reads a non-TTY stdin stream as prompt text. */
export async function readPipedInput(): Promise<string | undefined> {
if (process.stdin.isTTY === true) return undefined;
// stdin is a pipe: a producer that never writes nor closes would block
// startup forever with zero output. Say what we're blocked on after 1s.
const notice = setTimeout(() => {
@@ -56,6 +56,23 @@ describe("OPTIONAL_VALUE_FLAGS table is honored by args.ts parseArgs", () => {
}
});
describe("--session-dir", () => {
it("uses PI_CODING_AGENT_SESSION_DIR unless the CLI flag overrides it", () => {
const previous = Bun.env.PI_CODING_AGENT_SESSION_DIR;
Bun.env.PI_CODING_AGENT_SESSION_DIR = "/env/sessions";
try {
expect(parseArgs([]).sessionDir).toBe("/env/sessions");
expect(parseArgs(["--session-dir", "/cli/sessions"]).sessionDir).toBe("/cli/sessions");
} finally {
if (previous === undefined) {
delete Bun.env.PI_CODING_AGENT_SESSION_DIR;
} else {
Bun.env.PI_CODING_AGENT_SESSION_DIR = previous;
}
}
});
});
describe("--tools legacy aliases", () => {
it("maps search and find to grep and glob", () => {
const result = parseArgs(["--tools", "search,find,grep"]);
@@ -2,7 +2,11 @@ import { afterEach, describe, expect, it, vi } from "bun:test";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { applyResolvedSystemPromptInputs, submitInteractiveInput } from "@oh-my-pi/pi-coding-agent/main";
import {
applyResolvedSystemPromptInputs,
readPipedInput,
submitInteractiveInput,
} from "@oh-my-pi/pi-coding-agent/main";
import type { SubmittedUserInput } from "@oh-my-pi/pi-coding-agent/modes/types";
import type { CreateAgentSessionOptions } from "@oh-my-pi/pi-coding-agent/sdk";
import { discoverTitleSystemPromptFile } from "@oh-my-pi/pi-coding-agent/system-prompt";
@@ -12,6 +16,7 @@ const cleanupDirs: string[] = [];
afterEach(async () => {
await Promise.all(cleanupDirs.splice(0).map(dir => removeWithRetries(dir)));
vi.restoreAllMocks();
});
function createInput(overrides: Partial<SubmittedUserInput> = {}): SubmittedUserInput {
@@ -37,6 +42,21 @@ describe("discoverTitleSystemPromptFile", () => {
});
});
describe("readPipedInput", () => {
it("reads redirected stdin when Bun reports isTTY as undefined", async () => {
const originalIsTTY = process.stdin.isTTY;
const readText = vi.spyOn(Bun.stdin, "text").mockResolvedValue("piped prompt\n");
Object.defineProperty(process.stdin, "isTTY", { value: undefined, configurable: true });
try {
expect(await readPipedInput()).toBe("piped prompt\n");
expect(readText).toHaveBeenCalledTimes(1);
} finally {
Object.defineProperty(process.stdin, "isTTY", { value: originalIsTTY, configurable: true });
}
});
});
describe("applyResolvedSystemPromptInputs", () => {
it("routes SYSTEM.md content through template-aware session options", () => {
const options: CreateAgentSessionOptions = {};