Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Fixed

- Fixed pasted terminal transcripts beginning with a shell prompt (`$ ...`) being mistaken for local Python shortcuts instead of being submitted as normal prompts ([#4678](https://github.com/can1357/oh-my-pi/issues/4678)).

## [16.3.9] - 2026-07-06

### Added
Expand Down
18 changes: 17 additions & 1 deletion packages/coding-agent/src/modes/controllers/input-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ function hasPasteText(value: unknown): value is PasteTarget {
return typeof value === "object" && value !== null && typeof (value as PasteTarget).pasteText === "function";
}

const SHELL_PROMPT_COMMAND_RE =
/^(?:\.{0,2}\/|~\/|cd(?:\s|$)|sudo(?:\s|$)|git(?:\s|$)|bun(?:\s|$)|npm(?:\s|$)|pnpm(?:\s|$)|yarn(?:\s|$)|node(?:\s|$)|python\d*(?:\s|$)|cargo(?:\s|$)|go(?:\s|$)|make(?:\s|$)|docker(?:\s|$)|kubectl(?:\s|$))/;
const SHELL_PROMPT_OPERATOR_RE = /(?:^|\s)(?:&&|\|\||\||2>&1|[<>]{1,2})(?:\s|$)/;
const OMP_STATUS_LINE_RE = /^\s*in:\s+\d+\s+out:\s+\d+(?:\s+cache\s+\S+)?\s+t:\s+\S+\s+tok\/s:\s+\S+/m;

function looksLikePastedShellPrompt(code: string): boolean {
const firstLine = code.split("\n", 1)[0]?.trimStart() ?? "";
return (
SHELL_PROMPT_COMMAND_RE.test(firstLine) ||
SHELL_PROMPT_OPERATOR_RE.test(firstLine) ||
OMP_STATUS_LINE_RE.test(code)
);
}

function pythonCommandPrefixLength(trimmedText: string): 0 | 1 | 2 {
if (trimmedText.charCodeAt(0) !== 36 /* $ */) return 0;
if (trimmedText.charCodeAt(1) === 123 /* { */) return 0;
Expand All @@ -100,8 +114,10 @@ function parsePythonCommandInput(text: string): { code: string; isExcluded: bool
const trimmed = text.trimStart();
const prefixLength = pythonCommandPrefixLength(trimmed);
if (prefixLength === 0) return undefined;
const code = trimmed.slice(prefixLength).trim();
if (prefixLength === 1 && looksLikePastedShellPrompt(code)) return undefined;
return {
code: trimmed.slice(prefixLength).trim(),
code,
isExcluded: prefixLength === 2,
};
}
Expand Down
30 changes: 30 additions & 0 deletions packages/coding-agent/test/input-controller-python-prefix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,36 @@ describe("InputController Python prompt prefix", () => {
]);
});

it("submits pasted shell-prompt transcripts with OMP chrome as a normal prompt", async () => {
const transcript =
"$ cd ~/project && sudo ./build-and-push.sh o5.7 2>&1 | tail -4\n" +
" |\n" +
" in: 282 out: 152 cache 344K t: 3.3s tok/s: 351.9/s\n" +
" is this command stuck in limbo";
const { ctx, editor, handlePythonCommand, onInputCallback, startPendingSubmission, submitted } = createContext();
const controller = new InputController(ctx);
controller.setupEditorSubmitHandler();

await editor.onSubmit?.(transcript);

expect(handlePythonCommand).not.toHaveBeenCalled();
expect(startPendingSubmission).toHaveBeenCalledWith({
text: transcript,
images: undefined,
imageLinks: undefined,
streamingBehavior: "steer",
});
expect(onInputCallback).toHaveBeenCalledTimes(1);
expect(submitted).toEqual([
{
text: transcript,
images: undefined,
imageLinks: undefined,
streamingBehavior: "steer",
},
]);
});

it("keeps space-separated Python shortcuts available", async () => {
const { ctx, editor, handlePythonCommand, onInputCallback } = createContext();
const controller = new InputController(ctx);
Expand Down