Reference
Arcane.terminal
Bounded native terminal-session lifecycle and input operations.
This focused page is derived from the mechanically checked full member inventory.
Syntax
Arcane.terminal
Member kind
Namespace
Description
Bounded native terminal-session lifecycle and input operations.
Overview
Arcane.terminal owns a bounded native terminal-session lifecycle. It can start
and enumerate app-owned sessions, write input, update dimensions, send a
supported control signal, and request closure. Process output and final exit
state arrive through Arcane.events; they are not returned by write(),
signal(), or close().
Terminal access is deliberately narrow. The bound application must have the
exact id terminal, must be granted terminal.execute, and must run through an
admitted Core or Android host. Other applications do not receive terminal access
by default, and a standalone browser preview is not a native terminal host.
Availability and capability check
Feature detection answers whether the JavaScript surface was projected into the
document. Arcane.capabilities.list() confirms the bound application identity,
grants, and exact admitted RPC methods. Check both before presenting a terminal
workflow.
The complete namespace contains these methods:
| Member | Purpose |
|---|---|
Arcane.terminal.start(options?) |
Start one native session. |
Arcane.terminal.list() |
Return the wrapper containing current app-owned sessions. |
Arcane.terminal.write(sessionId, data) |
Write one nonempty UTF-8 input chunk. |
Arcane.terminal.resize(sessionId, columns, rows) |
Update the session's bounded dimensions. |
Arcane.terminal.signal(sessionId, signal?) |
Request interrupt or terminate. |
Arcane.terminal.close(sessionId) |
Request session closure. |
The host admits at most eight concurrent sessions for the application. Session
identifiers are opaque strings returned by start() or list(); do not invent,
parse, or persist them as durable identities.
Session lifecycle
Subscribe to terminal events before calling start(). A newly spawned process
may produce output immediately, and the output forwarding path is active before
the start response reaches application code. Buffer early events by sessionId
until the returned session identifies the process of interest.
The normal lifecycle is:
- Confirm the feature, application id, grant, and admitted methods.
- Subscribe to
terminal.output,terminal.exit, andterminal.error. - Call
start()and retain the returned session object. - Use
write(),resize(), and, when necessary,signal(). - Call
close()when the application is finished with the session. - Treat
terminal.exitas the process-completion observation. - Unsubscribe every listener during teardown.
An accepted write, signal, or close request is not command completion or process
exit. In particular, close() resolves when the host accepts the close request;
observe terminal.exit for the final process outcome.
Shared values
start() resolves to this exact session shape:
| Property | Type | Meaning |
|---|---|---|
id |
string |
Opaque session identifier, at most 128 characters. |
shell |
string |
Resolved shell name. |
cwd |
string |
Resolved working directory. |
title |
string |
Host-provided display title. |
columns |
number |
Accepted column count, from 20 through 500. |
rows |
number |
Accepted row count, from 5 through 200. |
createdAt |
string |
Host timestamp for session creation. |
list() resolves to { sessions }, not directly to an array. Each list entry
contains id, shell, cwd, columns, rows, createdAt, and state.
Supported state values are starting, running, exited, and closed, though a
host may remove a completed session from the current inventory promptly.
Terminal events
Terminal events are ordinary, future-only events:
| Event | Trigger | Data payload |
|---|---|---|
terminal.output |
The host reads a stdout or stderr chunk. | { sessionId, stream, data }, where stream is "stdout" or "stderr" and data is a string. |
terminal.exit |
The process exits and the host retires the session. | { sessionId, exitCode, signal }; exitCode or signal may be null. |
terminal.error |
A host reports an asynchronous stream/session error. | { sessionId, message }; currently used by the Android provider for output-limit and stream-read failures. |
Output payloads are chunks, not lines. A chunk may contain part of a line, several lines, or terminal control sequences. Preserve arrival order per session and use a terminal-aware renderer when displaying native output.
Platform behavior
On Microsoft NT Core hosts, auto resolves to PowerShell. PowerShell, Command
Prompt, and an installed Bash are selectable; POSIX sh is unavailable. On
Linux Core hosts, auto resolves to Bash, sh selects /bin/sh, PowerShell
requires an installed pwsh, and Command Prompt is unavailable.
On Android, terminal execution is confined to the Arcane Terminal application's
ordinary app identity and private files area. Only auto and sh are accepted,
and both resolve to the application-sandbox /system/bin/sh. Android stops a
session after one MiB of emitted output and reports the condition through
terminal.error.
Errors and recovery
Rejected operations use Arcane.Error. Read code, message, and resolution
instead of matching the message text. Common terminal failures include:
| Code | Recovery |
|---|---|
METHOD_NOT_ALLOWED |
Open Arcane Terminal through an admitted host; another app cannot self-grant terminal.execute. |
ARCANE_TRANSPORT_UNAVAILABLE |
Open the application through the installed Arcane host or its development launcher. |
METHOD_CONTRACT_INPUT_INVALID or TERMINAL_REQUEST_INVALID |
Send only the documented values and bounds. |
TERMINAL_SESSION_LIMIT |
Close an existing session before starting another. |
TERMINAL_SHELL_INVALID or TERMINAL_SHELL_UNAVAILABLE |
Select a supported shell for the active platform. |
TERMINAL_CWD_INVALID |
Choose an existing accessible directory allowed by the host sandbox. |
TERMINAL_START_FAILED |
Verify that the selected shell is installed and available. |
TERMINAL_SESSION_INVALID or TERMINAL_SESSION_NOT_FOUND |
Refresh with list() or start a new session; do not reuse a retired id. |
TERMINAL_DATA_INVALID or TERMINAL_INPUT_CLOSED |
Send a nonempty chunk no larger than 64 KiB to a running session. |
TERMINAL_SIGNAL_INVALID |
Use only interrupt or terminate. |
Example
const arcane = globalThis.Arcane;
const terminal = arcane?.terminal;
const events = arcane?.events;
if (!terminal?.start || !events?.on || !arcane?.capabilities?.list) {
throw new Error('Open Arcane Terminal from an admitted Arcane host.');
}
const access = await arcane.capabilities.list();
const requiredMethods = [
'terminal.start',
'terminal.list',
'terminal.write',
'terminal.resize',
'terminal.close'
];
if (
access.app?.id !== 'terminal'
|| !access.grants.includes('terminal.execute')
|| !requiredMethods.every(function isRequiredTerminalMethodAdmitted(method) {
return access.methods.includes(method);
})
) {
throw new Error('This application is not admitted for terminal execution.');
}
const earlyOutput = new Map();
const observedExits = new Map();
const exitWaiters = new Map();
const maxBufferedChunksPerSession = 128;
let activeSessionId = null;
let session = null;
function displayOutput({stream, data}) {
const write = stream === 'stderr' ? console.error : console.log;
write(data);
}
const offOutput = events.on('terminal.output', function handleTerminalOutput(payload) {
if (payload.sessionId === activeSessionId) {
displayOutput(payload);
return;
}
const chunks = earlyOutput.get(payload.sessionId) ?? [];
chunks.push(payload);
// Bound pre-identification buffering; surface truncation in a real UI.
if (chunks.length > maxBufferedChunksPerSession) {
chunks.shift();
}
earlyOutput.set(payload.sessionId, chunks);
});
const offExit = events.on('terminal.exit', function handleTerminalExit(payload) {
observedExits.set(payload.sessionId, payload);
const waiter = exitWaiters.get(payload.sessionId);
if (waiter) {
clearTimeout(waiter.timer);
exitWaiters.delete(payload.sessionId);
waiter.resolve(payload);
}
});
const offError = events.on('terminal.error', function handleTerminalError(payload) {
console.error(`Terminal ${payload.sessionId}: ${payload.message}`);
});
function waitForExit(sessionId, timeoutMs = 5000) {
if (observedExits.has(sessionId)) {
return Promise.resolve(observedExits.get(sessionId));
}
return new Promise(function createExitWait(resolve, reject) {
const timer = setTimeout(function rejectTimedOutExitWait() {
exitWaiters.delete(sessionId);
reject(new Error(`Timed out waiting for ${sessionId} to exit.`));
}, timeoutMs);
exitWaiters.set(sessionId, {resolve, timer});
});
}
try {
session = await terminal.start({
shell: 'auto',
cwd: '',
columns: 120,
rows: 32
});
activeSessionId = session.id;
for (const payload of earlyOutput.get(session.id) ?? []) {
displayOutput(payload);
}
earlyOutput.delete(session.id);
await terminal.resize(session.id, 100, 30);
const {sessions} = await terminal.list();
console.log('Owned sessions', sessions);
const lineEnding = ['powershell', 'cmd'].includes(session.shell)
? '\r\n'
: '\n';
await terminal.write(
session.id,
`echo Arcane terminal ready${lineEnding}`
);
const exitPromise = waitForExit(session.id);
const closeResult = await terminal.close(session.id);
console.log('Close request accepted', closeResult.accepted);
const exit = await exitPromise;
console.log('Process exited', exit.exitCode, exit.signal);
} catch (error) {
if (error instanceof arcane.Error) {
console.error(error.code, error.message, error.resolution);
} else {
throw error;
}
} finally {
if (session && !observedExits.has(session.id)) {
await terminal.close(session.id).catch(function ignoreTerminalCloseFailure() {});
}
offOutput();
offExit();
offError();
for (const waiter of exitWaiters.values()) {
clearTimeout(waiter.timer);
}
exitWaiters.clear();
}
Members
Arcane.terminal.start()— Starts one of at most eight app-owned native terminal sessions. Requires terminal.execute, app id terminal, and a Core or Android host.Arcane.terminal.list()— Lists the current app-owned sessions; it returns a wrapper object, not a bare array. Requires terminal.execute, app id terminal, and a Core or Android host.Arcane.terminal.write()— Writes one input chunk. Output is delivered separately through terminal.output, so subscribe before starting a session and correlate chunks by sessionId.Arcane.terminal.resize()— Updates the session dimensions. The current hosts record an emulated resize rather than claiming a native pseudo-terminal resize.Arcane.terminal.signal()— Requests a supported control signal. accepted reports process-controller acceptance, not process exit; observe terminal.exit for completion.Arcane.terminal.close()— Closes input and requests session termination. The resolved value acknowledges the request; observe terminal.exit for actual process completion.