withruntime 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/COMPATIBILITY.md +145 -0
  2. package/E2B.md +23 -13
  3. package/README.md +8 -5
  4. package/README.src.md +8 -5
  5. package/compatibility-lock.json +281 -0
  6. package/dist/ai-harness/index.js +2 -2
  7. package/dist/api-defaults.d.ts +17 -0
  8. package/dist/api-defaults.js +22 -0
  9. package/dist/blaxel/preview.d.ts +1 -1
  10. package/dist/blaxel/preview.js +1 -1
  11. package/dist/blaxel/process.d.ts +1 -1
  12. package/dist/blaxel/process.js +11 -2
  13. package/dist/cli-words.d.ts +27 -0
  14. package/dist/cli-words.js +135 -0
  15. package/dist/cli.js +162 -31
  16. package/dist/client.d.ts +4 -1
  17. package/dist/client.js +8 -2
  18. package/dist/cloudflare/index.d.ts +294 -0
  19. package/dist/cloudflare/index.js +554 -0
  20. package/dist/cloudflare/worker.d.ts +415 -0
  21. package/dist/cloudflare/worker.js +297 -0
  22. package/dist/codesandbox/guest-service.d.ts +31 -0
  23. package/dist/codesandbox/guest-service.js +453 -0
  24. package/dist/codesandbox/index.d.ts +386 -0
  25. package/dist/codesandbox/index.js +1313 -0
  26. package/dist/compat/core.d.ts +39 -0
  27. package/dist/compat/core.js +199 -0
  28. package/dist/compat/files.d.ts +26 -0
  29. package/dist/compat/files.js +115 -0
  30. package/dist/compat/shell.d.ts +4 -0
  31. package/dist/compat/shell.js +165 -0
  32. package/dist/daytona/context.js +2 -1
  33. package/dist/daytona/process.d.ts +1 -1
  34. package/dist/daytona/process.js +27 -4
  35. package/dist/e2b/code-interpreter.d.ts +4 -5
  36. package/dist/e2b/code-interpreter.js +54 -56
  37. package/dist/e2b/commands.d.ts +5 -4
  38. package/dist/e2b/commands.js +37 -29
  39. package/dist/e2b/errors.js +1 -1
  40. package/dist/e2b/filesystem.d.ts +8 -6
  41. package/dist/e2b/filesystem.js +127 -28
  42. package/dist/e2b/sandbox.d.ts +4 -3
  43. package/dist/e2b/sandbox.js +10 -10
  44. package/dist/errors.d.ts +8 -0
  45. package/dist/errors.js +15 -2
  46. package/dist/freestyle/index.d.ts +301 -0
  47. package/dist/freestyle/index.js +310 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/mcp.js +2 -2
  50. package/dist/modal/index.d.ts +205 -0
  51. package/dist/modal/index.js +485 -0
  52. package/dist/network-cli.js +1 -1
  53. package/dist/openai-agents/index.js +9 -26
  54. package/dist/products/audit.d.ts +3 -0
  55. package/dist/products/index.d.ts +2 -0
  56. package/dist/products/index.js +2 -0
  57. package/dist/products/interpreter.d.ts +11 -4
  58. package/dist/products/interpreter.js +33 -9
  59. package/dist/products/jobs.d.ts +1 -1
  60. package/dist/products/jobs.js +5 -4
  61. package/dist/products/limits.d.ts +10 -0
  62. package/dist/products/previews.d.ts +10 -2
  63. package/dist/products/previews.js +3 -2
  64. package/dist/products/tailscale.d.ts +57 -0
  65. package/dist/products/tailscale.js +17 -0
  66. package/dist/products/watch.d.ts +25 -4
  67. package/dist/products/watch.js +75 -14
  68. package/dist/runloop/index.d.ts +340 -0
  69. package/dist/runloop/index.js +438 -0
  70. package/dist/runloop/pagination.d.ts +46 -0
  71. package/dist/runloop/pagination.js +82 -0
  72. package/dist/runloop/snapshots.d.ts +52 -0
  73. package/dist/runloop/snapshots.js +244 -0
  74. package/dist/sandbox.d.ts +40 -9
  75. package/dist/sandbox.js +273 -76
  76. package/dist/snapshots.d.ts +11 -1
  77. package/dist/snapshots.js +11 -1
  78. package/dist/sprites/index.d.ts +251 -0
  79. package/dist/sprites/index.js +552 -0
  80. package/dist/tar.d.ts +8 -1
  81. package/dist/tar.js +185 -35
  82. package/dist/tools/index.js +2 -1
  83. package/dist/transport.d.ts +20 -2
  84. package/dist/transport.js +48 -11
  85. package/dist/types.d.ts +52 -3
  86. package/dist/vercel/command.js +12 -1
  87. package/dist/vercel/errors.d.ts +2 -2
  88. package/dist/vercel/errors.js +12 -1
  89. package/dist/vercel/filesystem.d.ts +21 -18
  90. package/dist/vercel/filesystem.js +95 -48
  91. package/dist/vercel/sandbox.js +5 -5
  92. package/package.json +45 -3
