diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/package_graph.json b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/package_graph.json index aecd248..7b83227 100644 --- a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/package_graph.json +++ b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/package_graph.json @@ -16,129 +16,6 @@ "test" ] }, - { - "name": "shelf_web_socket", - "version": "1.0.4", - "dependencies": [ - "shelf", - "stream_channel", - "web_socket_channel" - ] - }, - { - "name": "shelf", - "version": "1.4.2", - "dependencies": [ - "async", - "collection", - "http_parser", - "path", - "stack_trace", - "stream_channel" - ] - }, - { - "name": "http_parser", - "version": "4.1.2", - "dependencies": [ - "collection", - "source_span", - "string_scanner", - "typed_data" - ] - }, - { - "name": "collection", - "version": "1.19.1", - "dependencies": [] - }, - { - "name": "typed_data", - "version": "1.4.0", - "dependencies": [ - "collection" - ] - }, - { - "name": "stream_channel", - "version": "2.1.4", - "dependencies": [ - "async" - ] - }, - { - "name": "stack_trace", - "version": "1.12.1", - "dependencies": [ - "path" - ] - }, - { - "name": "path", - "version": "1.9.1", - "dependencies": [] - }, - { - "name": "string_scanner", - "version": "1.4.1", - "dependencies": [ - "source_span" - ] - }, - { - "name": "ffi", - "version": "2.2.0", - "dependencies": [] - }, - { - "name": "source_span", - "version": "1.10.2", - "dependencies": [ - "collection", - "path", - "term_glyph" - ] - }, - { - "name": "term_glyph", - "version": "1.2.2", - "dependencies": [] - }, - { - "name": "web_socket_channel", - "version": "2.4.5", - "dependencies": [ - "async", - "crypto", - "stream_channel", - "web" - ] - }, - { - "name": "web", - "version": "0.5.1", - "dependencies": [] - }, - { - "name": "crypto", - "version": "3.0.7", - "dependencies": [ - "typed_data" - ] - }, - { - "name": "async", - "version": "2.13.1", - "dependencies": [ - "collection", - "meta" - ] - }, - { - "name": "meta", - "version": "1.19.0", - "dependencies": [] - }, { "name": "test", "version": "1.31.2", @@ -170,6 +47,70 @@ "yaml" ] }, + { + "name": "meta", + "version": "1.19.0", + "dependencies": [] + }, + { + "name": "shelf_web_socket", + "version": "1.0.4", + "dependencies": [ + "shelf", + "stream_channel", + "web_socket_channel" + ] + }, + { + "name": "shelf", + "version": "1.4.2", + "dependencies": [ + "async", + "collection", + "http_parser", + "path", + "stack_trace", + "stream_channel" + ] + }, + { + "name": "ffi", + "version": "2.2.0", + "dependencies": [] + }, + { + "name": "yaml", + "version": "3.1.3", + "dependencies": [ + "collection", + "source_span", + "string_scanner" + ] + }, + { + "name": "webkit_inspection_protocol", + "version": "1.2.1", + "dependencies": [ + "logging" + ] + }, + { + "name": "web_socket_channel", + "version": "2.4.5", + "dependencies": [ + "async", + "crypto", + "stream_channel", + "web" + ] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, { "name": "test_core", "version": "0.6.19", @@ -212,6 +153,101 @@ "term_glyph" ] }, + { + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" + ] + }, + { + "name": "stack_trace", + "version": "1.12.1", + "dependencies": [ + "path" + ] + }, + { + "name": "source_span", + "version": "1.10.2", + "dependencies": [ + "collection", + "path", + "term_glyph" + ] + }, + { + "name": "shelf_static", + "version": "1.1.3", + "dependencies": [ + "convert", + "http_parser", + "mime", + "path", + "shelf" + ] + }, + { + "name": "shelf_packages_handler", + "version": "3.0.2", + "dependencies": [ + "path", + "shelf", + "shelf_static" + ] + }, + { + "name": "pool", + "version": "1.5.2", + "dependencies": [ + "async", + "stack_trace" + ] + }, + { + "name": "path", + "version": "1.9.1", + "dependencies": [] + }, + { + "name": "package_config", + "version": "3.0.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "node_preamble", + "version": "2.0.2", + "dependencies": [] + }, + { + "name": "matcher", + "version": "0.12.20", + "dependencies": [ + "async", + "meta", + "stack_trace", + "term_glyph", + "test_api" + ] + }, + { + "name": "io", + "version": "1.0.5", + "dependencies": [ + "meta", + "path", + "string_scanner" + ] + }, + { + "name": "http_multi_server", + "version": "3.2.2", + "dependencies": [ + "async" + ] + }, { "name": "coverage", "version": "1.15.1", @@ -230,22 +266,99 @@ ] }, { - "name": "yaml", - "version": "3.1.3", + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "boolean_selector", + "version": "2.1.2", "dependencies": [ - "collection", "source_span", "string_scanner" ] }, { - "name": "cli_config", - "version": "0.2.0", + "name": "async", + "version": "2.13.1", "dependencies": [ - "args", + "collection", + "meta" + ] + }, + { + "name": "analyzer", + "version": "14.1.0", + "dependencies": [ + "_fe_analyzer_shared", + "collection", + "convert", + "crypto", + "glob", + "meta", + "package_config", + "path", + "pub_semver", + "source_span", + "watcher", "yaml" ] }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "string_scanner", + "version": "1.4.1", + "dependencies": [ + "source_span" + ] + }, + { + "name": "logging", + "version": "1.3.0", + "dependencies": [] + }, + { + "name": "web", + "version": "0.5.1", + "dependencies": [] + }, + { + "name": "crypto", + "version": "3.0.7", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "vm_service", + "version": "15.2.0", + "dependencies": [] + }, + { + "name": "source_maps", + "version": "0.10.13", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_map_stack_trace", + "version": "2.1.2", + "dependencies": [ + "path", + "source_maps", + "stack_trace" + ] + }, { "name": "glob", "version": "2.1.3", @@ -266,146 +379,18 @@ ] }, { - "name": "shelf_packages_handler", - "version": "3.0.2", - "dependencies": [ - "path", - "shelf", - "shelf_static" - ] - }, - { - "name": "pool", - "version": "1.5.2", - "dependencies": [ - "async", - "stack_trace" - ] - }, - { - "name": "node_preamble", - "version": "2.0.2", + "name": "args", + "version": "2.7.0", "dependencies": [] }, { - "name": "boolean_selector", - "version": "2.1.2", - "dependencies": [ - "source_span", - "string_scanner" - ] + "name": "term_glyph", + "version": "1.2.2", + "dependencies": [] }, { - "name": "source_map_stack_trace", - "version": "2.1.2", - "dependencies": [ - "path", - "source_maps", - "stack_trace" - ] - }, - { - "name": "source_maps", - "version": "0.10.13", - "dependencies": [ - "source_span" - ] - }, - { - "name": "file", - "version": "7.0.1", - "dependencies": [ - "meta", - "path" - ] - }, - { - "name": "webkit_inspection_protocol", - "version": "1.2.1", - "dependencies": [ - "logging" - ] - }, - { - "name": "shelf_static", - "version": "1.1.3", - "dependencies": [ - "convert", - "http_parser", - "mime", - "path", - "shelf" - ] - }, - { - "name": "io", - "version": "1.0.5", - "dependencies": [ - "meta", - "path", - "string_scanner" - ] - }, - { - "name": "http_multi_server", - "version": "3.2.2", - "dependencies": [ - "async" - ] - }, - { - "name": "package_config", - "version": "3.0.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "matcher", - "version": "0.12.20", - "dependencies": [ - "async", - "meta", - "stack_trace", - "term_glyph", - "test_api" - ] - }, - { - "name": "analyzer", - "version": "14.1.0", - "dependencies": [ - "_fe_analyzer_shared", - "collection", - "convert", - "crypto", - "glob", - "meta", - "package_config", - "path", - "pub_semver", - "source_span", - "watcher", - "yaml" - ] - }, - { - "name": "_fe_analyzer_shared", - "version": "105.0.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "pub_semver", - "version": "2.2.0", - "dependencies": [ - "collection" - ] - }, - { - "name": "args", - "version": "2.7.0", + "name": "mime", + "version": "2.0.0", "dependencies": [] }, { @@ -416,9 +401,12 @@ ] }, { - "name": "logging", - "version": "1.3.0", - "dependencies": [] + "name": "cli_config", + "version": "0.2.0", + "dependencies": [ + "args", + "yaml" + ] }, { "name": "watcher", @@ -429,14 +417,26 @@ ] }, { - "name": "mime", - "version": "2.0.0", - "dependencies": [] + "name": "pub_semver", + "version": "2.2.0", + "dependencies": [ + "collection" + ] }, { - "name": "vm_service", - "version": "15.2.0", - "dependencies": [] + "name": "_fe_analyzer_shared", + "version": "105.0.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "file", + "version": "7.0.1", + "dependencies": [ + "meta", + "path" + ] } ], "configVersion": 1 diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/pub/bin/test/test.dart-3.12.1.snapshot b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/pub/bin/test/test.dart-3.12.1.snapshot new file mode 100644 index 0000000..a80b184 Binary files /dev/null and b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/pub/bin/test/test.dart-3.12.1.snapshot differ 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= new file mode 100644 index 0000000..af0147c Binary files /dev/null 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 8b5f12e..11788b2 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 @@ -20,6 +20,30 @@ typedef _ptsname_dart = ffi.Pointer Function(int fd); typedef _ioctl_c = ffi.Int32 Function(ffi.Int32 fd, ffi.UnsignedLong request, ffi.Pointer argp); typedef _ioctl_dart = int Function(int fd, int request, ffi.Pointer argp); +typedef _fork_c = ffi.Int32 Function(); +typedef _fork_dart = int Function(); + +typedef _setsid_c = ffi.Int32 Function(); +typedef _setsid_dart = int Function(); + +typedef _open_c = ffi.Int32 Function(ffi.Pointer pathname, ffi.Int32 flags); +typedef _open_dart = int Function(ffi.Pointer pathname, int flags); + +typedef _close_c = ffi.Int32 Function(ffi.Int32 fd); +typedef _close_dart = int Function(int fd); + +typedef _dup2_c = ffi.Int32 Function(ffi.Int32 oldfd, ffi.Int32 newfd); +typedef _dup2_dart = int Function(int oldfd, int newfd); + +typedef _execvp_c = ffi.Int32 Function(ffi.Pointer file, ffi.Pointer> argv); +typedef _execvp_dart = int Function(ffi.Pointer file, ffi.Pointer> argv); + +typedef _read_c = ffi.IntPtr Function(ffi.Int32 fd, ffi.Pointer buf, ffi.IntPtr count); +typedef _read_dart = int Function(int fd, ffi.Pointer buf, int count); + +typedef _write_c = ffi.IntPtr Function(ffi.Int32 fd, ffi.Pointer buf, ffi.IntPtr count); +typedef _write_dart = int Function(int fd, ffi.Pointer buf, int count); + base class Winsize extends ffi.Struct { @ffi.Uint16() external int ws_row; @@ -34,22 +58,16 @@ base class Winsize extends ffi.Struct { class PtySession { final int masterFd; final String slaveName; - late final File _masterFile; - late final RandomAccessFile _masterRaf; + final int childPid; final _stdoutController = StreamController>(); - late final StreamSubscription _readSub; - late final Process _process; bool _isClosed = false; PtySession._({ required this.masterFd, required this.slaveName, - required RandomAccessFile raf, - required File file, + required this.childPid, }) { - _masterFile = file; - _masterRaf = raf; _startReading(); } @@ -65,72 +83,137 @@ class PtySession { final grantpt = libc.lookupFunction<_grantpt_c, _grantpt_dart>('grantpt'); final unlockpt = libc.lookupFunction<_unlockpt_c, _unlockpt_dart>('unlockpt'); final ptsname = libc.lookupFunction<_ptsname_c, _ptsname_dart>('ptsname'); + final fork = libc.lookupFunction<_fork_c, _fork_dart>('fork'); + final setsid = libc.lookupFunction<_setsid_c, _setsid_dart>('setsid'); + final open = libc.lookupFunction<_open_c, _open_dart>('open'); + final close = libc.lookupFunction<_close_c, _close_dart>('close'); + final dup2 = libc.lookupFunction<_dup2_c, _dup2_dart>('dup2'); + final execvp = libc.lookupFunction<_execvp_c, _execvp_dart>('execvp'); + final ioctl = libc.lookupFunction<_ioctl_c, _ioctl_dart>('ioctl'); - // O_RDWR = 2, O_NOCTTY = 0x00000400 - final fd = posixOpenpt(2 | 0x00000400); - if (fd < 0) { + // 1. Open master PTY (O_RDWR = 2, O_NOCTTY = 0x00000400) + final masterFd = posixOpenpt(2 | 0x00000400); + if (masterFd < 0) { throw OSError('Failed to open pseudo-terminal master'); } - if (grantpt(fd) != 0) { + if (grantpt(masterFd) != 0) { + close(masterFd); throw OSError('Failed to grant pseudo-terminal slave permissions'); } - if (unlockpt(fd) != 0) { + if (unlockpt(masterFd) != 0) { + close(masterFd); throw OSError('Failed to unlock pseudo-terminal slave descriptor'); } - final slavePtr = ptsname(fd); + final slavePtr = ptsname(masterFd); if (slavePtr == ffi.Pointer.fromAddress(0)) { + close(masterFd); throw OSError('Failed to get pseudo-terminal slave device name'); } final slaveName = slavePtr.cast().toDartString(); - final masterFile = File('/proc/self/fd/'); - final raf = masterFile.openSync(mode: FileMode.writeOnlyAppend); + // 2. Resolve environment, isolating nested TMUX contexts (ยง6.7) + final env = Map.from(environment ?? Platform.environment); + env.remove('TMUX'); + env.remove('TMUX_PANE'); - final process = await Process.start( - executable, - arguments, - workingDirectory: workingDirectory, - environment: environment, - runInShell: false, - mode: ProcessStartMode.normal, - ); + // 3. Fork child process + final pid = fork(); + if (pid < 0) { + close(masterFd); + throw OSError('Failed to fork child process'); + } - final session = PtySession._( - masterFd: fd, + if (pid == 0) { + // --- CHILD PROCESS BRANCH --- + setsid(); + + final pathPtr = slaveName.toNativeUtf8(); + final slaveFd = open(pathPtr.cast(), 2); + malloc.free(pathPtr); + if (slaveFd < 0) { + exit(-1); + } + + // TIOCSCTTY = 0x540E + ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0)); + + dup2(slaveFd, 0); + dup2(slaveFd, 1); + dup2(slaveFd, 2); + + close(masterFd); + if (slaveFd > 2) { + close(slaveFd); + } + + if (workingDirectory != null) { + Directory.current = workingDirectory; + } + + final exePtr = executable.toNativeUtf8(); + final argsCount = arguments.length; + final argv = malloc>(argsCount + 2); + argv[0] = exePtr.cast(); + + for (var i = 0; i < argsCount; i++) { + argv[i + 1] = arguments[i].toNativeUtf8().cast(); + } + argv[argsCount + 1] = ffi.Pointer.fromAddress(0); + + execvp(exePtr.cast(), argv); + exit(-2); + } + + // --- PARENT PROCESS BRANCH --- + return PtySession._( + masterFd: masterFd, slaveName: slaveName, - raf: raf, - file: masterFile, + childPid: pid, ); - session._process = process; - return session; } void _startReading() { - final readStream = _masterFile.openRead(); - _readSub = readStream.listen( - (data) { - if (!_isClosed) _stdoutController.add(data); - }, - onError: (err) { - if (!_isClosed) _stdoutController.addError(err); - }, - onDone: () { + final libc = ffi.DynamicLibrary.open('libc.so.6'); + final cRead = libc.lookupFunction<_read_c, _read_dart>('read'); + final buffer = malloc(1024); + + Timer.periodic(const Duration(milliseconds: 10), (timer) { + if (_isClosed) { + timer.cancel(); + malloc.free(buffer); + return; + } + + final bytesRead = cRead(masterFd, buffer.cast(), 1024); + if (bytesRead > 0) { + final bytes = List.generate(bytesRead, (i) => buffer[i]); + _stdoutController.add(bytes); + } else if (bytesRead < 0) { + final errno = ffi.DynamicLibrary.open('libc.so.6').lookup('errno').value; + if (errno != 4 && errno != 11) { + close(); + } + } else { close(); - }, - cancelOnError: false, - ); + } + }); } void write(List data) { if (_isClosed) return; - try { - _masterRaf.writeFromSync(data); - _masterRaf.flushSync(); - } catch (_) { + final libc = ffi.DynamicLibrary.open('libc.so.6'); + final cWrite = libc.lookupFunction<_write_c, _write_dart>('write'); + + final buffer = malloc(data.length); + for (var i = 0; i < data.length; i++) { + buffer[i] = data[i]; } + + cWrite(masterFd, buffer.cast(), data.length); + malloc.free(buffer); } void writeString(String str) { @@ -153,9 +236,16 @@ class PtySession { void close() { if (_isClosed) return; _isClosed = true; - _readSub.cancel(); _stdoutController.close(); - _masterRaf.closeSync(); - _process.kill(ProcessSignal.sigterm); + + final libc = ffi.DynamicLibrary.open('libc.so.6'); + final close = libc.lookupFunction<_close_c, _close_dart>('close'); + close(masterFd); + + final kill = libc.lookupFunction< + ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig), + int Function(int pid, int sig) + >('kill'); + kill(childPid, 15); } } diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart new file mode 100644 index 0000000..5ca63a1 --- /dev/null +++ b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart @@ -0,0 +1,27 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:mam_pty/mam_pty.dart'; +import 'package:test/test.dart'; + +void main() { + test('PtySession runtime execution resolves process output', () async { + final pty = await PtySession.start('echo', ['hello-pty-ok']); + + final completer = Completer(); + final buffer = StringBuffer(); + + final sub = pty.stdout.listen((data) { + buffer.write(utf8.decode(data)); + }, onDone: () { + completer.complete(buffer.toString()); + }); + + final result = await completer.future.timeout(const Duration(seconds: 3)); + sub.cancel(); + pty.close(); + + expect(result, contains('hello-pty-ok')); + print('PTY Runtime stdout verified: ' + result.trim()); + }); +} diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json new file mode 100644 index 0000000..9050f96 --- /dev/null +++ b/.dart_tool/package_config.json @@ -0,0 +1,16 @@ +{ + "configVersion": 2, + "packages": [ + { + "name": "multi_agent_mux_ui_root", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "3.0" + } + ], + "generator": "pub", + "generatorVersion": "3.12.1", + "flutterRoot": "file:///home/godopu16/puenv/flutter", + "flutterVersion": "3.44.1", + "pubCache": "file:///home/godopu16/.pub-cache" +} diff --git a/.dart_tool/package_graph.json b/.dart_tool/package_graph.json new file mode 100644 index 0000000..5cd06e7 --- /dev/null +++ b/.dart_tool/package_graph.json @@ -0,0 +1,14 @@ +{ + "roots": [ + "multi_agent_mux_ui_root" + ], + "packages": [ + { + "name": "multi_agent_mux_ui_root", + "version": "1.0.0", + "dependencies": [], + "devDependencies": [] + } + ], + "configVersion": 1 +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..573410f --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,5 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: {} +sdks: + dart: ">=3.0.0 <4.0.0"