426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
324 lines
7.0 KiB
TypeScript
324 lines
7.0 KiB
TypeScript
/**
|
|
* Encodes keyboard input into the escape sequences a PTY expects — bracketed
|
|
* paste, control keys, and named key sequences — and strips terminal DSR
|
|
* (device-status-report) requests from output. Used when driving interactive
|
|
* PTY sessions.
|
|
*/
|
|
|
|
const ESC = "\x1b";
|
|
const CR = "\r";
|
|
const TAB = "\t";
|
|
const BACKSPACE = "\x7f";
|
|
|
|
export const BRACKETED_PASTE_START = `${ESC}[200~`;
|
|
export const BRACKETED_PASTE_END = `${ESC}[201~`;
|
|
|
|
type Modifiers = {
|
|
ctrl: boolean;
|
|
alt: boolean;
|
|
shift: boolean;
|
|
};
|
|
|
|
function escapeRegExp(value: string): string {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
const namedKeyMap = new Map<string, string>([
|
|
["enter", CR],
|
|
["return", CR],
|
|
["tab", TAB],
|
|
["escape", ESC],
|
|
["esc", ESC],
|
|
["space", " "],
|
|
["bspace", BACKSPACE],
|
|
["backspace", BACKSPACE],
|
|
["up", `${ESC}[A`],
|
|
["down", `${ESC}[B`],
|
|
["right", `${ESC}[C`],
|
|
["left", `${ESC}[D`],
|
|
["home", `${ESC}[1~`],
|
|
["end", `${ESC}[4~`],
|
|
["pageup", `${ESC}[5~`],
|
|
["pgup", `${ESC}[5~`],
|
|
["ppage", `${ESC}[5~`],
|
|
["pagedown", `${ESC}[6~`],
|
|
["pgdn", `${ESC}[6~`],
|
|
["npage", `${ESC}[6~`],
|
|
["insert", `${ESC}[2~`],
|
|
["ic", `${ESC}[2~`],
|
|
["delete", `${ESC}[3~`],
|
|
["del", `${ESC}[3~`],
|
|
["dc", `${ESC}[3~`],
|
|
["btab", `${ESC}[Z`],
|
|
["f1", `${ESC}OP`],
|
|
["f2", `${ESC}OQ`],
|
|
["f3", `${ESC}OR`],
|
|
["f4", `${ESC}OS`],
|
|
["f5", `${ESC}[15~`],
|
|
["f6", `${ESC}[17~`],
|
|
["f7", `${ESC}[18~`],
|
|
["f8", `${ESC}[19~`],
|
|
["f9", `${ESC}[20~`],
|
|
["f10", `${ESC}[21~`],
|
|
["f11", `${ESC}[23~`],
|
|
["f12", `${ESC}[24~`],
|
|
["kp/", `${ESC}Oo`],
|
|
["kp*", `${ESC}Oj`],
|
|
["kp-", `${ESC}Om`],
|
|
["kp+", `${ESC}Ok`],
|
|
["kp7", `${ESC}Ow`],
|
|
["kp8", `${ESC}Ox`],
|
|
["kp9", `${ESC}Oy`],
|
|
["kp4", `${ESC}Ot`],
|
|
["kp5", `${ESC}Ou`],
|
|
["kp6", `${ESC}Ov`],
|
|
["kp1", `${ESC}Oq`],
|
|
["kp2", `${ESC}Or`],
|
|
["kp3", `${ESC}Os`],
|
|
["kp0", `${ESC}Op`],
|
|
["kp.", `${ESC}On`],
|
|
["kpenter", `${ESC}OM`],
|
|
]);
|
|
|
|
const modifiableNamedKeys = new Set([
|
|
"up",
|
|
"down",
|
|
"left",
|
|
"right",
|
|
"home",
|
|
"end",
|
|
"pageup",
|
|
"pgup",
|
|
"ppage",
|
|
"pagedown",
|
|
"pgdn",
|
|
"npage",
|
|
"insert",
|
|
"ic",
|
|
"delete",
|
|
"del",
|
|
"dc",
|
|
]);
|
|
|
|
export type KeyEncodingRequest = {
|
|
keys?: string[];
|
|
hex?: string[];
|
|
literal?: string;
|
|
};
|
|
|
|
export type KeyEncodingResult = {
|
|
data: string;
|
|
warnings: string[];
|
|
};
|
|
|
|
export function encodeKeySequence(request: KeyEncodingRequest): KeyEncodingResult {
|
|
const warnings: string[] = [];
|
|
let data = "";
|
|
|
|
if (request.literal) {
|
|
data += request.literal;
|
|
}
|
|
|
|
if (request.hex?.length) {
|
|
for (const raw of request.hex) {
|
|
const byte = parseHexByte(raw);
|
|
if (byte === null) {
|
|
warnings.push(`Invalid hex byte: ${raw}`);
|
|
continue;
|
|
}
|
|
data += String.fromCharCode(byte);
|
|
}
|
|
}
|
|
|
|
if (request.keys?.length) {
|
|
for (const token of request.keys) {
|
|
data += encodeKeyToken(token, warnings);
|
|
}
|
|
}
|
|
|
|
return { data, warnings };
|
|
}
|
|
|
|
export function encodePaste(text: string, bracketed = true): string {
|
|
if (!bracketed) {
|
|
return text;
|
|
}
|
|
return `${BRACKETED_PASTE_START}${text}${BRACKETED_PASTE_END}`;
|
|
}
|
|
|
|
function encodeKeyToken(raw: string, warnings: string[]): string {
|
|
const token = raw.trim();
|
|
if (!token) {
|
|
return "";
|
|
}
|
|
|
|
if (token.length === 2 && token.startsWith("^")) {
|
|
const ctrl = toCtrlChar(token[1]);
|
|
if (ctrl) {
|
|
return ctrl;
|
|
}
|
|
}
|
|
|
|
const parsed = parseModifiers(token);
|
|
const base = parsed.base;
|
|
const baseLower = base.toLowerCase();
|
|
|
|
if (baseLower === "tab" && parsed.mods.shift) {
|
|
return `${ESC}[Z`;
|
|
}
|
|
|
|
const baseSeq = namedKeyMap.get(baseLower);
|
|
if (baseSeq) {
|
|
let seq = baseSeq;
|
|
if (modifiableNamedKeys.has(baseLower) && hasAnyModifier(parsed.mods)) {
|
|
const mod = xtermModifier(parsed.mods);
|
|
if (mod > 1) {
|
|
const modified = applyXtermModifier(seq, mod);
|
|
if (modified) {
|
|
seq = modified;
|
|
return seq;
|
|
}
|
|
}
|
|
}
|
|
if (parsed.mods.alt) {
|
|
return `${ESC}${seq}`;
|
|
}
|
|
return seq;
|
|
}
|
|
|
|
if (base.length === 1) {
|
|
return applyCharModifiers(base, parsed.mods);
|
|
}
|
|
|
|
if (parsed.hasModifiers) {
|
|
warnings.push(`Unknown key "${base}" for modifiers; sending literal.`);
|
|
}
|
|
return base;
|
|
}
|
|
|
|
function parseModifiers(token: string): {
|
|
mods: Modifiers;
|
|
base: string;
|
|
hasModifiers: boolean;
|
|
} {
|
|
const mods: Modifiers = { ctrl: false, alt: false, shift: false };
|
|
let rest = token;
|
|
let sawModifiers = false;
|
|
|
|
while (rest.length > 2 && rest[1] === "-") {
|
|
const mod = rest[0].toLowerCase();
|
|
if (mod === "c") {
|
|
mods.ctrl = true;
|
|
} else if (mod === "m") {
|
|
mods.alt = true;
|
|
} else if (mod === "s") {
|
|
mods.shift = true;
|
|
} else {
|
|
break;
|
|
}
|
|
sawModifiers = true;
|
|
rest = rest.slice(2);
|
|
}
|
|
|
|
return { mods, base: rest, hasModifiers: sawModifiers };
|
|
}
|
|
|
|
function applyCharModifiers(char: string, mods: Modifiers): string {
|
|
let value = char;
|
|
if (mods.shift && value.length === 1 && /[a-z]/.test(value)) {
|
|
value = value.toUpperCase();
|
|
}
|
|
if (mods.ctrl) {
|
|
const ctrl = toCtrlChar(value);
|
|
if (ctrl) {
|
|
value = ctrl;
|
|
}
|
|
}
|
|
if (mods.alt) {
|
|
value = `${ESC}${value}`;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function toCtrlChar(char: string): string | null {
|
|
if (char.length !== 1) {
|
|
return null;
|
|
}
|
|
if (char === "?") {
|
|
return "\x7f";
|
|
}
|
|
const code = char.toUpperCase().charCodeAt(0);
|
|
if (code >= 64 && code <= 95) {
|
|
return String.fromCharCode(code & 0x1f);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function xtermModifier(mods: Modifiers): number {
|
|
let mod = 1;
|
|
if (mods.shift) {
|
|
mod += 1;
|
|
}
|
|
if (mods.alt) {
|
|
mod += 2;
|
|
}
|
|
if (mods.ctrl) {
|
|
mod += 4;
|
|
}
|
|
return mod;
|
|
}
|
|
|
|
function applyXtermModifier(sequence: string, modifier: number): string | null {
|
|
const escPattern = escapeRegExp(ESC);
|
|
const csiNumber = new RegExp(`^${escPattern}\\[(\\d+)([~A-Z])$`);
|
|
const csiArrow = new RegExp(`^${escPattern}\\[(A|B|C|D|H|F)$`);
|
|
|
|
const numberMatch = sequence.match(csiNumber);
|
|
if (numberMatch) {
|
|
return `${ESC}[${numberMatch[1]};${modifier}${numberMatch[2]}`;
|
|
}
|
|
|
|
const arrowMatch = sequence.match(csiArrow);
|
|
if (arrowMatch) {
|
|
return `${ESC}[1;${modifier}${arrowMatch[1]}`;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function hasAnyModifier(mods: Modifiers): boolean {
|
|
return mods.ctrl || mods.alt || mods.shift;
|
|
}
|
|
|
|
function parseHexByte(raw: string): number | null {
|
|
const trimmed = raw.trim().toLowerCase();
|
|
const normalized = trimmed.startsWith("0x") ? trimmed.slice(2) : trimmed;
|
|
if (!/^[0-9a-f]{1,2}$/.test(normalized)) {
|
|
return null;
|
|
}
|
|
const value = Number.parseInt(normalized, 16);
|
|
if (Number.isNaN(value) || value < 0 || value > 0xff) {
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// DSR (Device Status Report) handling
|
|
const DSR_PATTERN = new RegExp(`${ESC}\\[\\??6n`, "g");
|
|
|
|
export function stripDsrRequests(input: string): {
|
|
cleaned: string;
|
|
requests: number;
|
|
} {
|
|
let requests = 0;
|
|
const cleaned = input.replace(DSR_PATTERN, () => {
|
|
requests += 1;
|
|
return "";
|
|
});
|
|
return { cleaned, requests };
|
|
}
|
|
|
|
export function buildCursorPositionResponse(row = 1, col = 1): string {
|
|
return `\x1b[${row};${col}R`;
|
|
}
|