tabminal 3.0.7 → 3.0.9

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.
package/public/styles.css CHANGED
@@ -1561,7 +1561,7 @@ kbd {
1561
1561
  .tab-status-icon.is-attention,
1562
1562
  .agent-editor-tab-icon.is-attention,
1563
1563
  .toggle-agent-btn.is-attention {
1564
- color: var(--warning-color);
1564
+ color: #cb4b16;
1565
1565
  }
1566
1566
 
1567
1567
  .tab-status-icon.is-running,
@@ -102,6 +102,66 @@ function buildAgentConfigSummary(agentId, config = {}) {
102
102
  }
103
103
  }
104
104
 
105
+ function normalizeAgentSessionCapabilities(
106
+ agentCapabilities = null,
107
+ connection = null
108
+ ) {
109
+ const sessionCapabilities = (
110
+ agentCapabilities?.sessionCapabilities
111
+ && typeof agentCapabilities.sessionCapabilities === 'object'
112
+ )
113
+ ? agentCapabilities.sessionCapabilities
114
+ : {};
115
+ return {
116
+ load: !!(
117
+ agentCapabilities?.loadSession
118
+ && typeof connection?.loadSession === 'function'
119
+ ),
120
+ list: !!(
121
+ sessionCapabilities?.list
122
+ && typeof connection?.listSessions === 'function'
123
+ ),
124
+ resume: !!(
125
+ sessionCapabilities?.resume
126
+ && typeof connection?.unstable_resumeSession === 'function'
127
+ ),
128
+ fork: !!(
129
+ sessionCapabilities?.fork
130
+ && typeof connection?.unstable_forkSession === 'function'
131
+ )
132
+ };
133
+ }
134
+
135
+ function normalizeListedSessionInfo(session) {
136
+ const normalized = session && typeof session === 'object' ? session : {};
137
+ return {
138
+ sessionId: String(normalized.sessionId || '').trim(),
139
+ cwd: String(normalized.cwd || '').trim(),
140
+ title: typeof normalized.title === 'string' ? normalized.title : '',
141
+ updatedAt: typeof normalized.updatedAt === 'string'
142
+ ? normalized.updatedAt
143
+ : ''
144
+ };
145
+ }
146
+
147
+ function parseGeminiListedSessions(output) {
148
+ const text = String(output || '');
149
+ const lines = text.split(/\r?\n/);
150
+ const sessions = [];
151
+ for (const line of lines) {
152
+ const match = line.match(
153
+ /^\s*\d+\.\s+(.*?)\s+\((.*?)\)\s+\[([0-9a-f-]{8,})\]\s*$/i
154
+ );
155
+ if (!match) continue;
156
+ sessions.push({
157
+ title: match[1]?.trim() || '',
158
+ relativeUpdatedAt: match[2]?.trim() || '',
159
+ sessionId: match[3]?.trim() || ''
160
+ });
161
+ }
162
+ return sessions.filter((session) => session.sessionId);
163
+ }
164
+
105
165
  let ghCopilotCliInstalledCache = null;
106
166
  let ghAuthTokenCache = null;
107
167
  const availabilityProbeCache = new Map();
@@ -1451,6 +1511,72 @@ class AcpRuntime extends EventEmitter {
1451
1511
  this.cachedModelState = null;
1452
1512
  }
1453
1513
 
