INTEGRITY Документация

Терминалы

Создавайте интерактивные PTY-терминалы в текущем контейнере песочницы и управляйте ими.

Общую модель работы и пошаговое руководство по подключению браузера см. в Терминалы.

createTerminal()

Запустите терминал из argv (обычно shell). Промис разрешается при создании ресурса терминала. Действуют те же правила, что и для процесса exec: без неявного оборачивания в shell, аргументы argv не экранируются для shell.

createTerminal(options: CreateTerminalOptions): Promise<Terminal>

CreateTerminalOptions

Поле Тип Описание
command SandboxCommand Argv для запуска в PTY. Обязательно. Пример: ['bash'] или ['/bin/bash'].
cwd string Рабочий каталог для процесса терминала.
env Record<string, string> Переопределение окружения для этого терминала. Не влияет на последующие запуски.
cols number Начальная ширина в столбцах.
rows number Начальная высота в строках.
bufferSize number Размер буфера вывода для воспроизведения (если поддерживается средой выполнения).

SandboxCommand это тот же тип argv, что и у process exec: readonly [executable: string, ...args: string[]].

Возвращает

Promise<Terminal> : handle для терминала в текущий контейнер.

const terminal = await sandbox.createTerminal({
	command: ["bash"],
	cwd: "/workspace",
	env: { TERM: "xterm-256color" },
	cols: 120,
	rows: 40,
});

console.log(terminal.id);
const terminal = await sandbox.createTerminal({
	command: ["bash"],
	cwd: "/workspace",
	env: { TERM: "xterm-256color" },
	cols: 120,
	rows: 40,
});

console.log(terminal.id);

getTerminal()

Возвращает дескриптор терминала в текущий контейнер, или null.

Не запускает контейнер, если ни один не выполняется. Возвращает null когда контейнер не запущен, когда идентификатор терминала неизвестен в текущем контейнере или когда этот терминал принадлежал предыдущему контейнеру с тем же идентификатором песочницы.

getTerminal(id: string): Promise<Terminal | null>

listTerminals()

Выводит список терминалов в текущем контейнере этой песочницы. Не запускает контейнер, если он не работает. Если контейнер не запущен, возвращает пустой список.

listTerminals(): Promise<Terminal[]>

Terminal

Участник Описание
id Идентификатор терминала в текущем контейнере.
getSnapshot() Текущий снимок (running / exited / error).
write(data) Запись байтов в PTY (stdin).
resize(cols, rows) Изменяет размер PTY.
output(options?) Поток событий вывода на основе курсора.
waitForExit(options?) Дождитесь завершения терминала.
interrupt() Отправьте прерывание сессии терминала (например, семантику Ctrl-C).
terminate() Завершите ресурс терминала.
connect(request, opts?) Примите обновление WebSocket от браузера и подключите его к этому терминалу.

getSnapshot()

interface TerminalSnapshot {
	id: string;
	pid?: number;
	command: SandboxCommand;
	cwd?: string;
	status: "running" | "exited" | "error";
	exit?: ProcessExit;
	error?: ProcessFailure;
}

write()

write(data: Uint8Array): Promise<void>

Запись байтов в PTY. Нажатия клавиш в браузере обычно поступают через connect() взамен.

resize()

resize(cols: number, rows: number): Promise<void>

output()

output(options?: TerminalOutputOptions): Promise<ReadableStream<TerminalOutputEvent>>

TerminalOutputOptions

Поле Тип Описание
since string Непрозрачный курсор; возобновление после предыдущего события.
replay boolean Включайте буферизованную историю при возобновлении.
follow boolean Держите поток открытым для вывода в реальном времени.
signal AbortSignal Отмените только эту подписку. Терминал продолжит работать.

TerminalOutputEvent

type TerminalOutputEvent =
	| {
			type: "data";
			terminalId: string;
			cursor: string;
			timestamp: string;
			data: Uint8Array;
	  }
	| {
			type: "terminal";
			terminalId: string;
			cursor: string;
			timestamp: string;
			state: "exited";
			exit: ProcessExit;
	  }
	| {
			type: "terminal";
			terminalId: string;
			cursor: string;
			timestamp: string;
			state: "error";
			error: ProcessFailure;
	  }
	| {
			type: "truncated";
			terminalId: string;
			cursor?: string;
			timestamp: string;
	  };

