diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= index e6f48d0..f600b09 100644 Binary files a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= and b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= differ diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart index a5fbdfc..0b00bb1 100644 --- a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart +++ b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart @@ -53,6 +53,12 @@ typedef _chdir_dart = int Function(ffi.Pointer path); typedef _fcntl_c = ffi.Int32 Function(ffi.Int32 fd, ffi.Int32 cmd, ffi.Int32 arg); typedef _fcntl_dart = int Function(int fd, int cmd, int arg); +typedef _unsetenv_c = ffi.Int32 Function(ffi.Pointer name); +typedef _unsetenv_dart = int Function(ffi.Pointer name); + +typedef _exit_c = ffi.Void Function(ffi.Int32 status); +typedef _exit_dart = void Function(int status); + base class Winsize extends ffi.Struct { @ffi.Uint16() external int ws_row; @@ -101,6 +107,8 @@ class PtySession { final ioctl = libc.lookupFunction<_ioctl_c, _ioctl_dart>('ioctl'); final chdir = libc.lookupFunction<_chdir_c, _chdir_dart>('chdir'); final fcntl = libc.lookupFunction<_fcntl_c, _fcntl_dart>('fcntl'); + final unsetenv = libc.lookupFunction<_unsetenv_c, _unsetenv_dart>('unsetenv'); + final cExit = libc.lookupFunction<_exit_c, _exit_dart>('exit'); // 1. Prepare master PTY final masterFd = posixOpenpt(2 | 0x00000400); @@ -132,16 +140,15 @@ class PtySession { fcntl(masterFd, 4, flags | 2048); } - // 2. Resolve environment, isolating nested TMUX contexts (§6.7) - final env = Map.from(environment ?? Platform.environment); - env.remove('TMUX'); - env.remove('TMUX_PANE'); - - // 3. Pre-allocate ALL structures on the heap before fork() for async-signal-safety (§6.7) + // 2. Pre-allocate ALL structures on the heap before fork() for async-signal-safety (§6.7) final pathPtr = slaveName.toNativeUtf8(); final exePtr = executable.toNativeUtf8(); final workDirPtr = workingDirectory?.toNativeUtf8(); + // Environment isolation variables to be cleared in child context + final tmuxNamePtr = 'TMUX'.toNativeUtf8(); + final tmuxPaneNamePtr = 'TMUX_PANE'.toNativeUtf8(); + final argsCount = arguments.length; final argv = malloc>(argsCount + 2); argv[0] = exePtr.cast(); @@ -154,12 +161,14 @@ class PtySession { } argv[argsCount + 1] = ffi.Pointer.fromAddress(0); - // 4. Fork child process + // 3. Fork child process final pid = fork(); if (pid < 0) { close(masterFd); malloc.free(pathPtr); malloc.free(exePtr); + malloc.free(tmuxNamePtr); + malloc.free(tmuxPaneNamePtr); if (workDirPtr != null) malloc.free(workDirPtr); malloc.free(argv); for (final p in allocatedArgs) { @@ -169,12 +178,18 @@ class PtySession { } if (pid == 0) { - // --- CHILD PROCESS BRANCH (Strictly async-signal-safe syscalls only!) --- + // --- CHILD PROCESS BRANCH (Strictly async-signal-safe OS calls only!) --- + // A. Disassociate controlling terminal setsid(); + // B. Isolate from nested TMUX environments (§6.7) + unsetenv(tmuxNamePtr.cast()); + unsetenv(tmuxPaneNamePtr.cast()); + + // C. Open slave and redirect stdio final slaveFd = open(pathPtr.cast(), 2); if (slaveFd < 0) { - exit(-1); + cExit(-1); // direct libc _exit to prevent async-signal-unsafe Dart runtime exit } ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0)); @@ -193,12 +208,14 @@ class PtySession { } execvp(exePtr.cast(), argv); - exit(-2); + cExit(-2); // direct libc _exit on exec failure } // --- PARENT PROCESS BRANCH --- malloc.free(pathPtr); malloc.free(exePtr); + malloc.free(tmuxNamePtr); + malloc.free(tmuxPaneNamePtr); if (workDirPtr != null) malloc.free(workDirPtr); malloc.free(argv); for (final p in allocatedArgs) { @@ -279,16 +296,17 @@ class PtySession { final close = libc.lookupFunction<_close_c, _close_dart>('close'); close(masterFd); + // Send SIGTERM to terminate child process final kill = libc.lookupFunction< ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig), int Function(int pid, int sig) >('kill'); kill(childPid, 15); // SIGTERM = 15 - // Reap child zombie process (non-blocking WNOHANG = 1) + // Reap child zombie process (blocking waitpid options = 0 to guarantee reaping) final waitpid = libc.lookupFunction<_waitpid_c, _waitpid_dart>('waitpid'); final statusPtr = calloc(); - waitpid(childPid, statusPtr, 1); + waitpid(childPid, statusPtr, 0); // Blocking wait guarantees process resource removal calloc.free(statusPtr); } }