1514
+ #getSessionCapabilities() {
1515
+ const capabilities = normalizeAgentSessionCapabilities(
1516
+ this.agentCapabilities,
1517
+ this.connection
1518
+ );
1519
+ if (
1520
+ this.definition?.id === 'gemini'
1521
+ && this.#supportsGeminiCliSessionListing()
1522
+ ) {
1523
+ capabilities.list = true;
1524
+ }
1525
+ return capabilities;
1526
+ }
1527
+
1528
+ #supportsGeminiCliSessionListing() {
1529
+ if (this.definition?.id !== 'gemini') return false;
1530
+ const command = this.definition.command;
1531
+ return Boolean(
1532
+ command === 'gemini'
1533
+ || (
1534
+ command === NPX_COMMAND
1535
+ && Array.isArray(this.definition.args)
1536
+ && this.definition.args[0] === '@google/gemini-cli@latest'
1537
+ )
1538
+ );
1539
+ }
1540
+
1541
+ #buildGeminiSessionListArgs() {
1542
+ const baseArgs = Array.isArray(this.definition.args)
1543
+ ? this.definition.args.filter((arg) => arg !== '--acp')
1544
+ : [];
1545
+ return [
1546
+ ...baseArgs,
1547
+ '--list-sessions'
1548
+ ];
1549
+ }
1550
+
1551
+ async #listSessionsViaGeminiCli() {
1552
+ const args = this.#buildGeminiSessionListArgs();
1553
+ const result = spawnSync(this.definition.command, args, {
1554
+ encoding: 'utf8',
1555
+ timeout: 5000,
1556
+ cwd: this.cwd,
1557
+ env: withAgentPath(this.env)
1558
+ });
1559
+ if (result.error) {
1560
+ throw result.error;
1561
+ }
1562
+ if (result.status !== 0) {
1563
+ const detail = [
1564
+ result.stdout,
1565
+ result.stderr
1566
+ ].filter(Boolean).join('\n').trim();
1567
+ throw new Error(
1568
+ detail || 'Gemini session listing failed'
1569
+ );
1570
+ }
1571
+ return parseGeminiListedSessions(result.stdout).map((session) => ({
1572
+ sessionId: session.sessionId,
1573
+ cwd: this.cwd,
1574
+ title: session.title,
1575
+ updatedAt: '',
1576
+ relativeUpdatedAt: session.relativeUpdatedAt
1577
+ }));
1578
+ }
1579
+
1454
1580
  #resolveAvailableModes(availableModes, existingModes = []) {
1455
1581
  if (Array.isArray(availableModes) && availableModes.length > 0) {
1456
1582
  this.cachedAvailableModes = availableModes;
@@ -1892,6 +2018,125 @@ class AcpRuntime extends EventEmitter {
1892
2018
  }
1893
2019
  }
1894
2020
 
2021
+ async listSessions(options = {}) {
2022
+ await this.start();
2023
+ this.clearIdleShutdown();
2024
+
2025
+ const sessionCapabilities = this.#getSessionCapabilities();
2026
+ if (
2027
+ sessionCapabilities.list
2028
+ && typeof this.connection?.listSessions === 'function'
2029
+ ) {
2030
+ try {
2031
+ const response = await this.connection.listSessions({
2032
+ cwd: options.cwd || this.cwd,
2033
+ cursor: options.cursor || null
2034
+ });
2035
+ return {
2036
+ sessions: Array.isArray(response?.sessions)
2037
+ ? response.sessions
2038
+ .map(normalizeListedSessionInfo)
2039
+ .filter(
2040
+ (session) => session.sessionId && session.cwd
2041
+ )
2042
+ : [],
2043
+ nextCursor: typeof response?.nextCursor === 'string'
2044
+ ? response.nextCursor
2045
+ : ''
2046
+ };
2047
+ } catch (error) {
2048
+ const message = String(error?.message || '');
2049
+ const canFallbackToCli = this.#supportsGeminiCliSessionListing();
2050
+ const unsupportedListMethod =
2051
+ /method not found/i.test(message)
2052
+ || /session\/list/i.test(message);
2053
+ if (!canFallbackToCli || !unsupportedListMethod) {
2054
+ throw error;
2055
+ }
2056
+ }
2057
+ }
2058
+ if (this.#supportsGeminiCliSessionListing()) {
2059
+ return {
2060
+ sessions: await this.#listSessionsViaGeminiCli(),
2061
+ nextCursor: ''
2062
+ };
2063
+ }
2064
+ if (!sessionCapabilities.list) {
2065
+ throw new Error(
2066
+ `${this.definition.label} does not support session history`
2067
+ );
2068
+ }
2069
+ throw new Error(`${this.definition.label} session history unavailable`);
2070
+ }
2071
+
2072
+ async resumeTab(meta) {
2073
+ await this.start();
2074
+ this.clearIdleShutdown();
2075
+
2076
+ const sessionCapabilities = this.#getSessionCapabilities();
2077
+ if (!sessionCapabilities.load) {
2078
+ throw new Error(
2079
+ `${this.definition.label} does not support session restore`
2080
+ );
2081
+ }
2082
+ if (this.sessionToTabId.has(meta.acpSessionId)) {
2083
+ throw new Error('Session is already open');
2084
+ }
2085
+
2086
+ const tab = this.#buildTab({
2087
+ id: meta.id,
2088
+ acpSessionId: meta.acpSessionId,
2089
+ terminalSessionId: meta.terminalSessionId,
2090
+ cwd: meta.cwd,
2091
+ createdAt: new Date().toISOString(),
2092
+ title: meta.title || ''
2093
+ });
2094
+ tab.status = 'restoring';
2095
+ tab.busy = true;
2096
+
2097
+ this.tabs.set(tab.id, tab);
2098
+ this.sessionToTabId.set(tab.acpSessionId, tab.id);
2099
+
2100
+ try {
2101
+ const response = await this.connection.loadSession({
2102
+ cwd: meta.cwd,
2103
+ sessionId: meta.acpSessionId,
2104
+ mcpServers: []
2105
+ });
2106
+ const restoredSessionId = response?.sessionId || meta.acpSessionId;
2107
+ if (restoredSessionId !== tab.acpSessionId) {
2108
+ this.sessionToTabId.delete(tab.acpSessionId);
2109
+ tab.acpSessionId = restoredSessionId;
2110
+ this.sessionToTabId.set(tab.acpSessionId, tab.id);
2111
+ }
2112
+ if (typeof response?.title === 'string') {
2113
+ tab.title = response.title;
2114
+ }
2115
+ tab.currentModeId = response?.modes?.currentModeId || '';
2116
+ tab.availableModes = this.#resolveAvailableModes(
2117
+ response?.modes?.availableModes,
2118
+ tab.availableModes
2119
+ );
2120
+ tab.availableCommands = this.#resolveAvailableCommands(
2121
+ response?.availableCommands,
2122
+ tab.availableCommands
2123
+ );
2124
+ tab.configOptions = this.#resolveConfigOptions(
2125
+ response?.configOptions,
2126
+ tab.configOptions,
2127
+ response?.models
2128
+ );
2129
+ tab.status = 'ready';
2130
+ tab.busy = false;
2131
+ tab.errorMessage = '';
2132
+ return this.serializeTab(tab);
2133
+ } catch (error) {
2134
+ this.tabs.delete(tab.id);
2135
+ this.sessionToTabId.delete(tab.acpSessionId);
2136
+ throw error;
2137
+ }
2138
+ }
2139
+
1895
2140
  #markTabDirty(tab) {
