@adhdev/daemon-core 0.7.6 → 0.7.7

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 (41) hide show
  1. package/dist/index.d.mts +158 -38
  2. package/dist/index.d.ts +158 -38
  3. package/dist/index.js +3965 -2521
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +3455 -2011
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{normalize-tKg8IiDk.d.mts → normalize-auJAPmKy.d.mts} +669 -629
  8. package/dist/{normalize-tKg8IiDk.d.ts → normalize-auJAPmKy.d.ts} +669 -629
  9. package/dist/status/normalize.d.mts +1 -1
  10. package/dist/status/normalize.d.ts +1 -1
  11. package/package.json +5 -1
  12. package/src/boot/daemon-lifecycle.ts +7 -4
  13. package/src/cli-adapter-types.ts +2 -0
  14. package/src/cli-adapters/provider-cli-adapter.ts +148 -11
  15. package/src/cli-adapters/pty-transport.ts +100 -0
  16. package/src/cli-adapters/session-host-transport.ts +392 -0
  17. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +126 -0
  18. package/src/cli-adapters/terminal-backends/types.ts +17 -0
  19. package/src/cli-adapters/terminal-backends/xterm-backend.ts +87 -0
  20. package/src/cli-adapters/terminal-screen.ts +40 -53
  21. package/src/commands/cli-manager.ts +184 -55
  22. package/src/config/config.d.ts +116 -0
  23. package/src/config/workspace-activity.d.ts +22 -0
  24. package/src/config/workspaces.d.ts +84 -0
  25. package/src/daemon/dev-auto-implement.ts +1087 -0
  26. package/src/daemon/dev-cdp-handlers.ts +1003 -0
  27. package/src/daemon/dev-cli-debug.ts +288 -0
  28. package/src/daemon/dev-server-types.ts +45 -0
  29. package/src/daemon/dev-server.ts +121 -1698
  30. package/src/index.ts +5 -1
  31. package/src/providers/cli-provider-instance.ts +13 -1
  32. package/src/providers/contracts.d.ts +408 -0
  33. package/src/providers/contracts.ts +9 -0
  34. package/src/providers/provider-instance-manager.ts +21 -0
  35. package/src/providers/provider-instance.d.ts +142 -0
  36. package/src/providers/provider-instance.ts +23 -1
  37. package/src/shared-types.d.ts +157 -0
  38. package/src/shared-types.ts +14 -0
  39. package/src/status/builders.ts +6 -0
  40. package/src/status/normalize.d.ts +14 -0
  41. package/src/types.d.ts +127 -0
@@ -1 +1 @@
1
- export { w as ManagedStatus, O as isManagedStatusWaiting, Q as isManagedStatusWorking, X as normalizeActiveChatData, Y as normalizeManagedStatus } from '../normalize-tKg8IiDk.mjs';
1
+ export { x as ManagedStatus, Q as isManagedStatusWaiting, T as isManagedStatusWorking, Y as normalizeActiveChatData, Z as normalizeManagedStatus } from '../normalize-auJAPmKy.mjs';
@@ -1 +1 @@
1
- export { w as ManagedStatus, O as isManagedStatusWaiting, Q as isManagedStatusWorking, X as normalizeActiveChatData, Y as normalizeManagedStatus } from '../normalize-tKg8IiDk.js';
1
+ export { x as ManagedStatus, Q as isManagedStatusWaiting, T as isManagedStatusWorking, Y as normalizeActiveChatData, Z as normalizeManagedStatus } from '../normalize-auJAPmKy.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,6 +38,7 @@
38
38
  "license": "AGPL-3.0-or-later",
39
39
  "dependencies": {
40
40
  "@agentclientprotocol/sdk": "^0.16.1",
41
+ "@adhdev/session-host-core": "*",
41
42
  "@xterm/xterm": "^6.0.0",
42
43
  "chalk": "^5.3.0",
43
44
  "chokidar": "^5.0.0",
@@ -45,6 +46,9 @@
45
46
  "node-pty": "^1.1.0",
46
47
  "ws": "^8.19.0"
47
48
  },
