Skip to content

Releases: OmnyGrid/omnyshell

v1.63.3 — early PTY resizes applied; PTY tests de-flaked

Choose a tag to compare

@gmpassos gmpassos released this 01 Oct 08:23
600289b

Fixed

  • A terminal resize sent right after a PTY session opens is no longer lost
    (the script(1) backend, used on macOS and Linux).
    • Cause: live resize sets the size on the child's tty, whose path the
      wrapper records a moment after script starts. A resize requested before
      that (for example a client sending its terminal size as soon as it
      attaches) was dropped silently, and the program kept the old geometry
      until the next resize.
    • Now: such a resize is kept and applied as soon as the path is known,
      checked every 25ms for up to 5s until the session ends. Only the latest
      size is applied.
    • New ScriptPtyShellSession.canResizeNow and .hasPendingResize report
      the state.

Tests

  • De-flaked the macOS PTY resize test. It resized after a fixed 300ms and
    read the size after a fixed 1s. On slow CI runners (macos-15-intel) the
    wrapper hadn't recorded the tty path within 300ms, the resize was dropped,
    and stty printed the original 30 100.
  • The child now waits on stdin and prints its size only when the test says
    so, and the test waits until the tty path is known instead of sleeping.
  • A new test resizes immediately after start, before the path is known, and
    checks that the latest of two sizes is applied.
  • Both pass in 12 parallel runs.
  • The Windows PTY tests no longer hang CI. resize does not throw slept a
    fixed 400ms before sending exit. On a slow runner the shell missed it, the
    test timed out, and its winpty session was never closed. The session's
    reader and waiter isolates stayed blocked in ReadFile /
    WaitForSingleObject, so the VM could not exit and the job looped on
    "waiting for isolate _readerMain to check in".
    • Every real-PTY Windows test now kills its session in a tear-down.
    • The resize test waits for the shell's answers instead of sleeping.
    • The PTY and VM CI jobs now have a timeout-minutes limit.

v1.63.2 — no default Content-Type on the Hub's 204/304s

Choose a tag to compare

@gmpassos gmpassos released this 01 Oct 07:57
b97c811