1896
2141
  if (!tab) return;
1897
2142
  this.emit('tab_dirty', {
@@ -1919,6 +2164,7 @@ class AcpRuntime extends EventEmitter {
1919
2164
  currentModeId: tab.currentModeId,
1920
2165
  availableModes: tab.availableModes,
1921
2166
  availableCommands: tab.availableCommands,
2167
+ sessionCapabilities: this.#getSessionCapabilities(),
1922
2168
  configOptions: tab.configOptions,
1923
2169
  messages: tab.messages,
1924
2170
  toolCalls: Array.from(tab.toolCalls.values()),
@@ -3082,11 +3328,74 @@ export class AcpManager {
3082
3328
  return this.getSerializedAgentConfig(agentId);
3083
3329
  }
3084
3330
 
3331
+ #ensureRuntimeEntry(definition, cwd) {
3332
+ const runtimeKey = makeRuntimeKey(definition.id, cwd);
3333
+ const runtimeStoreKey = makeRuntimeStoreKey(
3334
+ definition.id,
3335
+ cwd,
3336
+ this.getAgentConfigVersion(definition.id)
3337
+ );
3338
+ let runtimeEntry = this.runtimes.get(runtimeStoreKey);
3339
+ let createdRuntime = false;
3340
+
3341
+ if (!runtimeEntry) {
3342
+ const runtime = this.runtimeFactory(definition, {
3343
+ cwd,
3344
+ idleTimeoutMs: this.idleTimeoutMs,
3345
+ runtimeStoreKey,
3346
+ terminalManager: this.terminalManager,
3347
+ env: mergeDefinitionEnv(
3348
+ definition,
3349
+ this.getAgentConfig(definition.id)
3350
+ )
3351
+ });
3352
+ runtimeEntry = {
3353
+ runtime,
3354
+ definition,
3355
+ runtimeKey,
3356
+ runtimeStoreKey
3357
+ };
3358
+ this.runtimes.set(runtimeStoreKey, runtimeEntry);
3359
+ createdRuntime = true;
3360
+ runtime.on('tab_dirty', () => {
3361
+ this.schedulePersistTabs();
3362
+ });
3363
+ runtime.on('runtime_exit', () => {
3364
+ if (this.disposing) return;
3365
+ for (const [tabId, tabEntry] of this.tabs.entries()) {
3366
+ if (tabEntry.runtime !== runtime) continue;
3367
+ this.tabs.delete(tabId);
3368
+ }
3369
+ this.runtimes.delete(runtimeStoreKey);
3370
+ void this.persistTabs();
3371
+ });
3372
+ }
3373
+
3374
+ return {
3375
+ runtimeEntry,
3376
+ createdRuntime,
3377
+ runtimeStoreKey
3378
+ };
3379
+ }
3380
+
3381
+ async #disposeRuntimeEntry(runtimeStoreKey, runtimeEntry) {
3382
+ if (!runtimeEntry) return;
3383
+ this.runtimes.delete(runtimeStoreKey);
3384
+ await runtimeEntry.runtime.dispose().catch(() => {});
3385
+ }
3386
+
3085
3387
  #applyRuntimeMetadataFallback(runtime, serialized) {
3086
3388
  if (!serialized || typeof serialized !== 'object') {
3087
3389
  return serialized;
3088
3390
  }
3089
3391
 
3392
+ const sessionCapabilities = normalizeAgentSessionCapabilities(
3393
+ runtime?.agentCapabilities,
3394
+ runtime?.connection
3395
+ );
3396
+ const hasSessionCapabilities = serialized.sessionCapabilities
3397
+ && typeof serialized.sessionCapabilities === 'object';
3398
+
3090
3399
  let availableModes = Array.isArray(serialized.availableModes)
3091
3400
  ? serialized.availableModes
3092
3401
  : [];
@@ -3105,7 +3414,13 @@ export class AcpManager {
3105
3414
  )
3106
3415
  || !(runtime?.tabs instanceof Map)
3107
3416
  ) {
3108
- return serialized;
3417
+ if (hasSessionCapabilities) {
3418
+ return serialized;
3419
+ }
3420
+ return {
3421
+ ...serialized,
3422
+ sessionCapabilities
3423
+ };
3109
3424
  }