49
+ "optionalDependencies": {
50
+ "@adhdev/ghostty-vt-node": "*"
51
+ },
48
52
  "devDependencies": {
49
53
  "@types/node": "^22.0.0",
50
54
  "@types/ws": "^8.18.1",
@@ -312,13 +312,16 @@ export async function shutdownDaemonComponents(components: DaemonComponents): Pr
312
312
  }
313
313
  } catch (e: any) { LOG.warn('Shutdown', `AgentStream dispose: ${e?.message}`); }
314
314
 
315
- // 3. Shutdown CLIs
316
- try { await cliManager.shutdownAll(); } catch { /* noop */ }
315
+ // 3. Detach CLIs (persistent runtimes survive daemon restarts)
316
+ try { cliManager.detachAll(); } catch { /* noop */ }
317
317
 
318
- // 4. Dispose instances
318
+ // 4. Remove CLI instances without disposing their runtimes again
319
+ try { instanceManager.removeByCategory('cli', { dispose: false }); } catch { /* noop */ }
320
+
321
+ // 5. Dispose remaining instances
319
322
  try { instanceManager.disposeAll(); } catch { /* noop */ }
320
323
 
321
- // 5. Disconnect CDPs
324
+ // 6. Disconnect CDPs
322
325
  for (const m of cdpManagers.values()) {
323
326
  try { m.disconnect(); } catch { /* noop */ }
324
327
  }
