UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

196 lines • 10.1 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { singleQuote } from "../../utils/quote.js"; import { boundSystemClock, systemNow } from "../../utils/clock.js"; /** * Why a sandbox cannot take a line, named by the state its status command * reported. `hints` holds the provider's advice per state, with `{name}` * standing for `name`; `otherwise` is the advice for a state it lacks. * Mirrors Python's `not_running_hint`. */ export function notRunningHint(noun, name, state, hints, otherwise = null) { const detail = Object.hasOwn(hints, state) ? hints[state] : undefined; if (detail !== undefined) return `${noun} ${name} is not running: ${detail.replaceAll('{name}', name)}`; const said = `${noun} ${name} is not running (state: ${state})`; return otherwise === null ? said : `${said}; ${otherwise}`; } /** * The signal a `kill` stopped `runner` with, when a program may handle it: * null for KILL, or for a run no `kill` stopped, which is killed at once. * Mirrors Python's `stop_signal`. */ export function stopSignal(runner) { const signal = runner?.signal ?? null; return signal === null || signal === 9 ? null : signal; } export const MOUNT_TYPES = ['ssh', 'fuse', 'both']; // How long a sandbox has to show a served prefix as mounted, and how // often it is asked. export const MOUNT_WAIT_SECONDS = 15; export const MOUNT_POLL_SECONDS = 0.1; // How long a served prefix's SFTP server may take to end once its // sandbox side is unmounted. export const UNMOUNT_WAIT_SECONDS = 5; // How much of a pipe helper's stderr is kept to say why a mount failed, // and how long a helper still running has to finish saying it. export const PIPE_DIAGNOSTIC_BYTES = 16384; export const PIPE_DIAGNOSTIC_WAIT_SECONDS = 1; // sshfs keeps no copy of its own: every listing, stat and read asks the // host, so a file the host changes, or a profile now hides, shows at // once, and a file whose size the backend does not know reads whole // (direct_io), as the workspace's own FUSE mounts do. export const SSHFS_OPTIONS = 'passive,dir_cache=no,direct_io,attr_timeout=0,entry_timeout=0,negative_timeout=0'; // Where sshfs writes its errors in the sandbox, one file per prefix; its // stdout is the SFTP stream, so its stderr cannot share the pipe. export const SSHFS_LOG_PREFIX = '/tmp/.mirage_sshfs'; export const SSHFS_HINT = 'serving the workspace into a sandbox needs sshfs there; install it in the image (apt-get install sshfs)'; export const PIPE_SERVER_HINT = 'serving the workspace into a sandbox needs @struktoai/mirage-node and its ssh2 peer'; /** * Every argv a sandbox runs goes under this POSIX sh prelude, with the * cwd as $1: a provider program's exec passes `-w /` (`execArgs`), and a * provider whose exec takes one command line dresses it (`wrapLine`), so * no provider's own cwd is used. An exec's `-e` can append to the image's * environment rather than replace a name it already sets, `-w` can * create a missing directory, and an SDK's cwd fails a missing one its * own way (E2B: exit 1). sh imports the environment last-wins, `cd` * fails loud (126) on a cwd the sandbox does not serve, and `exec` hands * over the argv unchanged. `cd` runs without CDPATH, so a relative cwd * resolves from `/` and prints nothing, while the program still sees the * guest's own CDPATH. The shell is named by path, /bin/sh, since the * command's PATH is the session's and may hold no sh; the argv after the * prelude is still found on it. */ export const EXEC_PRELUDE = 'CDPATH= cd -- "$1" || exit 126; shift; exec "$@"'; /** * Every process a run starts in a sandbox carries this variable, set to the * run's own id, so a run cancelled midway can be stopped in the box: * stopping the client that started it does not reach the box everywhere * (`docker exec` has no kill, and the daemon keeps draining the program's * output, so a `yes` cut short by `head` would spin on). The id rides the * environment because every process of a pipeline inherits it. */ export const EXEC_ID = 'MIRAGE_EXEC_ID'; // How long stopping a cancelled run may hold the cancel: the stop line's // own second of grace, plus the exec's round trips. A box that stopped // answering is given up on after it. export const REAP_WAIT_SECONDS = 5; // How long a program a `kill` signalled may take to exit before it gets // KILL, for its handlers to clean up: the stop line's same second. export const STOP_GRACE_SECONDS = 1; // How far a host process's start, as `ps` prints it to the second, may be // from when a run recorded starting it and still be that run's: past it the // PID names another process, which a reap leaves alone. export const HOST_START_SLACK_SECONDS = 2; // How often a stop in its grace checks whether the signalled group ended. export const STOP_POLL_SECONDS = 0.02; // Where the stop line finds grep and sleep: the config's PATH is for the // programs it runs and may name none of the system's directories. export const REAP_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'; /** * The command line for a sandbox whose exec takes one (ssh, e2b, * daytona): cwd, env, then the argv. * * Such an exec has no docker-style `-w`/`-e` to trust: SSH's env channel * is AcceptEnv-gated server-side, so it silently drops names, and an * SDK's own cwd fails a missing directory in its own words. So the * working directory and the merged environment ride the command itself: * the prelude every sandbox runs a command under enters `cwd` (exit 126 * when the sandbox lacks it), then `env 'K=V' ... 'prog' 'arg'`; a shell * line is the argv `/bin/sh -c line`. Every piece is single-quoted for sh, * so the shell the exec starts reads each as one word whatever it holds; * the sandbox only needs a POSIX shell at `/bin/sh`. * * @param argv the program and its arguments. * @param env the merged environment. * @param cwd the working directory, passed through verbatim. */ export function wrapLine(argv, env, cwd) { const parts = ['/bin/sh', '-c', singleQuote(EXEC_PRELUDE), 'sh', singleQuote(cwd), 'env']; for (const [key, value] of Object.entries(env)) parts.push(singleQuote(`${key}=${value}`)); for (const word of argv) parts.push(singleQuote(word)); return parts.join(' '); } // What the stop line exits with when no process carries the run's id: the // box it ran in holds nothing of the run. export const REAP_ABSENT = 3; /** * The shell line that stops every process of the run `execId`. * * Each process whose environment holds the run's id, found through * `/proc`, gets `signum`, and what still runs a second later gets KILL, * so a program that ignores it stops too. The line exits 0 once it stopped * them and `REAP_ABSENT` when there were none. It looks grep up on its own * PATH (`REAP_PATH`) and exits 127 without it, so it needs only a POSIX * shell, grep and `/proc`. * * @param execId the run's id, as `EXEC_ID` carries it. * @param signum the first signal, TERM (15) unless a `kill` named another. */ export function reapLine(execId, signum = 15) { const mark = singleQuote(`${EXEC_ID}=${execId}`); return (`PATH=${REAP_PATH}; command -v grep >/dev/null || exit 127; ` + `m=${mark}; ` + 'left() { l=; for p in /proc/[0-9]*; do ' + 'grep -qs "$m" "$p/environ" && l="$l ${p#/proc/}"; done; ' + '[ -n "$l" ]; }; ' + `left || exit ${String(REAP_ABSENT)}; kill -${String(signum)} $l 2>/dev/null; ` + 'for i in 1 2 3 4 5 6 7 8 9 10; do sleep 0.1; left || exit 0; done; ' + 'kill -KILL $l 2>/dev/null; true'); } // How long a sandbox gets to answer the libfaketime probe, which every run // waiting on it shares. export const FAKETIME_PROBE_SECONDS = 30; // Where Linux distributions install libfaketime, which a process reads a // workspace's system time through, on this host or in a sandbox. export const FAKETIME_LIBRARIES = [ '/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1', '/usr/lib/aarch64-linux-gnu/faketime/libfaketime.so.1', '/usr/lib64/faketime/libfaketime.so.1', '/usr/lib/faketime/libfaketime.so.1', '/usr/local/lib/faketime/libfaketime.so.1', ]; /** * What has a process read the running workspace's system time through the * libfaketime at `library`: the time now, in epoch seconds to the * millisecond, which its clock and its children's run on from, whatever * the machine's own clock or zone; the monotonic clock and file dates, which * the workspace already gives, left alone. `env`'s * `LD_PRELOAD` libraries load after it. Empty when the workspace keeps the * host's time. Mirrors Python's `faketime_env`. */ export function faketimeEnv(library, env) { if (boundSystemClock() === null) return {}; const preload = env.LD_PRELOAD; return { LD_PRELOAD: preload === undefined || preload === '' ? library : `${library}:${preload}`, FAKETIME: `@${systemNow().toFixed(3)}`, FAKETIME_FMT: '%s', FAKETIME_DONT_RESET: '1', FAKETIME_DONT_FAKE_MONOTONIC: '1', NO_FAKE_STAT: '1', }; } /** * A shell line that prints the first of `FAKETIME_LIBRARIES` a sandbox * holds, and nothing when it holds none. Mirrors Python's `faketime_probe`. */ export function faketimeProbe() { const paths = FAKETIME_LIBRARIES.map(singleQuote).join(' '); return `for p in ${paths}; do if [ -f "$p" ]; then echo "$p"; break; fi; done`; } //# sourceMappingURL=constants.js.map