3110
3425
 
3111
3426
  for (const runtimeTab of runtime.tabs.values()) {
@@ -3144,6 +3459,7 @@ export class AcpManager {
3144
3459
  availableModes === serialized.availableModes
3145
3460
  && availableCommands === serialized.availableCommands
3146
3461
  && configOptions === serialized.configOptions
3462
+ && hasSessionCapabilities
3147
3463
  ) {
3148
3464
  return serialized;
3149
3465
  }
@@ -3152,7 +3468,8 @@ export class AcpManager {
3152
3468
  ...serialized,
3153
3469
  availableModes,
3154
3470
  availableCommands,
3155
- configOptions
3471
+ configOptions,
3472
+ sessionCapabilities
3156
3473
  };
3157
3474
  }
3158
3475
 
@@ -3274,7 +3591,8 @@ export class AcpManager {
3274
3591
  status: serialized.status,
3275
3592
  busy: serialized.busy,
3276
3593
  errorMessage: serialized.errorMessage,
3277
- currentModeId: serialized.currentModeId
3594
+ currentModeId: serialized.currentModeId,
3595
+ sessionCapabilities: serialized.sessionCapabilities
3278
3596
  };
3279
3597
  })
3280
3598
  };
@@ -3294,54 +3612,115 @@ export class AcpManager {
3294
3612
  }
3295
3613
 
3296
3614
  const cwd = path.resolve(options.cwd || process.cwd());
