• src/sftp/sftp.h sftp_client.c src/syncterm/Wren.adoc src/syncterm/scri

    From Deucе@VERT to Git commit to main/sbbs/master on Tuesday, April 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.<op>(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