UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

204 lines • 9.86 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 { quoteText } from "../../../../commands/quote.js"; import { NUMERIC_SHORT } from "../../../../commands/spec/constants.js"; import { specOf } from "../../../../commands/spec/index.js"; import { helpPage, versionLine } from "../../../../commands/spec/standard.js"; import { ambiguousOptionError, unexpectedValueError, unknownOptionError, usageHint, } from "../../../../commands/spec/usage.js"; import { yieldBytes } from "../../../../io/stream.js"; import { IOResult } from "../../../../io/types.js"; import { encodeText } from "../../../../shell/bytes.js"; import { sleep } from "../../../../utils/abort.js"; import { ExecutionNode } from "../../../types.js"; import { SLEEP_INTERVAL, SLEEP_SUFFIXES } from "./constants.js"; // The only two options coreutils sleep accepts; its --help page lists no // others. They share no prefix, so an abbreviation of either resolves and // neither can ever be ambiguous. const STANDARD_OPTIONS = ['--help', '--version']; /** * The standard options a long spelling names, declaration order. * * getopt_long takes an exact word outright and otherwise keeps every candidate * the word prefixes, so `sleep --h` is one match (help, exit 0) and * `sleep --=x` is an empty name that prefixes both, which GNU refuses as * ambiguous. Measured on 9.7. `_standard_matches` in sleep.py is the twin. */ function standardMatches(name) { const exact = STANDARD_OPTIONS.find((word) => word === name); if (exact !== undefined) return [exact]; return STANDARD_OPTIONS.filter((word) => word.startsWith(name)); } /** * sleep's answer to `--help` or `--version`: stdout, exit 0. * * The page is built by `helpPage`, the one function every registered * command's `--help` goes through (commands/config.ts), so the two cannot * drift. That matters here because sleep is a shell builtin rather than a * registered command: nothing injects the two standard options into its * spec, so rendering that spec directly produced a page documenting neither * of the options this arm exists to answer, under a synthesized * `sleep [<text>...]` in place of GNU's own `sleep NUMBER[SUFFIX]...`. */ function standardResponse(option) { const text = option === '--help' ? helpPage('sleep', specOf('sleep')) : versionLine('sleep'); return [ yieldBytes(encodeText(text)), new IOResult(), new ExecutionNode({ command: 'sleep', exitCode: 0 }), ]; } /** * sleep's operands, and the first dash word that is not one. * * coreutils sleep accepts only `--help` and `--version` and refuses any other * dash-leading word wherever that word sits: measured on 9.4, `sleep --zzz 0` * and `sleep 0 --zzz` both report the option and neither reports the interval. * The caller decides what that word means, since a `--help`/`--version` * spelling is an answer rather than a refusal. `--` ends the scan, which is * what makes `sleep -- '--zzz=é'` an interval diagnostic instead of an option * one, and a `-<digits>` word stays an operand -- mirage's NUMERIC_SHORT rule, * which every command shares, and the reason `sleep -1` names the interval * where GNU names the option letter. * * The returned word is the whole token for a long spelling and the offending * letter for a short one, the split `unknownOptionError` already words. * * `_sleep_operands` in sleep.py is the twin. */ function sleepOperands(args) { const operands = []; for (const [index, arg] of args.entries()) { if (arg === '--') { for (const item of args.slice(index + 1)) operands.push(item); break; } if (arg.startsWith('-') && arg.length > 1 && !NUMERIC_SHORT.test(arg)) { return [operands, arg.startsWith('--') ? arg : (arg[1] ?? arg)]; } operands.push(arg); } return [operands, null]; } /** * One operand's seconds, or null when it is not an interval. * * GNU's grammar is `NUMBER[SUFFIX]`, which the help page advertises: a * floating-point number followed by at most ONE suffix letter, one of s, m, h * or d. Measured on coreutils 9.7: `sleep 0.005m` takes 0.3s, `sleep 1e-3s` is * accepted, and `sleep 0S`, `sleep 0ss`, `sleep 0sx` and `sleep s` are each * `invalid time interval`. Null also covers GNU's own "inf", a documented * divergence carried by SLEEP_INTERVAL. `_interval_seconds` in sleep.py is the * twin. */ function intervalSeconds(raw) { const multiplier = SLEEP_SUFFIXES[raw.slice(-1)] ?? 0; const num = multiplier ? raw.slice(0, -1) : raw; if (!SLEEP_INTERVAL.test(num)) return null; const seconds = Number(num) * (multiplier || 1); // "1e309" passes the regex and overflows to Infinity. return Number.isFinite(seconds) ? seconds : null; } export async function handleSleep(args, signal) { const [operands, badOption] = sleepOperands(args); if (badOption !== null) { // The scan stops at the first dash word, and that word decides the whole // line: `sleep --help --zzz` is help and `sleep --zzz --help` is the // refusal (measured on 9.7). A long spelling is first offered to the two // standard options, which are real getopt_long options, so an abbreviation // resolves and a value on one is refused for the VALUE rather than as an // unknown option (`sleep --hel=x` is `option '--help' doesn't allow an // argument`). const eq = badOption.indexOf('='); const name = eq === -1 ? badOption : badOption.slice(0, eq); const matches = name.startsWith('--') ? standardMatches(name) : []; const sole = matches[0]; if (sole !== undefined && matches.length === 1 && eq === -1) { return standardResponse(sole); } // `--=x` is an empty long name, which prefixes both, and getopt_long // quotes the WHOLE token here where the doesn't-allow-an-argument refusal // quotes the canonical spelling. const [message, code] = matches.length > 1 ? ambiguousOptionError('sleep', badOption, matches) : sole !== undefined ? unexpectedValueError('sleep', sole) : unknownOptionError('sleep', badOption); return [ null, new IOResult({ exitCode: code, stderr: message }), new ExecutionNode({ command: 'sleep', exitCode: code }), ]; } if (operands.length === 0) { // Missing operand is refused like an invalid interval: exit 1 under the // same closing Try-help line (measured on 9.4: `sleep` is two lines, not // one). const err = encodeText(`sleep: missing operand\n${usageHint('sleep')}\n`); return [ null, new IOResult({ exitCode: 1, stderr: err }), new ExecutionNode({ command: 'sleep', exitCode: 1 }), ]; } // `NUMBER[SUFFIX]...`: every operand is an interval and the line sleeps // their SUM (measured on 9.7: `sleep 0.3 0.3` takes 0.6s). All of them are // checked before any of them is slept, so a bad one anywhere refuses the // whole line immediately rather than after sleeping its predecessors // (`sleep 0.2 x 0.2` exits 1 at once). let total = 0; const bad = []; for (const raw of operands) { const seconds = intervalSeconds(raw); if (seconds === null) { bad.push(raw); continue; } // The SUM is what gets slept, so an operand that carries it past the // representable range is refused exactly like one that is not finite on // its own: `sleep 1e308 1e308` overflows to Infinity, and an infinite // total slipped past the check each operand passes alone. GNU sleeps // forever on it (measured on 9.7, as it does on `sleep inf`); refusing it // is the same deliberate divergence SLEEP_INTERVAL already carries, for // the same reason. if (!Number.isFinite(total + seconds)) { bad.push(raw); continue; } total += seconds; } if (bad.length > 0) { // GNU names EVERY bad operand, one line each, in line order and repeated // if it repeats, under one closing Try-help line (measured on 9.7: // `sleep 1x 2y` is three lines, `sleep 1x 1x` names 1x twice). Each // operand is quoted the way every other coreutils operand diagnostic is // (measured on 9.4: `sleep -- é` names `'\303\251'`). const lines = bad.map((raw) => `sleep: invalid time interval '${quoteText(raw)}'\n`); const err = encodeText(`${lines.join('')}${usageHint('sleep')}\n`); return [ null, new IOResult({ exitCode: 1, stderr: err }), new ExecutionNode({ command: 'sleep', exitCode: 1 }), ]; } await sleep(total * 1000, signal); return [null, new IOResult(), new ExecutionNode({ command: 'sleep', exitCode: 0 })]; } /** The `sleep` arm; the abort signal ends the wait early. */ export async function sleepBuiltin(call) { return handleSleep([...call.argv.args], call.signal); } //# sourceMappingURL=sleep.js.map