fix(terminal): stop inline-image decoders exhausting the renderer's wasm memory budget (#23499)

V8 reserves an 8 GiB guard region per wasm memory inside its 1 TiB sandbox,
so an Electron renderer can hold only ~124 live wasm memories regardless of
free RAM. @xterm/addon-image instantiated a SIXEL decoder per terminal at
activation (and kept IIP decoders after the first image), so ~120+ terminals
exhausted the budget: new panes raised 'WebAssembly.instantiate(): Out of
memory' rejections, and the next Kitty/IIP image threw 'WebAssembly.Memory():
could not allocate memory' out of the parser, permanently wedging that
terminal's write queue.

The addon-image source patch now borrows SIXEL decoders from a shared pool
only while a sequence is open (color registers stay on the terminal), drops
IIP decoders after each image, and turns a failed decoder allocation into a
dropped image instead of a parser throw. Bundles regenerated with
regenerate-xterm-patches.mjs --write.

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
This commit is contained in:
OrcaWin
2026-09-29 01:20:37 -07:00
committed by GitHub
co-authored by m4air
parent 36c473ea1a
commit e1362ada4c
5 changed files with 676 additions and 37 deletions
File diff suppressed because one or more lines are too long
@@ -1,8 +1,21 @@
diff --git a/src/IIPHandler.ts b/src/IIPHandler.ts
index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df64c5bd088 100644
index 559b907416eb38318f439d060d7f89311ed34c7e..73cedbe99f831bffd202697cc8ba80a46a39cb99 100644
--- a/src/IIPHandler.ts
+++ b/src/IIPHandler.ts
@@ -34,6 +34,7 @@ const DEFAULT_HEADER: IHeaderFields = {
@@ -13,8 +13,10 @@ import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
// Local const enum mirror - esbuild can't inline const enums from external packages
const enum DecoderConst {
- // Limit held memory in base64 decoder (encoded bytes).
- KEEP_DATA = 4194304,
+ // Held memory in base64/QOI decoders between images. Zero because each kept
+ // decoder pins a wasm memory, and V8 caps those per process (~124 in a
+ // sandboxed renderer), so idle terminals must not hold one.
+ KEEP_DATA = 0,
// Initial buffer allocation for the decoder.
INITIAL_DATA = 1048576,
// Local mirror of const enum (esbuild can't inline const enums from external packages)
@@ -34,6 +36,7 @@ const DEFAULT_HEADER: IHeaderFields = {
export class IIPHandler implements IOscHandler, IResetHandler {
@@ -10,7 +23,7 @@ index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df6
private _aborted = false;
private _hp = new HeaderParser();
private _header: IHeaderFields = DEFAULT_HEADER;
@@ -55,6 +56,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
@@ -55,6 +58,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
}
public reset(): void {
@@ -18,7 +31,47 @@ index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df6
this._hp.reset();
this._dec.release();
this._qoiDec.release();
@@ -198,8 +200,13 @@ export class IIPHandler implements IOscHandler, IResetHandler {
@@ -92,7 +96,10 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._aborted = true;
return;
}
- this._dec.init();
+ if (!this._initDecoder()) {
+ this._aborted = true;
+ return;
+ }
} else if (this._abortMulti) {
this._aborted = true;
return;
@@ -135,7 +142,9 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._isMultipart = true;
this._abortMulti = false;
this._dec.release();
- this._dec.init();
+ if (!this._initDecoder()) {
+ this._abortMulti = true;
+ }
return true;
}
@@ -179,7 +188,15 @@ export class IIPHandler implements IOscHandler, IResetHandler {
let blob: Blob | ImageData;
if (metrics.mime === 'image/qoi') {
- const data = this._qoiDec.decode(this._dec.data8);
+ let data: Uint8Array<ArrayBuffer>;
+ try {
+ data = this._qoiDec.decode(this._dec.data8);
+ } catch (e) {
+ console.warn('IIP: could not decode QOI image', e);
+ this._dec.release();
+ this._qoiDec.release();
+ return true;
+ }
blob = new ImageData(
new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength),
this._qoiDec.width,
@@ -198,8 +215,13 @@ export class IIPHandler implements IOscHandler, IResetHandler {
blob = new Blob([this._dec.data8], { type: metrics.mime });
}
this._dec.release();
@@ -32,6 +85,25 @@ index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df6
this._storage.addImage(bm);
return true;
})
@@ -209,6 +231,18 @@ export class IIPHandler implements IOscHandler, IResetHandler {
});
}
+ // Why: wasm memory exhaustion must drop this image, not throw out of the parser and wedge the write queue.
+ private _initDecoder(): boolean {
+ try {
+ this._dec.init();
+ return true;
+ } catch (e) {
+ console.warn('IIP: could not allocate decoder', e);
+ this._dec.release();
+ return false;
+ }
+ }
+
private _resize(w: number, h: number): [number, number] {
const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;
const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;
diff --git a/src/ImageAddon.ts b/src/ImageAddon.ts
index 8fd39543118cd420e36c1614c1af370b6c7bbfbb..0c44d2a81642113417bf8dc10a4faa76d7cc5864 100644
--- a/src/ImageAddon.ts
@@ -144,8 +216,156 @@ index 5854efaec1fdf9dfcb886023542998a563b6d2f2..3afaf9bd63ffd7a4cdf32bf0ac24cf33
}
public get document(): Document | undefined {
diff --git a/src/SixelHandler.ts b/src/SixelHandler.ts
index 1af2d85bcdd541ed60b1e707f6186a91f1bbf1b2..0711a122ea43d5212a2851add9e744b65550ec85 100644
--- a/src/SixelHandler.ts
+++ b/src/SixelHandler.ts
@@ -10,6 +10,7 @@ import { RGBA8888 } from 'sixel/lib/Types';
import { ImageRenderer } from './ImageRenderer';
import { DecoderAsync, Decoder } from 'sixel/lib/Decoder';
+import { LIMITS } from 'sixel/lib/wasm';
// always free decoder ressources after decoding if it exceeds this limit
const MEM_PERMA_LIMIT = 4194304; // 1024 pixels * 1024 pixels * 4 channels = 4MB
@@ -18,48 +19,88 @@ const MEM_PERMA_LIMIT = 4194304; // 1024 pixels * 1024 pixels * 4 channels = 4MB
const DEFAULT_PALETTE = PALETTE_ANSI_256;
DEFAULT_PALETTE.set(PALETTE_VT340_COLOR);
+// Why pooled: every decoder owns a wasm memory, and V8 caps live wasm memories
+// per process (~124 in a sandboxed renderer). Terminals borrow a decoder only
+// while a SIXEL sequence is open, so idle terminals hold none.
+const MAX_IDLE_DECODERS = 2;
+const idleDecoders = new Map<number, Decoder[]>();
+let poolPrimed = false;
+
+function primeDecoderPool(memoryLimit: number): void {
+ if (poolPrimed) return;
+ poolPrimed = true;
+ // Async compile once, off the parser's hot path; later decoders reuse the cached module.
+ DecoderAsync({ memoryLimit, palette: DEFAULT_PALETTE }).then(
+ d => releaseDecoder(d, memoryLimit),
+ () => { poolPrimed = false; }
+ );
+}
+
+function acquireDecoder(memoryLimit: number): Decoder {
+ return idleDecoders.get(memoryLimit)?.pop() ?? new Decoder({ memoryLimit, palette: DEFAULT_PALETTE });
+}
+
+function releaseDecoder(dec: Decoder, memoryLimit: number): void {
+ if (dec.memoryUsage > MEM_PERMA_LIMIT) {
+ dec.release();
+ }
+ const idle = idleDecoders.get(memoryLimit) ?? [];
+ if (idle.length < MAX_IDLE_DECODERS) {
+ idle.push(dec);
+ idleDecoders.set(memoryLimit, idle);
+ }
+}
+
export class SixelHandler implements IDcsHandler, IResetHandler {
private _size = 0;
private _aborted = false;
private _dec: Decoder | undefined;
+ private _decMemoryLimit = 0;
+ // Color registers outlive a single image, so they live here rather than in a pooled decoder.
+ private readonly _palette = new Uint32Array(LIMITS.PALETTE_SIZE);
constructor(
private readonly _opts: IImageAddonOptions,
private readonly _storage: SixelImageStorage,
private readonly _coreTerminal: ITerminalExt
) {
- DecoderAsync({
- memoryLimit: this._opts.pixelLimit * 4,
- palette: DEFAULT_PALETTE,
- paletteLimit: this._opts.sixelPaletteLimit
- }).then(d => this._dec = d);
+ this._palette.set(DEFAULT_PALETTE);
+ primeDecoderPool(this._opts.pixelLimit * 4);
}
public reset(): void {
- /**
- * reset sixel decoder to defaults:
- * - release all memory
- * - nullify palette (4096)
- * - apply default palette (256)
- */
- if (this._dec) {
- this._dec.release();
- // FIXME: missing interface on decoder to nullify full palette
- (this._dec as any)._palette.fill(0);
- this._dec.init(0, DEFAULT_PALETTE, this._opts.sixelPaletteLimit);
- }
+ this._returnDecoder();
+ this._palette.fill(0);
+ this._palette.set(DEFAULT_PALETTE);
}
public hook(params: IParams): void {
this._size = 0;
this._aborted = false;
- if (this._dec) {
- const fillColor = params.params[1] === 1 ? 0 : extractActiveBg(
- this._coreTerminal._core._inputHandler._curAttrData,
- this._coreTerminal._core._themeService?.colors);
- this._dec.init(fillColor, null, this._opts.sixelPaletteLimit);
+ this._returnDecoder();
+ const memoryLimit = this._opts.pixelLimit * 4;
+ try {
+ this._dec = acquireDecoder(memoryLimit);
+ } catch (e) {
+ // Why: exhausting wasm memory must drop this image, not throw out of the parser and wedge the write queue.
+ console.warn(`SIXEL: could not allocate decoder - ${e}`);
+ this._aborted = true;
+ return;
}
+ this._decMemoryLimit = memoryLimit;
+ const fillColor = params.params[1] === 1 ? 0 : extractActiveBg(
+ this._coreTerminal._core._inputHandler._curAttrData,
+ this._coreTerminal._core._themeService?.colors);
+ this._dec.init(fillColor, this._palette, this._opts.sixelPaletteLimit);
+ }
+
+ private _returnDecoder(): void {
+ const dec = this._dec;
+ if (!dec) return;
+ this._dec = undefined;
+ this._palette.set(dec.palette);
+ releaseDecoder(dec, this._decMemoryLimit);
}
public put(data: Uint32Array, start: number, end: number): void {
@@ -83,6 +124,14 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
}
public unhook(success: boolean): boolean | Promise<boolean> {
+ try {
+ return this._unhook(success);
+ } finally {
+ this._returnDecoder();
+ }
+ }
+
+ private _unhook(success: boolean): boolean {
if (this._aborted || !success || !this._dec) {
return true;
}
@@ -100,9 +149,6 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
const canvas = ImageRenderer.createCanvas(undefined, width, height);
canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8 as Uint8ClampedArray<ArrayBuffer>, width, height), 0, 0);
- if (this._dec.memoryUsage > MEM_PERMA_LIMIT) {
- this._dec.release();
- }
this._storage.addImage(canvas);
return true;
}
diff --git a/src/kitty/KittyGraphicsHandler.ts b/src/kitty/KittyGraphicsHandler.ts
index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1fa320c4f8 100644
index de889dfff75d9ecc8ab47a025e6989ffe75bb202..cf66e5b75c78645b1641e53d1425d88201134f78 100644
--- a/src/kitty/KittyGraphicsHandler.ts
+++ b/src/kitty/KittyGraphicsHandler.ts
@@ -7,6 +7,7 @@ import { IDisposable } from '@xterm/xterm';
@@ -172,10 +392,12 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
this._cleanupAllPending();
if (this._activeDecoder) {
this._activeDecoder.release();
@@ -200,6 +203,25 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -200,8 +203,38 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
this._activeDecoder = pending.decoder;
}
if (!this._activeDecoder) {
- this._activeDecoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);
- this._activeDecoder.init();
+ // Budget WASM capacity, including one page of decoder state and rounding.
+ const decoderCapacity = this._maxEncodedBytes + 131072;
+ if (decoderCapacity > this._opts.storageLimit * 1000000) {
@@ -195,10 +417,23 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
+ this._sendResponse(oldest[1].cmd.id, 'ENOMEM:pending image budget exceeded', oldest[1].cmd.quiet ?? 0);
+ }
+ }
this._activeDecoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);
this._activeDecoder.init();
+ const decoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);
+ try {
+ decoder.init();
+ } catch (e) {
+ // Why: wasm memory exhaustion must drop this image, not throw out of the parser and wedge the write queue.
+ console.warn('KITTY: could not allocate decoder', e);
+ this._aborted = true;
+ if (this._parsedCommand?.id !== undefined) {
+ this._sendResponse(this._parsedCommand.id, 'ENOMEM:could not allocate decoder', this._parsedCommand.quiet ?? 0);
+ }
+ return;
+ }
+ this._activeDecoder = decoder;
}
@@ -550,9 +572,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
if (this._activeDecoder.put(data.subarray(start, end)) !== DECODER_OK) {
@@ -550,9 +583,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
}
private async _decodeAndDisplay(image: IKittyImageData, cmd: IKittyCommand): Promise<void> {
@@ -210,7 +445,7 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
const cropX = Math.max(0, cmd.x ?? 0);
const cropY = Math.max(0, cmd.y ?? 0);
const cropW = cmd.sourceWidth || (bitmap.width - cropX);
@@ -660,6 +684,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -660,6 +695,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
}
}
@@ -218,7 +453,7 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
const zIndex = cmd.zIndex ?? 0;
this._kittyStorage.addImage(image.id, bitmap, true, layer, zIndex);
bitmap = undefined; // ownership transferred to storage
@@ -693,6 +718,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -693,6 +729,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
}
if (image.format === KittyFormat.PNG) {
@@ -231,7 +466,7 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
const blob = new Blob([bytes as BlobPart], { type: 'image/png' });
if (!window.createImageBitmap) {
const url = URL.createObjectURL(blob);
@@ -775,27 +806,45 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -775,27 +817,45 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
private async _decompressZlib(compressed: Uint8Array): Promise<Uint8Array> {
try {
return await this._decompress(compressed, 'deflate');
@@ -0,0 +1,160 @@
import { createRequire } from 'node:module'
import { afterEach, describe, expect, it, vi } from 'vitest'
const require = createRequire(import.meta.url)
const { Terminal } = require('@xterm/xterm')
const { ImageAddon } = require('@xterm/addon-image')
// V8 caps live wasm memories per renderer (~124 under Electron's sandbox), so
// these contracts pin that idle terminals hold none and that exhaustion drops
// an image instead of wedging the terminal's write queue.
const SIXEL_RED_REGISTER_1 = '\x1bPq#1;2;100;0;0#1~\x1b\\'
const SIXEL_USE_REGISTER_1 = '\x1bPq#1~\x1b\\'
const PNG_1X1 =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
const IIP_PNG = `\x1b]1337;File=inline=1;size=${Buffer.from(PNG_1X1, 'base64').length}:${PNG_1X1}\x07`
class TrackedBitmap {
width = 1
height = 1
close = vi.fn()
}
function stubCanvasGlobals() {
const painted = []
vi.stubGlobal('ImageBitmap', TrackedBitmap)
vi.stubGlobal('window', { ImageBitmap: TrackedBitmap })
vi.stubGlobal('createImageBitmap', async () => new TrackedBitmap())
vi.stubGlobal(
'ImageData',
class {
constructor(data, width, height) {
Object.assign(this, { data: new Uint8ClampedArray(data), width, height })
}
}
)
vi.stubGlobal('document', {
createElement: () => ({
width: 0,
height: 0,
getContext: () => ({ putImageData: (image) => painted.push(image) })
})
})
return painted
}
function createTerminal(options = {}) {
const terminal = new Terminal({ allowProposedApi: true })
const addon = new ImageAddon({
enableSizeReports: false,
storageLimit: 32,
kittySizeLimit: 8 * 1024 * 1024,
...options
})
terminal.loadAddon(addon)
return {
terminal,
addon,
sixel: addon._handlers.get('sixel'),
iip: addon._handlers.get('iip')
}
}
function write(terminal, data) {
return new Promise((resolve) => terminal.write(data, resolve))
}
// A throw out of a handler leaves xterm's write queue waiting forever.
function writeOrWedge(terminal, data) {
return Promise.race([
write(terminal, data).then(() => 'parsed'),
new Promise((resolve) => setTimeout(() => resolve('wedged'), 2000))
])
}
function firstPixel(image) {
return Array.from(image.data.subarray(0, 4))
}
afterEach(() => vi.unstubAllGlobals())
describe('xterm image wasm budget', () => {
it('keeps idle terminals free of wasm decoders', async () => {
const panes = Array.from({ length: 300 }, () => createTerminal())
try {
// Let any asynchronous decoder instantiation settle.
await new Promise((resolve) => setTimeout(resolve, 100))
for (const { sixel, iip } of panes) {
expect(sixel._dec).toBeUndefined()
expect(iip._dec._inst).toBeNull()
expect(iip._qoiDec._inst).toBeFalsy()
}
} finally {
for (const { terminal } of panes) {
terminal.dispose()
}
}
})
it('returns decoders after each SIXEL and IIP image', async () => {
const painted = stubCanvasGlobals()
const { terminal, addon, sixel, iip } = createTerminal()
let added = 0
addon.onImageAdded(() => added++)
try {
await write(terminal, SIXEL_RED_REGISTER_1)
expect(painted).toHaveLength(1)
expect(sixel._dec).toBeUndefined()
await write(terminal, IIP_PNG)
await new Promise((resolve) => setTimeout(resolve, 0))
expect(iip._dec._inst).toBeNull()
expect(added).toBe(2)
} finally {
terminal.dispose()
}
})
it('keeps SIXEL color registers across images until a terminal reset', async () => {
const painted = stubCanvasGlobals()
const { terminal } = createTerminal()
try {
await write(terminal, SIXEL_RED_REGISTER_1)
await write(terminal, SIXEL_USE_REGISTER_1)
expect(firstPixel(painted[1])).toEqual([255, 0, 0, 255])
await write(terminal, '\x1bc')
await write(terminal, SIXEL_USE_REGISTER_1)
expect(firstPixel(painted[2])).not.toEqual([255, 0, 0, 255])
} finally {
terminal.dispose()
}
})
it('drops images without wedging the parser when wasm memory is exhausted', async () => {
stubCanvasGlobals()
// A pixelLimit no other test uses keeps the shared SIXEL pool empty for this key.
const { terminal, addon } = createTerminal({ pixelLimit: 1234567 })
const replies = []
terminal.onData((data) => replies.push(data))
function exhausted() {
throw new RangeError('WebAssembly.Memory(): could not allocate memory')
}
vi.stubGlobal('WebAssembly', { ...WebAssembly, Memory: exhausted, Instance: exhausted })
try {
expect(await writeOrWedge(terminal, '\x1b_Ga=T,f=32,s=1,v=1,i=7;AAAA/w==\x1b\\')).toBe(
'parsed'
)
expect(await writeOrWedge(terminal, IIP_PNG)).toBe('parsed')
expect(await writeOrWedge(terminal, SIXEL_USE_REGISTER_1)).toBe('parsed')
expect(await writeOrWedge(terminal, 'after')).toBe('parsed')
expect(addon._storage._images.size).toBe(0)
expect(replies.join('')).toContain('ENOMEM')
expect(terminal.buffer.active.getLine(0).translateToString(true)).toContain('after')
} finally {
terminal.dispose()
}
})
})
@@ -48,6 +48,15 @@ after reset, disable or disposal. `config/scripts/xterm-image-lifecycle-contract
exercises those boundaries against the installed addon. Font zoom scales visible
tiles without creating enlarged full-image canvases;
`config/scripts/xterm-image-resize-contract.test.mjs` checks allocation and tile mapping.
Every wasm memory reserves its guard region inside V8's sandbox, which caps a
renderer at about 124 live memories no matter how much RAM is free. Upstream
gave each terminal a SIXEL decoder at activation and kept IIP decoders after the
first image, so a window with ~120+ terminals ran out. The patch borrows SIXEL
decoders from a small shared pool only while a sequence is open, keeping color
registers on the terminal, and drops IIP decoders after each image. A decoder
that cannot be allocated drops that image; before the patch it threw out of the
parser and left the terminal's write queue stuck.
`config/scripts/xterm-image-wasm-budget-contract.test.mjs` covers both.
## Rules
+3 -3
View File
@@ -111,7 +111,7 @@ overrides:
patchedDependencies:
i18next-cli@1.74.2: 7955b89d3aa229f477408608d331f78c85148a85a85913729f89fac65ad8b207
'@vscode/windows-process-tree@0.8.0': b4be93859cefb159949d9cf05f97fa91a221dff0f793a6f03d02d78ae5b32035
'@xterm/addon-image@0.10.0-beta.300': 5fc280019957573eefe8daa2969534d6090ddb29c6b5842b8f22e99dc1ce3146
'@xterm/addon-image@0.10.0-beta.300': e5254a46d6f57bef4a8a19683bfa685afa0ca0127545aea53b54a48104ca3562
'@xterm/addon-ligatures@0.11.0-beta.300': 47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920
'@xterm/addon-search@0.17.0-beta.300': eee5338dd2621ece46e79c61ec06766cd7fadaf79ffdb24e2a8ab68e97ef31f0
'@xterm/addon-serialize@0.15.0-beta.300': b35533fe252e7e45433150170348889f4e08a6c17f7017ac34ea694d831fec7f
@@ -331,7 +331,7 @@ importers:
version: 0.12.0-beta.300(@xterm/xterm@6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393))
'@xterm/addon-image':
specifier: 0.10.0-beta.300
version: 0.10.0-beta.300(patch_hash=5fc280019957573eefe8daa2969534d6090ddb29c6b5842b8f22e99dc1ce3146)(@xterm/xterm@6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393))
version: 0.10.0-beta.300(patch_hash=e5254a46d6f57bef4a8a19683bfa685afa0ca0127545aea53b54a48104ca3562)(@xterm/xterm@6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393))
'@xterm/addon-ligatures':
specifier: 0.11.0-beta.300
version: 0.11.0-beta.300(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393))
@@ -10744,7 +10744,7 @@ snapshots:
dependencies:
'@xterm/xterm': 6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393)
'@xterm/addon-image@0.10.0-beta.300(patch_hash=5fc280019957573eefe8daa2969534d6090ddb29c6b5842b8f22e99dc1ce3146)(@xterm/xterm@6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393))':
'@xterm/addon-image@0.10.0-beta.300(patch_hash=e5254a46d6f57bef4a8a19683bfa685afa0ca0127545aea53b54a48104ca3562)(@xterm/xterm@6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393))':
dependencies:
'@xterm/xterm': 6.1.0-beta.303(patch_hash=dd0ccc59cd1ccf99f4d76e5aa2456da165fa0804dce19a833d7638bd07ffa393)