Сохранение последних cursor из доставленных событий при повторном подключении или вызове output({ since, replay: true }) позже в одинаковый терминал в одинаковый контейнер.

waitForExit()

waitForExit(options?: {
	timeout?: number;
	signal?: AbortSignal;
}): Promise<ProcessExit>

Локальный timeout / signal отменяют только ожидание. Они не завершают терминал. Вызовите terminate() или interrupt() когда вы собираетесь остановить его.

interrupt() и terminate()

interrupt(): Promise<void>
terminate(): Promise<void>

Это операции управления терминалом. Они отличаются от операций с процессом kill(signal) на exec хендл.

connect()

Подключите к этому терминалу запрос на обновление WebSocket от браузера (или другого источника).

connect(
	request: Request,
	options?: {
		cursor?: string;
		cols?: number;
		rows?: number;
	},
): Promise<Response>

Возвращает обновление до WebSocket Response ваш Worker должен вернуть клиенту.

const url = new URL(request.url);
const terminalId = url.searchParams.get("terminalId");
if (!terminalId) {
	return new Response("terminalId is required", { status: 400 });
}

const terminal = await sandbox.getTerminal(terminalId);
if (!terminal) {
	return new Response("Terminal not found", { status: 404 });
}

return terminal.connect(request, {
	cursor: url.searchParams.get("cursor") ?? undefined,
	cols: 120,
	rows: 40,
});
const url = new URL(request.url);
const terminalId = url.searchParams.get("terminalId");
if (!terminalId) {
	return new Response("terminalId is required", { status: 400 });
}

const terminal = await sandbox.getTerminal(terminalId);
if (!terminal) {
	return new Response("Terminal not found", { status: 404 });
}

return terminal.connect(request, {
	cursor: url.searchParams.get("cursor") ?? undefined,
	cols: 120,
	rows: 40,
});

Полный путь с Worker и xterm.js см. в Терминалы.

Клиентский помощник: @cloudflare/sandbox/xterm

SandboxAddon интегрирует xterm.js ↗ с preview-терминалами.

import { SandboxAddon } from "@cloudflare/sandbox/xterm";

const addon = new SandboxAddon({
	// `origin` is already a WebSocket origin (`wss://` or `ws://`).
	getWebSocketUrl: ({ sandboxId, terminalId, cursor, origin }) => {
		const params = new URLSearchParams({ sandboxId });
		if (terminalId) params.set("terminalId", terminalId);
		if (cursor) params.set("cursor", cursor);
		return `${origin}/ws/terminal?${params}`;
	},
	reconnect: true,
	onStateChange: (state, error) => {
		/* update UI */
	},
});
import { SandboxAddon } from "@cloudflare/sandbox/xterm";

const addon = new SandboxAddon({
	// `origin` is already a WebSocket origin (`wss://` or `ws://`).
	getWebSocketUrl: ({ sandboxId, terminalId, cursor, origin }) => {
		const params = new URLSearchParams({ sandboxId });
		if (terminalId) params.set("terminalId", terminalId);
		if (cursor) params.set("cursor", cursor);
		return `${origin}/ws/terminal?${params}`;
	},
	reconnect: true,
	onStateChange: (state, error) => {
		/* update UI */
	},
});
Элемент Детали предпросмотра
Цель подключения { sandboxId, terminalId? }
getWebSocketUrl параметры sandboxId, terminalId?, cursor?, origin
Свойства state, sandboxId, terminalId

@xterm/xterm это необязательная peer зависимость предварительного пакета.

Распространённые ошибки

Ситуация Класс / результат
Неизвестный идентификатор терминала в текущем контейнере TerminalNotFoundError
getTerminal / listTerminals пока не запущен ни один контейнер null / [] (это не ошибка; контейнер при этом не запускается)
Дескриптор или ID терминала от предыдущего контейнера StaleTerminalHandleError
Недопустимый рабочий каталог при создании InvalidTerminalCwdError
Недопустимый курсор вывода InvalidTerminalCursorError
Не удалось выполнить управляющую операцию TerminalControlError

Рекомендации по восстановлению: Ошибки и восстановление. Полный каталог: API ошибок. Время жизни: Сколько живёт процесс.