@@ -0,0 +1,453 @@
1
+ const root = "/workspace/.runtime-compat/codesandbox/service";
2
+ export const taskServiceSource = String.raw `import base64,errno,fcntl,json,os,pty,select,signal,socket,socketserver,struct,subprocess,sys,termios,threading,time,uuid
3
+ root=sys.argv[1]
4
+ os.makedirs(root,mode=0o700,exist_ok=True)
5
+ os.umask(0o077)
6
+ lock=open(os.path.join(root,'lock'),'a')
7
+ try: fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
8
+ except BlockingIOError: sys.exit(0)
9
+ mutex=threading.RLock()
10
+ runs={}
11
+ current={}
12
+ boot_state={'state':'starting'}
13
+ def atomic(path,data):
14
+ temp=path+'.tmp'
15
+ with open(temp,'w') as f: json.dump(data,f)
16
+ os.replace(temp,path)
17
+ def identity(pid):
18
+ try:
19
+ with open('/proc/'+str(pid)+'/stat') as f: return f.read().rsplit(')',1)[1].split()[19]
20
+ except FileNotFoundError: return None
21
+ def record(run):
22
+ with run['_lock']: return {k:v for k,v in run.items() if k not in ('process','fd','writer','_lock')}
23
+ def save(run):
24
+ value=record(run)
25
+ value.pop('inputWriters',None)
26
+ atomic(os.path.join(root,run['id']+'.json'),value)
27
+ def settled(run):
28
+ try: save(run)
29
+ except OSError as e:
30
+ # Keep terminal failure readable over the socket even when storage is full.
31
+ run['exitCode']=125
32
+ run['error']=(run.get('error','')+'; ' if run.get('error') else '')+'Cannot persist task result: '+str(e)
33
+ def complete(run):
34
+ fd=run['fd']
35
+ failed=False
36
+ try:
37
+ with open(os.path.join(root,run['id']+'.log'),'ab',buffering=0) as out:
38
+ while True:
39
+ ready,_,_=select.select([fd],[],[],0.05)
40
+ if not ready:
41
+ if run['process'].poll() is not None: break
42
+ continue
43
+ try: data=os.read(fd,65536)
44
+ except OSError as e:
45
+ if e.errno==errno.EIO: break
46
+ raise
47
+ if not data: break
48
+ out.write(data)
49
+ except BaseException as e:
50
+ failed=True
51
+ with run['_lock']: run['error']=str(e)
52
+ try: os.killpg(run['process'].pid,signal.SIGKILL)
53
+ except ProcessLookupError: pass
54
+ finally:
55
+ code=run['process'].wait()
56
+ with run['_lock']:
57
+ try: os.close(fd)
58
+ except OSError: pass
59
+ run['fd']=None
60
+ run['exitCode']=125 if failed else code
61
+ run['state']='exited'
62
+ settled(run)
63
+ def stop(run):
64
+ if run.get('state')!='running': return
65
+ proc=run.get('process')
66
+ if proc is not None and proc.poll() is None:
67
+ try: os.killpg(proc.pid,signal.SIGTERM)
68
+ except ProcessLookupError: pass
69
+ try: proc.wait(timeout=2)
70
+ except subprocess.TimeoutExpired:
71
+ try: os.killpg(proc.pid,signal.SIGKILL)
72
+ except ProcessLookupError: pass
73
+ proc.wait()
74
+ elif proc is None and run.get('pid') and run.get('identity') and identity(run['pid'])==run['identity']:
75
+ # On supervisor recovery, fence against PID reuse before touching a process.
76
+ try: os.killpg(run['pid'],signal.SIGKILL)
77
+ except ProcessLookupError: pass
78
+ if proc is not None and run.get('writer'):
79
+ run['writer'].join(timeout=3)
80
+ if run['writer'].is_alive(): raise TimeoutError('Task output did not finish after stop')
81
+ if proc is None:
82
+ run['state']='exited'
83
+ run['exitCode']=-signal.SIGKILL
84
+ settled(run)
85
+ def config():
86
+ with open(os.path.join(root,'config.json')) as f: return json.load(f)
87
+ def start(task):
88
+ cfg=config()
89
+ definition=cfg['tasks'].get(task)
90
+ if definition is None: raise ValueError('No such task')
91
+ old=current.get(task)
92
+ if old is not None: stop(runs[old])
93
+ master,slave=pty.openpty()
94
+ fcntl.ioctl(slave,termios.TIOCSWINSZ,struct.pack('HHHH',24,128,0,0))
95
+ try:
96
+ proc=subprocess.Popen(['bash','-c',definition['command']],cwd=cfg['cwd'],env=dict(os.environ,**cfg.get('env',{})),stdin=slave,stdout=slave,stderr=slave,start_new_session=True)
97
+ except BaseException:
98
+ os.close(master)
99
+ raise
100
+ finally: os.close(slave)
101
+ run={'id':str(uuid.uuid4()),'taskId':task,'pid':proc.pid,'identity':identity(proc.pid),'state':'running','exitCode':None,'command':definition['command'],'startedAt':time.time()*1000,'process':proc,'fd':master,'_lock':threading.RLock()}
102
+ runs[run['id']]=run
103
+ current[task]=run['id']
104
+ try:
105
+ save(run)
106
+ atomic(os.path.join(root,'current.json'),current)
107
+ except BaseException:
108
+ try: os.killpg(proc.pid,signal.SIGKILL)
109
+ except ProcessLookupError: pass
110
+ proc.wait()
111
+ os.close(master)
112
+ runs.pop(run['id'],None)
113
+ if old is None: current.pop(task,None)
114
+ else: current[task]=old
115
+ raise
116
+ run['writer']=threading.Thread(target=complete,args=(run,),daemon=True)
117
+ run['writer'].start()
118
+ return record(run)
119
+ def write(req):
120
+ data=base64.b64decode(req['data'],validate=True)
121
+ with mutex:
122
+ run=runs.get(req['id'])
123
+ if run is None: raise ValueError('Task is not running')
124
+ # Own this open file description across close/restart; never reuse a raw FD.
125
+ with run['_lock']:
126
+ if run['state']!='running': raise ValueError('Task is not running')
127
+ fd=os.dup(run['fd'])
128
+ run['inputWriters']=run.get('inputWriters',0)+1
129
+ try:
130
+ os.set_blocking(fd,False)
131
+ deadline=time.monotonic()+5
132
+ while data:
133
+ with mutex:
134
+ if run['state']!='running': raise ValueError('Task is not running')
135
+ remaining=deadline-time.monotonic()
136
+ if remaining<=0: raise TimeoutError('Task input write timed out')
137
+ _,ready,_=select.select([],[fd],[],min(remaining,0.1))
138
+ if not ready: continue
139
+ try: sent=os.write(fd,data)
140
+ except BlockingIOError: continue
141
+ data=data[sent:]
142
+ finally:
143
+ os.close(fd)
144
+ with run['_lock']: run['inputWriters']-=1
145
+ def read(req):
146
+ with mutex:
147
+ run=runs.get(req['id'])
148
+ if run is None: raise ValueError('No such task run')
149
+ offset=req.get('offset',0)
150
+ deadline=time.monotonic()+min(max(req.get('waitMs',0),0),2000)/1000
151
+ while True:
152
+ # Observe terminal status before reading: exited guarantees logs are drained.
153
+ snapshot=record(run)
154
+ try:
155
+ with open(os.path.join(root,run['id']+'.log'),'rb') as f:
156
+ size=os.fstat(f.fileno()).st_size
157
+ f.seek(offset); data=f.read(min(65536,max(0,req.get('until',size)-offset)))
158
+ except FileNotFoundError: data=b''; size=0
159
+ if data or snapshot['state']!='running' or time.monotonic()>=deadline:
160
+ return dict(snapshot,data=base64.b64encode(data).decode(),offset=offset+len(data),size=size)
161
+ time.sleep(0.02)
162
+ def handle(req):
163
+ if req['op']=='write': return write(req)
164
+ if req['op']=='read': return read(req)
165
+ with mutex:
166
+ op=req['op']
167
+ if op=='ping': return {'ok':True}
168
+ if op=='boot': return dict(boot_state)
169
+ if op=='list': return [record(runs[id]) for id in current.values() if id in runs]
170
+ if op=='get':
171
+ id=current.get(req['taskId'])
172
+ return record(runs[id]) if id is not None else None
173
+ if op=='start': return start(req['taskId'])
174
+ if op=='stopTask':
175
+ id=current.get(req['taskId'])
176
+ if id is None: return None
177
+ stop(runs[id]); return record(runs[id])
178
+ run=runs.get(req['id'])
179
+ if run is None: raise ValueError('No such task run')
180
+ if op=='stop': stop(run); return record(run)
181
+ if op=='resize':
182
+ with run['_lock']:
183
+ if run['state']=='running': fcntl.ioctl(run['fd'],termios.TIOCSWINSZ,struct.pack('HHHH',req['rows'],req['cols'],0,0))
184
+ return None
185
+ raise ValueError('Unknown operation')
186
+ # A restarted service never assumes a recorded PID still belongs to it.
187
+ try:
188
+ with open(os.path.join(root,'current.json')) as f: current=json.load(f)
189
+ except FileNotFoundError: pass
190
+ for id in list(current.values()):
191
+ try:
192
+ with open(os.path.join(root,id+'.json')) as f: run=json.load(f)
193
+ run['_lock']=threading.RLock()
194
+ stop(run)
195
+ runs[id]=run
196
+ except FileNotFoundError: pass
197
+ class Handler(socketserver.StreamRequestHandler):
198
+ def handle(self):
199
+ try:
200
+ line=self.rfile.readline(1048577)
201
+ if len(line)>1048576: raise ValueError('Request too large')
202
+ result={'ok':True,'value':handle(json.loads(line))}
203
+ except Exception as e: result={'ok':False,'error':str(e)}
204
+ try: self.wfile.write(json.dumps(result).encode()+b'\n')
205
+ except (BrokenPipeError,ConnectionResetError): pass # The SDK detached its request.
206
+ class Server(socketserver.ThreadingUnixStreamServer): daemon_threads=True
207
+ path=os.path.join(root,'socket')
208
+ boot_state['config']=config().get('setup',{}).get('config')
209
+ try: os.unlink(path)
210
+ except FileNotFoundError: pass
211
+ server=Server(path,Handler)
212
+ def shutdown(signum,frame):
213
+ with mutex:
214
+ for run in runs.values(): stop(run)
215
+ os._exit(128+signum)
216
+ signal.signal(signal.SIGTERM,shutdown)
217
+ signal.signal(signal.SIGINT,shutdown)
218
+ def boot():
219
+ cfg=config()
220
+ setup=cfg.get('setup')
221
+ with mutex: boot_state['config']=setup.get('config') if setup else None
222
+ if setup and setup['steps']:
223
+ setup_root=os.path.join(os.path.dirname(root),'setup')
224
+ os.makedirs(setup_root,mode=0o700,exist_ok=True)
225
+ progress=os.path.join(setup_root,'progress.json')
226
+ try:
227
+ with open(progress) as f: previous=json.load(f)
228
+ except FileNotFoundError: previous={}
229
+ if previous.get('config')!=setup['config'] or previous.get('state')!='FINISHED':
230
+ run=dict(setup,runId=str(uuid.uuid4()),cwd=cfg['cwd'])
231
+ path=os.path.join(setup_root,run['runId']+'.json')
232
+ atomic(path,run)
233
+ result=subprocess.run([sys.executable,os.path.join(root,'setup.py'),path],env=dict(os.environ,**cfg.get('env',{})),capture_output=True,text=True)
234
+ if result.returncode!=0: raise RuntimeError(result.stderr.strip() or 'Setup exited with code '+str(result.returncode))
235
+ while True:
236
+ try:
237
+ with open(progress) as f: result=json.load(f)
238
+ except FileNotFoundError: return
239
+ if result.get('state')!='IN_PROGRESS': break
240
+ time.sleep(0.1)
241
+ if result.get('state')!='FINISHED' or result.get('config')!=setup['config']: return
242
+ # The service owns automatic tasks independently of SDK connections.
243
+ with mutex:
244
+ for task,definition in cfg['tasks'].items():
245
+ if definition.get('runAtStart'):
246
+ try: start(task)
247
+ except Exception as e:
248
+ # A failed automatic launch must remain observable even if storage is full.
249
+ run={'id':str(uuid.uuid4()),'taskId':task,'state':'exited','exitCode':125,'command':definition['command'],'startedAt':time.time()*1000,'error':'Automatic task startup failed: '+str(e),'_lock':threading.RLock()}
250
+ runs[run['id']]=run
251
+ current[task]=run['id']
252
+ settled(run)
253
+ try: atomic(os.path.join(root,'current.json'),current)
254
+ except OSError: pass
255
+ def boot_guard():
256
+ try:
257
+ boot()
258
+ with mutex: boot_state['state']='ready'
259
+ except Exception as e:
260
+ with mutex: boot_state.update(state='failed',error='Task setup startup failed: '+str(e))
261
+ threading.Thread(target=boot_guard,daemon=True).start()
262
+ server.serve_forever()
263
+ `;
264
+ const requestSource = String.raw `import json,socket,sys
265
+ s=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
266
+ s.settimeout(10)
267
+ s.connect(sys.argv[1]+'/socket')
268
+ s.sendall(sys.stdin.buffer.read()+b'\n')
269
+ f=s.makefile('rb')
270
+ try: print(f.readline(1048576).decode(),end='')
271
+ finally: f.close(); s.close()
272
+ `;
273
+ const installSource = String.raw `import os,subprocess,sys
274
+ root=sys.argv[1]
275
+ unit='''[Unit]
276
+ Description=Runtime CodeSandbox task supervisor
277
+ After=runtime-guest-agent.service
278
+ [Service]
279
+ Type=simple
280
+ User=1000
281
+ Group=1000
282
+ WorkingDirectory=/project/sandbox
283
+ ExecStart=/usr/bin/python3 ROOT/service.py ROOT
284
+ Restart=on-failure
285
+ RestartSec=1
286
+ KillMode=control-group
287
+ TimeoutStopSec=5
288
+ UMask=0077
289
+ [Install]
290
+ WantedBy=multi-user.target
291
+ '''.replace('ROOT',root)
292
+ path='/etc/systemd/system/runtime-codesandbox-tasks.service'
293
+ if not os.path.exists(path) or open(path).read()!=unit:
294
+ temp=path+'.'+str(os.getpid())+'.tmp'
295
+ with open(temp,'w') as f: f.write(unit)
296
+ os.chmod(temp,0o644)
297
+ os.replace(temp,path)
298
+ subprocess.run(['systemctl','daemon-reload'],check=True)
299
+ subprocess.run(['systemctl','enable','--now','runtime-codesandbox-tasks.service'],check=True)
300
+ `;
301
+ export class ManagedTasks {
302
+ native;
303
+ constructor(native) {
304
+ this.native = native;
305
+ }
306
+ async install(tasks, env, setup) {
307
+ const revision = crypto.randomUUID();
308
+ const script = `${root}/service-${revision}.py`, setupScript = `${root}/setup-${revision}.py`;
309
+ await this.native.files.write(script, taskServiceSource, { mode: 0o600 });
310
+ await this.native.files.write(setupScript, setup.source, { mode: 0o600 });
311
+ const pending = `${root}/config-${revision}.json`;
312
+ await this.native.files.write(pending, JSON.stringify({
313
+ tasks,
314
+ env,
315
+ cwd: "/project/sandbox",
316
+ setup: { steps: setup.steps, config: JSON.stringify(setup.steps) },
317
+ }), { mode: 0o600 });
318
+ await this.native.exec([
319
+ "python3",
320
+ "-c",
321
+ "import os,sys\nfor i in range(1,len(sys.argv),2): os.replace(sys.argv[i],sys.argv[i+1])",
322
+ script,
323
+ `${root}/service.py`,
324
+ setupScript,
325
+ `${root}/setup.py`,
326
+ pending,
327
+ `${root}/config.json`,
328
+ ], { check: true });
329
+ await this.native.exec(["sudo", "-n", "python3", "-c", installSource, root], { check: true });
330
+ const until = Date.now() + 5000;
331
+ for (;;) {
332
+ try {
333
+ await this.call({ op: "ping" });
334
+ return;
335
+ }
336
+ catch (error) {
337
+ if (Date.now() >= until)
338
+ throw error;
339
+ await new Promise((resolve) => setTimeout(resolve, 20));
340
+ }
341
+ }
342
+ }
343
+ async call(request, signal) {
344
+ const result = await this.native.exec(["python3", "-c", requestSource, root], {
345
+ check: true,
346
+ signal,
347
+ stdin: JSON.stringify(request),
348
+ onStdout: () => { },
349
+ });
350
+ const reply = JSON.parse(result.stdout);
351
+ if (!reply.ok)
352
+ throw new Error(reply.error ?? "Task supervisor failed");
353
+ return reply.value;
354
+ }
355
+ async get(taskId) {
356
+ const run = await this.call({ op: "get", taskId });
357
+ return run ? this.process(run) : undefined;
358
+ }
359
+ async boot(signal) {
360
+ if (!(await this.native.files.exists(`${root}/config.json`, { signal })))
361
+ return undefined;
362
+ return this.call({ op: "boot" }, signal);
363
+ }
364
+ async start(taskId) {
365
+ return this.process(await this.call({ op: "start", taskId }));
366
+ }
367
+ async stop(taskId) {
368
+ const run = await this.call({ op: "stopTask", taskId });
369
+ return run ? this.process(run) : undefined;
370
+ }
371
+ async list(signal) {
372
+ return (await this.call({ op: "list" }, signal)).map((run) => ({
373
+ taskId: run.taskId,
374
+ process: this.process(run),
375
+ }));
376
+ }
377
+ async output(id) {
378
+ const chunks = [];
379
+ let offset = 0, until;
380
+ do {
381
+ const result = await this.call({
382
+ op: "read",
383
+ id,
384
+ offset,
385
+ until,
386
+ });
387
+ until ??= result.size;
388
+ chunks.push(Buffer.from(result.data, "base64"));
389
+ offset = result.offset;
390
+ if (!result.data)
391
+ break;
392
+ } while (offset < until);
393
+ return Buffer.concat(chunks).toString("utf8");
394
+ }
395
+ process(run) {
396
+ const call = (request, signal) => this.call(request, signal);
397
+ const handle = {
398
+ id: run.id,
399
+ info: run,
400
+ async *output({ signal } = {}) {
401
+ let offset = 0;
402
+ const decoder = new TextDecoder();
403
+ for (;;) {
404
+ signal?.throwIfAborted();
405
+ const result = await call({ op: "read", id: run.id, offset, waitMs: 2000 }, signal);
406
+ Object.assign(run, result);
407
+ if (result.data)
408
+ yield {
409
+ type: "stdout",
410
+ data: decoder.decode(Buffer.from(result.data, "base64"), { stream: true }),
411
+ offset,
412
+ };
413
+ offset = result.offset;
414
+ if (result.state !== "running" && !result.data) {
415
+ const tail = decoder.decode();
416
+ if (tail)
417
+ yield { type: "stdout", data: tail, offset };
418
+ if (result.error)
419
+ throw new Error(result.error);
420
+ yield { type: "exit", exitCode: result.exitCode, state: "exited", timedOut: false };
421
+ return;
422
+ }
423
+ if (!result.data)
424
+ await new Promise((resolve, reject) => {
425
+ const abort = () => {
426
+ clearTimeout(timer);
427
+ reject(signal?.reason instanceof Error
428
+ ? signal.reason
429
+ : new Error("Task subscription cancelled"));
430
+ };
431
+ const timer = setTimeout(() => {
432
+ signal?.removeEventListener("abort", abort);
433
+ resolve();
434
+ }, 100);
435
+ signal?.addEventListener("abort", abort, { once: true });
436
+ if (signal?.aborted)
437
+ abort();
438
+ });
439
+ }
440
+ },
441
+ async kill() {
442
+ Object.assign(run, await call({ op: "stop", id: run.id }));
443
+ },
444
+ async resize(cols, rows) {
445
+ await call({ op: "resize", id: run.id, cols, rows });
446
+ },
447
+ async write(data) {
448
+ await call({ op: "write", id: run.id, data: Buffer.from(data).toString("base64") });
449
+ },
450
+ };
451
+ return handle;
452
+ }
453
+ }