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
21 changes: 16 additions & 5 deletions src/main/services/ptyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1534,14 +1534,13 @@ export async function startPty(options: {

const shellBase = (defaultShell.split('/').pop() || '').toLowerCase();

// After the provider exits, exec back into the user's shell (login+interactive)
const resumeShell =
shellBase === 'fish'
? `'${defaultShell.replace(/'/g, "'\\''")}' -i -l`
: `'${defaultShell.replace(/'/g, "'\\''")}' -il`;
const chainCommand = shellSetup
? `${shellSetup} && ${commandString}; exec ${resumeShell}`
: `${commandString}; exec ${resumeShell}`;
? `${shellSetup} && ${commandString}; ${resumeShell}`
: `${commandString}; ${resumeShell}`;

// Always use the default shell for the -c command to avoid re-detecting provider CLI
useShell = defaultShell;
Expand All @@ -1557,11 +1556,15 @@ export async function startPty(options: {
baseLower === 'fish'
? `'${useShell.replace(/'/g, "'\\''")}' -i -l`
: `'${useShell.replace(/'/g, "'\\''")}' -il`;
// Use foreground shell (no exec) so job control works properly.
// With 'exec', the shell chain can deadlock when Ctrl+Z is pressed
// because the parent shell stops before reaching exec, leaving no
// foreground process to handle fg/bg.
if (baseLower === 'fish') {
args.push('-l', '-i', '-c', `${shellSetup}; exec ${resumeShell}`);
args.push('-l', '-i', '-c', `${shellSetup}; ${resumeShell}`);
} else {
const cFlag = baseLower === 'sh' ? '-lc' : '-lic';
args.push(cFlag, `${shellSetup}; exec ${resumeShell}`);
args.push(cFlag, `${shellSetup}; ${resumeShell}`);
}
} else {
if (baseLower === 'fish') {
Expand Down Expand Up @@ -1648,6 +1651,14 @@ export function writePty(id: string, data: string): void {
if (!rec) {
return;
}
// Drop SIGTSTP (Ctrl+Z, \x1a) to prevent job control deadlocks.
// In emdash's terminal, suspending an agent is meaningless and causes the
// shell chain to deadlock. Dropping \x1a at the PTY write level ensures
// SIGTSTP is never generated. Ctrl+C (\x03) is NOT dropped so users can
// still interrupt agent actions within Claude.
if (data === '\x1a') {
return;
}
rec.proc.write(data);
}

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ const ChatInterface: React.FC<Props> = ({
>
<div
ref={terminalPanelRef}
className={`relative mx-auto h-full max-w-4xl overflow-hidden rounded-md ${
className={`relative mx-auto h-full w-full overflow-hidden rounded-md ${
agent === 'charm'
? effectiveTheme === 'dark-black'
? 'bg-black'
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/MultiAgentTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ const MultiAgentTask: React.FC<Props> = ({
onWheelCapture={handleTerminalViewportWheelForwarding}
>
<div
className={`mx-auto h-full max-w-4xl overflow-hidden rounded-md ${
className={`mx-auto h-full w-full overflow-hidden rounded-md ${
v.agent === 'mistral'
? isDark
? 'bg-[#202938]'
Expand Down
Loading