UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

125 lines • 5.38 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 { boundedDecimal } from "../../../../commands/builtin/utils/strtol.js"; import { decodeText, encodeText } from "../../../../shell/bytes.js"; import { builtinError, builtinUsage, result } from "../shared.js"; import { SIGNALS } from "../timeout/constants.js"; import { PSEUDO_SIGNALS, RUN_EVENTS, SIGNAL_MAX } from "./constants.js"; import { TrapEvent } from "./types.js"; /** * What a signal spec names: EXIT, ERR or RETURN, another signal bash knows * (which mirage cannot deliver), or nothing bash would accept. A name may * carry `SIG` and any case; a number is one too. */ export function eventOf(spec) { if (/^[0-9]+$/.test(spec)) { const number = boundedDecimal(spec, BigInt(SIGNAL_MAX) + 1n); if (number === null) return null; return number === 0n ? TrapEvent.Exit : TrapEvent.Other; } const name = spec.toUpperCase(); if (Object.hasOwn(RUN_EVENTS, name)) return name; if (PSEUDO_SIGNALS.has(name)) return TrapEvent.Other; const base = name.startsWith('SIG') ? name.slice(3) : name; if ([...SIGNALS.values()].includes(base)) return TrapEvent.Other; if (base.startsWith('RTMIN') || base.startsWith('RTMAX')) return TrapEvent.Other; return null; } /** One `trap -p` row, the action single-quoted the way bash does. */ export function listing(action, event) { return `trap -- '${action.replaceAll("'", "'\\''")}' ${event}\n`; } /** * Register, reset or list the shell's `EXIT`, `ERR` and `RETURN` actions. * * EXIT runs where the shell ends: at `exit` (in the frame that called it), * at the end of a child shell, or when an error ends the shell. A line of a * persistent session is not the end of its shell, so it runs nothing there. * ERR runs after a command fails where `set -e` would act, RETURN after a * function or a sourced file returns (`executor/traps.ts`). Mirage delivers * no signals, so any other event bash knows is refused rather than accepted * and never run. */ export function handleTrap(args, session) { const words = [...args]; let printing = false; for (let word = words[0]; word?.startsWith('-') === true && word !== '-'; word = words[0]) { words.shift(); if (word === '--') break; for (const flag of word.slice(1)) { if (flag === 'p') printing = true; else if (flag === 'l') { return result('trap', { exitCode: 2, stderr: 'mirage: trap: -l: not supported\n' }); } else { const message = decodeText(builtinError('trap', `-${flag}: invalid option`)); return result('trap', { exitCode: 2, stderr: message + builtinUsage('trap') }); } } } const errors = []; if (printing || words.length === 0) { const out = []; for (const spec of words.length > 0 ? words : Object.keys(RUN_EVENTS)) { const event = eventOf(spec); if (event === null) { errors.push(decodeText(builtinError('trap', `${spec}: invalid signal specification`))); } else if (event !== TrapEvent.Other) { const action = session[RUN_EVENTS[event][0]]; if (action !== null) out.push(listing(action, event)); } } const text = out.join(''); return result('trap', { out: text !== '' ? encodeText(text) : null, exitCode: errors.length > 0 ? 1 : 0, stderr: errors.join(''), }); } const [first = '', ...rest] = words; if (rest.length === 0 && eventOf(first) === null) { return result('trap', { exitCode: 2, stderr: builtinUsage('trap') }); } const action = rest.length === 0 ? '-' : first; const specs = rest.length === 0 ? words : rest; for (const spec of specs) { const event = eventOf(spec); if (event === null) { errors.push(decodeText(builtinError('trap', `${spec}: invalid signal specification`))); } else if (event !== TrapEvent.Other) { const [field, scoped] = RUN_EVENTS[event]; session[field] = action === '-' ? null : action; session[scoped] = false; } else if (action !== '-') { errors.push(`mirage: trap: ${spec}: not supported\n`); } } return result('trap', { exitCode: errors.length > 0 ? 1 : 0, stderr: errors.join('') }); } /** The `trap` arm. */ export function trapBuiltin(call) { return Promise.resolve(handleTrap(call.argv.args, call.context.session)); } //# sourceMappingURL=trap.js.map