3297
- const runtimeKey = makeRuntimeKey(definition.id, cwd);
3298
- const runtimeStoreKey = makeRuntimeStoreKey(
3299
- definition.id,
3300
- cwd,
3301
- this.getAgentConfigVersion(definition.id)
3302
- );
3303
- let runtimeEntry = this.runtimes.get(runtimeStoreKey);
3304
- let createdRuntime = false;
3305
- if (!runtimeEntry) {
3306
- const runtime = this.runtimeFactory(definition, {
3615
+ const { runtimeEntry, createdRuntime, runtimeStoreKey } =
3616
+ this.#ensureRuntimeEntry(definition, cwd);
3617
+
3618
+ const tabId = crypto.randomUUID();
3619
+ try {
3620
+ const rawSerialized = await runtimeEntry.runtime.createTab({
3621
+ id: tabId,
3307
3622
  cwd,
3308
- idleTimeoutMs: this.idleTimeoutMs,
3309
- runtimeStoreKey,
3310
- terminalManager: this.terminalManager,
3311
- env: mergeDefinitionEnv(
3312
- definition,
3313
- this.getAgentConfig(definition.id)
3314
- )
3315
- });
3316
- runtimeEntry = {
3317
- runtime,
3318
- definition,
3319
- runtimeKey,
3320
- runtimeStoreKey
3321
- };
3322
- this.runtimes.set(runtimeStoreKey, runtimeEntry);
3323
- createdRuntime = true;
3324
- runtime.on('tab_dirty', () => {
3325
- this.schedulePersistTabs();
3623
+ terminalSessionId: options.terminalSessionId || '',
3624
+ modeId: options.modeId || ''
3326
3625
  });
3327
- runtime.on('runtime_exit', () => {
3328
- if (this.disposing) return;
3329
- for (const [tabId, tabEntry] of this.tabs.entries()) {
3330
- if (tabEntry.runtime !== runtime) continue;
3331
- this.tabs.delete(tabId);
3626
+ const serialized = this.#applyRuntimeMetadataFallback(
3627
+ runtimeEntry.runtime,
3628
+ rawSerialized
3629
+ );
3630
+ const tabEntry = {
3631
+ runtime: runtimeEntry.runtime,
3632
+ serialize: () => {
3633
+ const tab = runtimeEntry.runtime.tabs.get(tabId);
3634
+ const nextSerialized = tab
3635
+ ? runtimeEntry.runtime.serializeTab(tab)
3636
+ : serialized;
3637
+ return this.#applyRuntimeMetadataFallback(
3638
+ runtimeEntry.runtime,
3639
+ nextSerialized
3640
+ );
3332
3641
  }
3333
- this.runtimes.delete(runtimeStoreKey);
3334
- void this.persistTabs();
3642
+ };
3643
+ this.tabs.set(tabId, tabEntry);
3644
+ this.#clearDefinitionAvailabilityOverride(definition.id);
3645
+ await this.persistTabs();
3646
+ return tabEntry.serialize();
3647
+ } catch (error) {
3648
+ const shouldDisposeRuntime = createdRuntime
3649
+ || runtimeEntry.runtime.tabs.size === 0;
3650
+ if (shouldDisposeRuntime) {
3651
+ await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
3652
+ }
3653
+ this.#recordDefinitionStartupFailure(definition, error);
3654
+ throw new Error(formatAgentStartupError(definition, error));
3655
+ }
3656
+ }
3657
+
3658
+ async listSessions(options) {
3659
+ await this.ensureConfigsLoaded();
3660
+ const definition = this.definitions.find(
3661
+ (entry) => entry.id === options.agentId
3662
+ );
3663
+ if (!definition) {
3664
+ throw new Error('Unknown agent');
3665
+ }
3666
+ const availability = this.getDefinitionAvailability(definition);
3667
+ if (!availability.available) {
3668
+ throw new Error(availability.reason || 'Agent unavailable');
3669
+ }
3670
+
3671
+ const cwd = path.resolve(options.cwd || process.cwd());
3672
+ const { runtimeEntry, createdRuntime, runtimeStoreKey } =
3673
+ this.#ensureRuntimeEntry(definition, cwd);
3674
+
3675
+ try {
3676
+ const result = await runtimeEntry.runtime.listSessions({
3677
+ cwd,
3678
+ cursor: typeof options.cursor === 'string' ? options.cursor : ''
3335
3679
  });
3680
+ this.#clearDefinitionAvailabilityOverride(definition.id);
3681
+ if (runtimeEntry.runtime.tabs.size === 0) {
3682
+ runtimeEntry.runtime.scheduleIdleShutdown(async () => {
3683
+ if (runtimeEntry.runtime.tabs.size > 0) return;
3684
+ await this.#disposeRuntimeEntry(
3685
+ runtimeStoreKey,
3686
+ runtimeEntry
3687
+ );
3688
+ });
3689
+ }
3690
+ return result;
3691
+ } catch (error) {
3692
+ if (createdRuntime && runtimeEntry.runtime.tabs.size === 0) {
3693
+ await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
3694
+ }
3695
+ throw error;
3336
3696
  }
3697
+ }
3337
3698
 
3699
+ async resumeTab(options) {
3700
+ await this.ensureConfigsLoaded();
3701
+ const definition = this.definitions.find(
3702
+ (entry) => entry.id === options.agentId
3703
+ );
3704
+ if (!definition) {
3705
+ throw new Error('Unknown agent');
3706
+ }
3707
+ const availability = this.getDefinitionAvailability(definition);
3708
+ if (!availability.available) {
3709
+ throw new Error(availability.reason || 'Agent unavailable');
3710
+ }
3711
+
3712
+ const cwd = path.resolve(options.cwd || process.cwd());
3713
+ const { runtimeEntry, createdRuntime, runtimeStoreKey } =
3714
+ this.#ensureRuntimeEntry(definition, cwd);
3338
3715
  const tabId = crypto.randomUUID();
3716
+
3339
3717
  try {
3340
- const rawSerialized = await runtimeEntry.runtime.createTab({
3718
+ const rawSerialized = await runtimeEntry.runtime.resumeTab({
3341
3719
  id: tabId,
3720
+ acpSessionId: options.sessionId,
3342
3721
  cwd,
3343
3722
  terminalSessionId: options.terminalSessionId || '',
3344
- modeId: options.modeId || ''
3723
+ title: options.title || ''
3345
3724
  });
3346
3725
  const serialized = this.#applyRuntimeMetadataFallback(
3347
3726
  runtimeEntry.runtime,
@@ -3368,11 +3747,9 @@ export class AcpManager {
3368
3747
  const shouldDisposeRuntime = createdRuntime
3369
3748
  || runtimeEntry.runtime.tabs.size === 0;
3370
3749
  if (shouldDisposeRuntime) {
3371
- this.runtimes.delete(runtimeStoreKey);
3372
- await runtimeEntry.runtime.dispose().catch(() => {});
3750
+ await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
3373
3751
  }
3374
- this.#recordDefinitionStartupFailure(definition, error);
3375
- throw new Error(formatAgentStartupError(definition, error));
3752
+ throw error;
3376
3753
  }
3377
3754
  }
3378
3755
 
@@ -3398,7 +3775,6 @@ export class AcpManager {
3398
3775
  continue;
3399
3776
  }
3400
3777
 
3401
- const agentConfig = this.getAgentConfig(definition.id);
3402
3778
  const availability = this.getDefinitionAvailability(definition);
3403
3779
  if (!availability.available) {
3404
3780
  changed = true;
@@ -3406,41 +3782,7 @@ export class AcpManager {
3406
3782
  }
3407
3783
 
3408
3784
  const cwd = path.resolve(meta.cwd || process.cwd());
3409
- const runtimeKey = makeRuntimeKey(definition.id, cwd);
3410
- const runtimeStoreKey = makeRuntimeStoreKey(
3411
- definition.id,
3412
- cwd,
3413
- this.getAgentConfigVersion(definition.id)
3414
- );
3415
- let runtimeEntry = this.runtimes.get(runtimeStoreKey);
3416
- if (!runtimeEntry) {
3417
- const runtime = this.runtimeFactory(definition, {
3418
- cwd,
3419
- idleTimeoutMs: this.idleTimeoutMs,
3420
- runtimeStoreKey,
3421
- terminalManager: this.terminalManager,
3422
- env: mergeDefinitionEnv(definition, agentConfig)
3423
- });
3424
- runtimeEntry = {
3425
- runtime,
3426
- definition,
3427
- runtimeKey,
3428
- runtimeStoreKey
3429
- };
3430
- this.runtimes.set(runtimeStoreKey, runtimeEntry);
3431
- runtime.on('tab_dirty', () => {
3432
- this.schedulePersistTabs();
3433
- });
3434
- runtime.on('runtime_exit', () => {
3435
- if (this.disposing) return;
3436
- for (const [tabId, tabEntry] of this.tabs.entries()) {
3437
- if (tabEntry.runtime !== runtime) continue;
3438
- this.tabs.delete(tabId);
3439
- }
3440
- this.runtimes.delete(runtimeStoreKey);
3441
- void this.persistTabs();
3442
- });
3443
- }
3785
+ const { runtimeEntry } = this.#ensureRuntimeEntry(definition, cwd);
3444
3786
 
3445
3787
  try {
3446
3788
  const rawSerialized = await runtimeEntry.runtime.restoreTab({