Fixed

  • The Hub's own 204/304 responses no longer claim Content-Type: text/plain. The Hub serves HTTP through omnyhub on dart:io, which
    pre-sets Content-Type: text/plain; charset=utf-8 on every response
    (dart-lang/sdk#64442). Requires omnyhub ^1.9.2, which removes that default
    from 204 No Content and 304 Not Modified responses unless a type was set.
    A cache in front of the Hub could otherwise merge the bogus type from a 304
    into a stored page.

v1.63.1 — cached HTTP tunnels keep Content-Type; 304 cookies not shared

Choose a tag to compare

@gmpassos gmpassos released this 01 Oct 07:44
458e2ef

Fixed

  • Cached HTTP tunnels no longer turn pages into text/plain. When a
    cached response went stale and the target confirmed it with a 304 Not Modified, the Hub copied the 304's headers onto the stored response. A
    Dart HttpServer target sends Content-Type: text/plain; charset=utf-8 on
    every 304. So after the first revalidation, a cached text/html page was
    served as plain text, and browsers showed its source. A 304 now never
    changes the stored body's Content-* fields.
  • A cookie set on a 304 is no longer shared with other consumers. The
    same merge stored a 304's Set-Cookie in the cache, replaying one
    consumer's cookie (a session id, for example) to every consumer the entry
    was served to. The cookie now goes only to the consumer whose request
    triggered the revalidation.
  • Both fixes are in omnyhub 1.9.1, now required (omnyhub: ^1.9.1).

Tests

  • http_tunnel_cache_test.dart runs through a real Hub, node and Dart
    HttpServer target. It adds three cases:

    • a text/html page revalidated three times keeps text/html;
    • a 304's Set-Cookie reaches only the consumer that triggered it;
    • a hit carries the same Content-Type, Cache-Control, ETag,
      Last-Modified, Content-Language, custom headers and Date as the
      original.

    The first two fail against omnyhub 1.9.0.

v1.63.0 — HTTP tunnel cache and proxy timeouts

Choose a tag to compare

@gmpassos gmpassos released this 01 Oct 07:09
92e6c24

Added

  • Response caching for HTTP tunnels: --cache. An HTTP tunnel can keep an
    in-memory response cache on the Hub. Repeated requests for cacheable
    responses are then answered by the Hub without reaching the node. Turn it on
    with any of:

    • omnyshell tunnel open … --protocol http --cache
    • :tunnel … --cache
    • the dashboard's HTTP cache / Cache private toggles
    • openTunnel(cache: TunnelCacheOptions())

    The cache follows Cache-Control the way a shared proxy cache does
    (RFC 9111):

    • Stored: public, max-age, s-maxage or Expires.
    • Not stored, unless an option allows it: responses without
      Cache-Control or Expires (--cache-default-ttl 5m caches them), and
      private responses (--cache-private).
    • Never stored: no-store, and responses that set a cookie.
    • Always checked with the target first: no-cache.
    • Skip the cache: requests carrying Authorization, Range or a body.

    How it behaves:

    • Stale copies with an ETag or Last-Modified are checked with the target
      and refreshed by a 304.
    • The consumer's own no-cache, max-age and no-store are honoured, so a
      browser hard refresh reaches the target.
    • Each Vary value is cached separately.
    • A successful POST, PUT, PATCH or DELETE drops its path's copies.
    • Responses report X-Cache: HIT | MISS | REVALIDATED | BYPASS | STALE and
      Age.

    Options: --cache-size (defaults to the Hub's limit), --cache-max-entry
    (default 8 MiB), --cache-private, --cache-default-ttl.

  • Hub memory limits for tunnel caches. Caches live only in the Hub's RAM
    and are dropped when their tunnel closes.

    • hub start --tunnel-cache-max-per-tunnel (default 32 MiB): a larger
      --cache-size is lowered to it, and the client is told
      (cache: … (requested 1 GiB, limited by the Hub)).
    • hub start --tunnel-cache-max-total (default 128 MiB): shared by
      every tunnel, evicting the least recently used entries across them.
    • 0 disables tunnel caching; tunnels then open without a cache and the
      client warns.
    • The Hub prints its limits at start, and tunnel list, :tunnel ls and
      the dashboard show usage (cache 1.2 MiB/32 MiB · 340 hit / 41 miss).
  • HTTP timeouts for every HTTP tunnel, following common reverse-proxy
    behaviour. 0 disables a limit:

    • --http-response-header-timeout (default 60s): no response head in time →
      504 Gateway Timeout.
    • --http-idle-timeout (default 5m): a started response that stalls is cut
      off.
    • --http-client-timeout (default 60s): a consumer that doesn't finish its
      request head → 408 Request Timeout. Idle keep-alive time is not counted.
    • --http-max-duration (off unless given): caps a whole request/response.
    • A target that closes or refuses the connection before responding → 502 Bad Gateway.
    • With --cache, a stale-if-error copy is served instead of a 502/504.
    • After a failure that consumer connection closes, because a late response
      must never answer the wrong request.
  • TunnelCacheOptions, TunnelHttpTimeouts and TunnelCacheStats, carried on
    TunnelOpenRequest/TunnelOpened/TunnelInfo/TunnelHandle. A request for
    caching or timeouts on a plain TCP tunnel is rejected with requires_http.

Changed

  • HTTP tunnels now apply the default timeouts. Before, a silent target kept
    the consumer waiting indefinitely; now it gets a 504 after 60s. Pass
    --http-response-header-timeout 0 (and the other options) to restore the
    old behaviour.
  • Consumers of an HTTP tunnel get a status when the dial fails. If the node
    can't reach the target, the consumer now gets 502 Bad Gateway instead of a
    bare connection close.
  • Requires omnyhub ^1.9.0 (HttpRelay, HttpCache, HttpCacheBudget).

Fixed

  • X-OmnyShell-Tunnel-Id now matches the short id tunnel list shows.
    Tunnel ids are base64url and can contain -. The header used a short-id
    helper that strips -, so for such ids it disagreed with the listed short
    id, and it couldn't be passed to tunnel close.

Tests

  • test/integration/http_tunnel_cache_test.dart runs through a real Hub, node
    and HTTP target. It covers:
    • hits shared across consumer connections, with stats in tunnel list;
    • private and default-TTL caching, and Set-Cookie / Authorization
      never being cached;
    • the per-tunnel limit lowering a request, and a Hub with caching disabled;
    • requires_http;
    • a real 504 from the response-header timeout, and 502 from a target
      that drops or refuses the connection;
    • the cache's memory returned to the budget on close;
    • @local tunnels;
    • pipelined order.
  • Unit tests:
    • size and duration parsing;
    • the shared flag rules and messages;
    • wire round-trips for the new fields;
    • :tunnel cache and timeout flags, warnings and ls usage;
    • the dashboard toggles and cache column.
  • 270 of the 272 changed executable lines in lib/ are covered. The two left
    are a socket-write error handler and an already-closed guard.

v1.62.0 — HTTP tunnels with forwarding headers

Choose a tag to compare

@gmpassos gmpassos released this 01 Oct 04:44
26816a7

Added

  • HTTP tunnels: --protocol http. A tunnel can now say it carries HTTP
    (omnyshell tunnel open <node> <port> --protocol http, :tunnel <port> --protocol http, the dashboard's HTTP headers toggle, or
    openTunnel(protocol: TunnelProtocol.http)). The Hub then adds forwarding
    headers to every request it relays to the target, keep-alive included:
    X-Forwarded-For, X-Real-IP, X-Forwarded-Proto and X-Forwarded-Ssl
    (http/off, or https/on with --secure), X-Forwarded-Host,
    X-Forwarded-Port, RFC 7239 Forwarded, Via: 1.1 omnyshell-hub, a fresh
    X-Request-Id, and X-OmnyShell-Tunnel-Id / -Node / -Owner. Values the
    consumer already sent are kept and the Hub's appended, so the target should
    trust the right-most entry; the single-valued X-Real-IP, X-Forwarded-Ssl
    and X-Request-Id are set only when absent. Bodies, responses and upgraded
    (WebSocket) streams are untouched, and non-HTTP/1.x traffic passes through.
    The Hub does the rewriting because only it sees the consumer's address and
    terminates its TLS. A Hub that predates this opens the tunnel as plain TCP
    and the CLI warns; an unknown protocol from a newer client is rejected with
    unsupported_protocol. Plain (tcp, the default) tunnels are unchanged.
  • TunnelProtocol, TunnelInfo.protocol / .scheme, TunnelHandle.protocol
    / .scheme / .publicAddress(hubHost), and HubBroker.tunnelViaName.
  • tunnel list, :tunnel ls and the dashboard show http:// for HTTP
    tunnels.

Changed

  • Requires omnyhub ^1.8.0, which supplies the header rewriter
    (HttpRequestHeaderRewriter + ForwardedHeaders).

Tests

  • test/integration/http_tunnel_test.dart: real HttpClient → Hub → node →
    HttpServer over one keep-alive connection (including a chunked 300 KB
    upload), checking every header; HTTPS via --secure; append behaviour for
    client-sent headers; a WebSocket through an HTTP tunnel; an @local HTTP
    tunnel; and a plain TCP tunnel leaving HTTP untouched.
  • A broker-level test that an unknown protocol is rejected; wire round-trips
    for protocol on TunnelOpenRequest, TunnelOpened and TunnelInfo
    (including a legacy Hub's reply); TunnelHandle.publicAddress; :tunnel --protocol parsing, the downgrade warning and bad values; and the dashboard
    toggle. Every changed executable line in lib/ is covered.

v1.61.2 — IDE deep git scan, dot-files shown, AI abort fix

Choose a tag to compare

@gmpassos gmpassos released this 27 Sep 03:11
887f5f9

Added

  • Deep git scan in the IDE file tree (g). Directories used to show a git
    status only when they were entirely untracked, so a collapsed folder gave no
    hint of the changes inside it. Pressing g in the tree rescans with
    git status -uall and marks every folder above a change. A folder whose
    changes all share one status shows that status (e.g. ? for only untracked
    files), a mix shows M, and a conflict below shows !. The status bar
    reports how many files and directories changed. Deep mode then stays on for
    the rest of the session, so saves and agent edits keep folders up to date.

Changed

  • The IDE file tree shows dot-files by default. Entries such as .github,
    .gitignore and .env were hidden until . was pressed, so they were easy
    to miss. Now they are listed from the start, and . hides them. Only
    version-control internals (.git, .hg, .svn) are always left out.

Fixed

  • Answering q at the AI agent's "Abort the AI agent?" prompt now aborts.
    After a Ctrl-C, q was read as "no", so the run kept going. The leftover
    confirmation then made the next Ctrl-C abort without asking.
  • The IDE terminal on a remote node no longer garbles non-ASCII output. Each
    output chunk was decoded on its own, so a character such as ü split across
    two chunks showed as ��. Output is now decoded across chunks.

Tests

  • Coverage raised for the AI client commands, file transfer and :drive
    commands, local commands (:ping, :latency, :tunnel, :tree, :detach),
    :ide, the IDE remote workspace, the WebSocket connection and the tunnel
    registry.
  • New optional parameters so tests stay off the real home and terminal:
    addFileTransferCommands(driveHome:) sets where :drive keeps its mount
    store, addIdeCommand(launch:) / IdeCommand(launch:) replace the IDE
    launcher, and runIdeApp takes terminal: and loadAiConfig:. Defaults are
    unchanged.

v1.61.1 — service reinstall no longer duplicates the runtime

Choose a tag to compare

@gmpassos gmpassos released this 23 Sep 08:21
a7cc4c3

Fixed

  • service reinstall no longer duplicates the runtime in the service
    command.
    A service installed while omnyshell ran under the Dart VM records
    its pub-cache snapshot ahead of <role> start. Reinstalling with no options
    reused those arguments as-is and kept the old snapshot. The AOT binary then
    ran omnyshell <old snapshot> hub start …, and after an SDK upgrade the VM
    ran two snapshots. With dart_service_manager 1.4.0 the registry records the
    script apart from the command, so reinstall rebuilds from the command alone.
    Reinstalling an affected service once repairs it.
  • service info shows the full command a Dart VM service runs, including
    its script. dart_service_manager 1.4.0 keeps the script out of
    entry.arguments, so info now prints entry.commandLine instead.

Changed

  • Requires dart_service_manager ^1.4.0.

v1.61.0 — use omnyshell right after installing, Windows prompt fix

Choose a tag to compare

@gmpassos gmpassos released this 23 Sep 04:15
eb3e265

The installers can hand over a shell, or a PATH, that already finds
omnyshell, so scripts can use it on the very next line.

Added

  • --print-env: the installer prints only the PATH setup on stdout (all
    other output now goes to stderr), so
    eval "$(curl … | sh -s -- --print-env)" applies it to the calling shell.
    install.ps1 prints PowerShell syntax, for … | Out-String | Invoke-Expression.
    An installer cannot change the shell that started it, so until now only new
    terminals found omnyshell.
  • --shell: once installed, the installer starts a shell that inherits the
    updated PATH. On Linux and macOS it replaces itself with $SHELL. On Windows
    it starts cmd.exe when launched from install.bat, otherwise PowerShell.
  • --shell-cmd <cmd>: runs <cmd> in that shell first (implies
    --shell). With a terminal, the shell stays open afterwards. Without one, as
    in CI, only <cmd> runs and the installer exits with its status.
  • All three are also OMNYSHELL_PRINT_ENV, OMNYSHELL_SHELL and
    OMNYSHELL_SHELL_CMD. install.md has a new "Using omnyshell right after
    installing" section.

Fixed

  • No more misleading pub warning during install. dart pub global activate
    warned that the pub cache's bin "is not on your path" and printed an
    export PATH line, although the installer adds that directory to PATH
    itself. The installer now puts it on its own PATH before activating.
  • Windows sessions could get a prompt directory of \r. winpty sometimes
    returns the cursor to column 0 before a line's CRLF, so a completion marker
    arrived as <token>\r\r\n. The parser dropped one \r and took the other
    as the reported directory. A ping (completion only) then overwrote the
    prompt's cwd with a carriage return, corrupting the prompt and making TAB
    completion's chdir fail. Carriage returns are now stripped from every
    marker field. This also made the Windows winpty_marker_test fail about one
    run in seven. That test now waits for each marker instead of sleeping a fixed
    800 ms: the Git Bash marker alone takes up to 0.8 s on an idle CI runner.

v1.60.0 — one-command installers and the `l` shortcut

Choose a tag to compare

@gmpassos gmpassos released this 23 Sep 02:30
9a514d2

One command installs OmnyShell on Linux, macOS and Windows, and l lists the
current directory on any shell.

Added

  • One-command installers: install.sh, install.ps1 and install.bat.
    curl -fsSL …/install.sh | sh (Linux, macOS, WSL) or
    irm …/install.ps1 | iex (Windows) installs everything omnyshell needs,
    without asking questions. When Dart is missing it comes from the system's
    usual source: Homebrew, Google's apt repository, pacman, winget, Chocolatey
    or Scoop, falling back to the official checksum-verified SDK download in the
    user's home. An existing Dart older than 3.10.9 is upgraded with whatever
    installed it, including flutter upgrade, asdf, mise and FVM. The installer
    then adds git, openssl and script (Git for Windows and OpenSSL on Windows),
    runs dart pub global activate omnyshell, and puts the pub-cache bin on
    PATH through a marked block in the shell profile, or the user PATH on
    Windows. Re-running it updates. Options work as flags or OMNYSHELL_*
    variables: --version, --source, --git, --no-tools,
    --no-modify-path, --no-sudo, --dart-method, --no-dart-upgrade,
    --reinstall-services, --dry-run and --uninstall. install.md documents
    them, and a new CI workflow runs the installers on Ubuntu, Debian, Fedora,
    Arch, macOS and Windows.

  • The l shortcut. Typing l in an interactive session (connect,
    resume, local, or any embedder of InteractiveShellController) lists the directory with hidden entries
    and human-readable sizes, translated for the session's shell: ls -alh on
    POSIX shells (GNU and BSD ls both accept it), Get-ChildItem -Force with a
    1024-based Size column on PowerShell, and dir /a on cmd.exe, which has
    no human-readable size format. Anything typed after l (paths, extra flags)
    is passed through. Sessions source no rc file, so a user's own l alias was
    never available there. Commands the AI agent runs are not expanded.

v1.59.0 — sessions can run the node's Dart CLIs

Choose a tag to compare

@gmpassos gmpassos released this 23 Sep 01:28
edca6a6

Sessions can run the Dart CLIs installed on the node — omnyshell included.

Added

  • The pub cache bin directory is on every session's PATH. OmnyShell is
    a Dart CLI installed with dart pub global activate, and so is much of what
    an operator reaches for once connected; those executables live in the pub
    cache's bin, which is on PATH only because a shell rc put it there.
    Sessions source no rc, so omnyshell was missing from the very shell
    OmnyShell had just opened. Each backend (pipe, script PTY and winpty) now
    appends that directory — $PUB_CACHE/bin, else ~/.pub-cache/bin
    (%LOCALAPPDATA%\Pub\Cache\bin on Windows) — when the session's PATH does
    not already carry it. It is appended, not prepended, so the inherited or
    profile PATH keeps precedence, and nothing is added when the directory does
    not exist.

Fixed

  • A local IDE command whose output nobody read never reported its exit
    code.
    ProcessCommandRunner awaited the close of its output stream before
    completing CommandExecution.exitCode; that stream is single-subscription,
    and a close is only delivered once something listens, so a caller that wanted
    the exit code alone waited forever. The exit code is now reported first and
    the close is left to land on its own — a listener that subscribes later still
    receives the buffered lines and the done event. The remote runner already
    worked this way, so the two now agree.