Subj : src/sftp/sftp.h sftp_client.c src/syncterm/Wren.adoc src/syncterm/scri To : Git commit to main/sbbs/master From : Deucе Date : Tue Apr 28 2026 17:47:40 https://gitlab.synchro.net/main/sbbs/-/commit/50dfd1318ca341eaea03d98b Modified Files: src/sftp/sftp.h sftp_client.c src/syncterm/Wren.adoc src/syncterm/scripts/syncterm.wren wrentest.wren src/syncterm/term.c wren_bind.c wren_host.c wren_host_internal.h Log Message: SyncTERM: Wren SFTP API + Input.nextEvent fiber-arg primitive Adds a Wren-callable SFTP surface on top of the async sftpc_ library and converts Input.nextEvent to the same fiber-arg shape. Async ops take the fiber-to-resume as their first argument, fire the underlying request, and return immediately: SFTP.(fiber, args...) -> null | SFTPError null means the request was queued (the caller may yield to receive the result via fiber.call); SFTPError means the request couldn't be queued (session is gone, OOM at the foreign-method site) — no callback will fire. All other errors round-trip through the result queue so the caller's yield surfaces them as an SFTPError too. Common idioms: // fire and immediately await var r = SFTP.realpath(Fiber.current, ".") || Fiber.yield() // multi-fire event loop, demuxed by type SFTP.stat(Fiber.current, "/a") SFTP.stat(Fiber.current, "/b") Input.nextEvent(Fiber.current) while (true) { var x = Fiber.yield() if (x is SFTPStat) ... if (x is KeyEvent) { Input.nextEvent(Fiber.current); ... } if (x is SFTPError) break } // hook-friendly: callback fiber, calling fiber doesn't yield SFTP.realpath(Fiber.new {|r| ... }, ".") Library additions (src/sftp/): sftpc_mkdir / sftpc_rmdir / sftpc_remove / sftpc_rename — four status-only async ops that were stripped in 97dd3955d6. They reuse the bare struct sftpc_pending plus parse_status_only and share a do_one_path helper for the single-path variants. Wren classes (foreign): SFTP — static-only; available, pubdir, plus 12 async ops (realpath, stat, opendir, readdir, close, open, read, write, mkdir, rmdir, remove, rename). SFTPEntry — name, longname, size, mtime, isDir, hash. SFTPStat — size, mtime, atime, mode, uid, gid. SFTPHandle — opaque server file/dir handle; finalizer fires sftpc_close fire-and-forget when GC'd. SFTPError — code (sftp_err_code_t), serverStatus (SSH_FX_*), message, isTransient. FileFlag — six SSH_FXF_* bitmask constants for SFTP.open. Zero-copy delivery: the recv-thread cb runs under state->mtx and just stamps ctx->pending = p + pushes onto the result queue. The owner-thread deliver fn reads typed-pending fields directly (sftp_str_t bytes, sftpc_attrs getters, sftpc_dir_entry array) and frees the pending in the queue's free fn. No memcpy under the mutex; the only Wren-heap copy is wrenSetSlotBytes / strdup at delivery, which is unavoidable. Input.nextEvent converted to the same shape: Input.nextEvent(fiber) -> null …replacing the prior Input.park_(fiber) primitive + Input.nextEvent() auto-yielding wrapper. The wrapper existed only to work around now- removed quirks (implicit CTerm.suspended setting, the now-dropped Hook.onOutput). Dropping it lets hooks pass Fiber.new {|ev| ...} for callback-style delivery without nesting Fiber.new {...}.call(). Throws if another fiber is already registered (single-subscriber is structural). Wren.adoc, wrentest.wren, and the relevant internal-comment references updated to match the new API. --- ■ Synchronet ■ Vertrauen ■ Home of Synchronet ■ [vert/cvs/bbs].synchro.net .