@@ -12,7 +12,9 @@ export interface CliAdapter {
12
12
  sendMessage(text: string): Promise<void>;
13
13
  getStatus(): any;
14
14
  getPartialResponse(): string;
15
+ saveAndStop?(): Promise<void>;
15
16
  shutdown(): void;
17
+ detach?(): void;
16
18
  cancel(): void;
17
19
  isProcessing(): boolean;
18
20
  isReady(): boolean;
@@ -20,6 +20,13 @@ import { execSync } from 'child_process';
20
20
  import type { CliAdapter } from '../cli-adapter-types.js';
21
21
  import { LOG } from '../logging/logger.js';
22
22
  import { TerminalScreen } from './terminal-screen.js';
23
+ import type { ProviderResumeCapability } from '../providers/contracts.js';
24
+ import {
25
+ NodePtyTransportFactory,
26
+ type PtyRuntimeMetadata,
27
+ type PtyRuntimeTransport,
28
+ type PtyTransportFactory,
29
+ } from './pty-transport.js';
23
30
 
24
31
  let pty: any;
25
32
  try {
@@ -71,7 +78,7 @@ export interface CliScripts {
71
78
  /** Lightweight status detection (high-frequency polling) → AgentStatus string */
72
79
  detectStatus?: (input: { tail: string; screenText?: string; rawBuffer?: string }) => string | null;
73
80
  /** Parse approval modal from PTY output → ModalInfo | null */
74
- parseApproval?: (input: { buffer: string; rawBuffer?: string; tail: string }) => { message: string; buttons: string[] } | null;
81
+ parseApproval?: (input: { buffer: string; screenText?: string; rawBuffer?: string; tail: string }) => { message: string; buttons: string[] } | null;
75
82
  /** Produce a cli-specific prompt from a dashboard action payload */
76
83
  resolveAction?: (data: any) => string;
77
84
  /** Custom scripts */
@@ -128,6 +135,7 @@ export interface CliProviderModule {
128
135
  /** Output settle debounce before evaluating status (default 300ms) */
129
136
  outputSettle?: number;
130
137
  };
138
+ resume?: ProviderResumeCapability;
131
139
  }
132
140
 
133
141
  // ─── Utility Functions ──────────────────────────────
@@ -354,7 +362,8 @@ export class ProviderCliAdapter implements CliAdapter {
354
362
  public workingDir: string;
355
363
 
356
364
  private provider: CliProviderModule;
357
- private ptyProcess: any = null;
365
+ private ptyProcess: PtyRuntimeTransport | null = null;
366
+ private transportFactory: PtyTransportFactory;
358
367
  private messages: CliChatMessage[] = [];
359
368
  private committedMessages: CliChatMessage[] = [];
360
369
  private structuredMessages: CliChatMessage[] = [];
@@ -478,8 +487,14 @@ export class ProviderCliAdapter implements CliAdapter {
478
487
  private readonly submitStrategy: 'wait_for_echo' | 'immediate';
479
488
  private static readonly SCRIPT_STATUS_DEBOUNCE_MS = 1000;
480
489
 
481
- constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
490
+ constructor(
491
+ provider: CliProviderModule,
492
+ workingDir: string,
493
+ private extraArgs: string[] = [],
494
+ transportFactory: PtyTransportFactory = new NodePtyTransportFactory(),
495
+ ) {
482
496
  this.provider = provider;
497
+ this.transportFactory = transportFactory;
483
498
  this.cliType = provider.type;
484
499
  this.cliName = provider.name;
485
500
  this.workingDir = workingDir.startsWith('~')
@@ -554,7 +569,6 @@ export class ProviderCliAdapter implements CliAdapter {
554
569
 
555
570
  async spawn(): Promise<void> {
556
571
  if (this.ptyProcess) return;
557
- if (!pty) throw new Error('node-pty is not installed');
558
572
 
559
573
  const { spawn: spawnConfig } = this.provider;
560
574
  const binaryPath = findBinary(spawnConfig.command);
@@ -586,7 +600,6 @@ export class ProviderCliAdapter implements CliAdapter {
586
600
  }
587
601
 
588
602
  const ptyOpts = {
589
- name: 'xterm-256color',
590
603
  cols: 120,
591
604
  rows: 40,
592
605
  cwd: this.workingDir,
@@ -597,7 +610,7 @@ export class ProviderCliAdapter implements CliAdapter {
597
610
  };
598
611
 
599
612
  try {
600
- this.ptyProcess = pty.spawn(shellCmd, shellArgs, ptyOpts);
613
+ this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
601
614
  } catch (err: any) {
602
615
  const msg = err?.message || String(err);
603
616
  if (!isWin && !useShell && /posix_spawn|spawn/i.test(msg)) {
@@ -605,7 +618,7 @@ export class ProviderCliAdapter implements CliAdapter {
605
618
  shellCmd = process.env.SHELL || '/bin/zsh';
606
619
  const fullCmd = [binaryPath, ...allArgs].map(shSingleQuote).join(' ');
607
620
  shellArgs = ['-l', '-c', fullCmd];
608
- this.ptyProcess = pty.spawn(shellCmd, shellArgs, ptyOpts);
621
+ this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
609
622
  } else {
610
623
  throw err;
611
624
  }
@@ -659,6 +672,7 @@ export class ProviderCliAdapter implements CliAdapter {
659
672
  this.terminalHistory = '';
660
673
  this.currentTurnScope = null;
661
674
  this.ready = false;
675
+ await this.ptyProcess.ready;
662
676
  this.setStatus('idle', 'pty_ready');
663
677
  this.onStatusChange?.();
664
678
  }
@@ -766,6 +780,17 @@ export class ProviderCliAdapter implements CliAdapter {
766
780
  }, 60000);
767
781
  }
768
782
 
783
+ private looksLikeVisibleIdlePrompt(screenText: string): boolean {
784
+ const text = String(screenText || '');
785
+ if (!text.trim()) return false;
786
+ return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text)
787
+ || /⏎\s+send/i.test(text)
788
+ || /\?\s*for\s*shortcuts/i.test(text)
789
+ || /Type your message(?:\s+or\s+@path\/to\/file)?/i.test(text)
790
+ || /workspace\s*\(\/directory\)/i.test(text)
791
+ || /for\s*shortcuts/i.test(text);
792
+ }
793
+
769
794
  private evaluateSettled(): void {
770
795
  const now = Date.now();
771
796
  if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
@@ -779,6 +804,7 @@ export class ProviderCliAdapter implements CliAdapter {
779
804
  return;
780
805
  }
781
806
  const tail = this.settledBuffer;
807
+ const screenText = this.terminalScreen.getText() || '';
782
808
  const modal = this.runParseApproval(tail);
783
809
  const rawScriptStatus = this.runDetectStatus(tail);
784
810
  // detectStatus is the sole authority for status. parseApproval only enriches modal info.
@@ -827,6 +853,24 @@ export class ProviderCliAdapter implements CliAdapter {
827
853
 
828
854
  if (scriptStatus === 'waiting_approval') {
829
855
  const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
856
+ const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
857
+ if ((inCooldown || visibleIdlePrompt) && !modal) {
858
+ if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
859
+ this.activeModal = null;
860
+ if (this.isWaitingForResponse) {
861
+ this.setStatus('generating', inCooldown ? 'approval_cooldown_ignore' : 'approval_prompt_gone');
862
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
863
+ this.idleTimeout = setTimeout(() => {
864
+ if (this.isWaitingForResponse && this.currentStatus !== 'waiting_approval') {
865
+ this.finishResponse();
866
+ }
867
+ }, this.timeouts.generatingIdle);
868
+ } else {
869
+ this.setStatus('idle', inCooldown ? 'approval_cooldown_ignore' : 'approval_prompt_gone');
870
+ }
871
+ this.onStatusChange?.();
872
+ return;
873
+ }
830
874
  if (!inCooldown) {
831
875
  this.isWaitingForResponse = true;
832
876
  this.setStatus('waiting_approval', 'script_detect');
@@ -842,13 +886,13 @@ export class ProviderCliAdapter implements CliAdapter {
842
886
  }
843
887
 
844
888
  if (scriptStatus === 'generating') {
845
- const screenText = this.terminalScreen.getText() || this.accumulatedBuffer;
889
+ const effectiveScreenText = screenText || this.accumulatedBuffer;
846
890
  const noActiveTurn = !this.currentTurnScope;
847
- const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(screenText)
848
- || (/accept edits on/i.test(screenText)
891
+ const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText)
892
+ || (/accept edits on/i.test(effectiveScreenText)
849
893
  && (/Update available!/i.test(screenText)
850
894
  || /\/effort/i.test(screenText)
851
- || /^.*➜\s+\S+/m.test(screenText)));
895
+ || /^.*➜\s+\S+/m.test(effectiveScreenText)));
852
896
  if (prevStatus === 'idle' && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome) {
853
897
  return;
854
898
  }
@@ -986,6 +1030,7 @@ export class ProviderCliAdapter implements CliAdapter {
986
1030
  try {
987
1031
  return this.cliScripts.parseApproval({
988
1032
  buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
1033
+ screenText: this.terminalScreen.getText(),
989
1034
  rawBuffer: this.accumulatedRawBuffer,
990
1035
  tail,
991
1036
  });
@@ -1203,8 +1248,75 @@ export class ProviderCliAdapter implements CliAdapter {
1203
1248
  return this.responseBuffer;
1204
1249
  }
1205
1250
 
1251
+ getRuntimeMetadata(): PtyRuntimeMetadata | null {
1252
+ if (!this.ptyProcess || typeof this.ptyProcess.getMetadata !== 'function') return null;
1253
+ return this.ptyProcess.getMetadata();
1254
+ }
1255
+
1206
1256
  cancel(): void { this.shutdown(); }
1207
1257
 
1258
+ async saveAndStop(): Promise<void> {
1259
+ if (!this.ptyProcess) return;
1260
+ const resume = this.provider.resume;
1261
+ if (!resume?.supported) {
1262
+ this.shutdown();
1263
+ return;
1264
+ }
1265
+
1266
+ const stopStrategy = resume.stopStrategy || 'command';
1267
+ const stopCommand = typeof resume.stopCommand === 'string' ? resume.stopCommand.trim() : '';
1268
+ const shutdownGraceMs = Math.max(
1269
+ this.timeouts.shutdownGrace,
1270
+ typeof resume.shutdownGraceMs === 'number' ? resume.shutdownGraceMs : 3000,
1271
+ );
1272
+ const wasProcessing = this.currentStatus === 'generating' || this.currentStatus === 'waiting_approval';
1273
+
1274
+ try {
1275
+ if (wasProcessing) {
1276
+ this.ptyProcess.write('\x03');
1277
+ }
1278
+ if (stopStrategy === 'command' && stopCommand) {
1279
+ const writeCommand = () => {
1280
+ if (!this.ptyProcess) return;
1281
+ const payload = stopCommand.endsWith('\r') || stopCommand.endsWith('\n')
1282
+ ? stopCommand
1283
+ : `${stopCommand}${this.sendKey}`;
1284
+ this.ptyProcess.write(payload);
1285
+ };
1286
+ if (wasProcessing) setTimeout(writeCommand, 250);
1287
+ else writeCommand();
1288
+ } else {
1289
+ this.ptyProcess.write('\x03');
1290
+ }
1291
+ } catch (error: any) {
1292
+ LOG.warn('CLI', `[${this.cliType}] saveAndStop signal failed: ${error?.message || error}`);
1293
+ }
1294
+
1295
+ const stopped = await this.waitForStopped(shutdownGraceMs);
1296
+ if (!stopped) {
1297
+ LOG.warn('CLI', `[${this.cliType}] graceful stop timed out, forcing shutdown`);
1298
+ this.shutdown();
1299
+ await this.waitForStopped(this.timeouts.shutdownGrace + 500);
1300
+ }
1301
+ }
1302
+
1303
+ private waitForStopped(timeoutMs: number): Promise<boolean> {
1304
+ return new Promise((resolve) => {
1305
+ const startedAt = Date.now();
1306
+ const timer = setInterval(() => {
1307
+ if (!this.ptyProcess || this.currentStatus === 'stopped') {
1308
+ clearInterval(timer);
1309
+ resolve(true);
1310
+ return;
1311
+ }
1312
+ if (Date.now() - startedAt >= timeoutMs) {
1313
+ clearInterval(timer);
1314
+ resolve(false);
1315
+ }
1316
+ }, 100);
1317
+ });
1318
+ }
1319
+
1208
1320
  shutdown(): void {
1209
1321
  if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
1210
1322
  if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
@@ -1227,6 +1339,30 @@ export class ProviderCliAdapter implements CliAdapter {
1227
1339
  }
1228
1340
  }
1229
1341
 
1342
+ detach(): void {
1343
+ if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
1344
+ if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
1345
+ if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
1346
+ if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
1347
+ this.pendingOutputParseBuffer = '';
1348
+ if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
1349
+ this.ptyOutputBuffer = '';
1350
+ if (this.ptyProcess) {
1351
+ try {
1352
+ if (typeof this.ptyProcess.detach === 'function') {
1353
+ this.ptyProcess.detach();
1354
+ } else {
1355
+ this.ptyProcess.kill();
1356
+ }
1357
+ } catch { /* noop */ }
1358
+ this.ptyProcess = null;
1359
+ }
1360
+ this.ready = false;
1361
+ this.startupParseGate = false;
1362
+ this.spawnAt = 0;
1363
+ this.onStatusChange?.();
1364
+ }
1365
+
1230
1366
  clearHistory(): void {
1231
1367
  this.committedMessages = [];
1232
1368
  this.syncMessageViews();
@@ -1241,6 +1377,7 @@ export class ProviderCliAdapter implements CliAdapter {
1241
1377
  if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
1242
1378
  this.ptyOutputBuffer = '';
1243
1379
  this.terminalScreen.reset();
1380
+ this.ptyProcess?.clearBuffer?.();
1244
1381
  this.onStatusChange?.();
1245
1382
  }
1246
1383
 
@@ -0,0 +1,100 @@
1
+ import * as os from 'os';
2
+
3
+ let pty: any;
4
+ try {
5
+ pty = require('node-pty');
6
+ } catch {
7
+ pty = null;
8
+ }
9
+
10
+ export interface PtySpawnOptions {
11
+ cwd: string;
12
+ env: Record<string, string>;
13
+ cols: number;
14
+ rows: number;
15
+ }
16
+
17
+ export interface PtyRuntimeWriteOwner {
18
+ clientId: string;
19
+ ownerType: 'agent' | 'user';
20
+ }
21
+
22
+ export interface PtyRuntimeClientInfo {
23
+ clientId: string;
24
+ type: 'daemon' | 'web' | 'local-terminal';
25
+ readOnly: boolean;
26
+ }
27
+
28
+ export interface PtyRuntimeMetadata {
29
+ runtimeId: string;
30
+ runtimeKey?: string;
31
+ displayName?: string;
32
+ workspaceLabel?: string;
33
+ writeOwner?: PtyRuntimeWriteOwner | null;
34
+ attachedClients?: PtyRuntimeClientInfo[];
35
+ }
36
+
37
+ export interface PtyRuntimeTransport {
38
+ readonly pid: number;
39
+ readonly ready: Promise<void>;
40
+ write(data: string): void;
41
+ resize(cols: number, rows: number): void;
42
+ kill(): void;
43
+ clearBuffer?(): void;
44
+ detach?(): void;
45
+ getMetadata?(): PtyRuntimeMetadata | null;
46
+ onData(callback: (data: string) => void): void;
47
+ onExit(callback: (info: { exitCode: number }) => void): void;
48
+ }
49
+
50
+ export interface PtyTransportFactory {
51
+ spawn(command: string, args: string[], options: PtySpawnOptions): PtyRuntimeTransport;
52
+ }
53
+
54
+ class NodePtyRuntimeTransport implements PtyRuntimeTransport {
55
+ readonly ready = Promise.resolve();
56
+
57
+ constructor(private readonly handle: any) {}
58
+
59
+ get pid(): number {
60
+ return this.handle.pid;
61
+ }
62
+
63
+ write(data: string): void {
64
+ this.handle.write(data);
65
+ }
66
+
67
+ resize(cols: number, rows: number): void {
68
+ this.handle.resize(cols, rows);
69
+ }
70
+
71
+ kill(): void {
72
+ this.handle.kill();
73
+ }
74
+
75
+ getMetadata(): PtyRuntimeMetadata | null {
76
+ return null;
77
+ }
78
+
79
+ onData(callback: (data: string) => void): void {
80
+ this.handle.onData(callback);
81
+ }
82
+
83
+ onExit(callback: (info: { exitCode: number }) => void): void {
84
+ this.handle.onExit(callback);
85
+ }
86
+ }
87
+
88
+ export class NodePtyTransportFactory implements PtyTransportFactory {
89
+ spawn(command: string, args: string[], options: PtySpawnOptions): PtyRuntimeTransport {
90
+ if (!pty) throw new Error('node-pty is not installed');
91
+ const handle = pty.spawn(command, args, {
92
+ name: os.platform() === 'win32' ? 'xterm-color' : 'xterm-256color',
93
+ cols: options.cols,
94
+ rows: options.rows,
95
+ cwd: options.cwd,
96
+ env: options.env,
97
+ });
98
+ return new NodePtyRuntimeTransport(handle);
99
+ }
100
+ }