tabminal 3.0.8 → 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/app.js CHANGED
@@ -375,6 +375,11 @@ function persistRuntimeBootId(bootId) {
375
375
  }
376
376
  }
377
377
 
378
+ function getLoadedRuntimeAssetKey() {
379
+ const assetKey = window.__tabminalRuntimeAssetKey;
380
+ return typeof assetKey === 'string' ? assetKey : '';
381
+ }
382
+
378
383
  function handlePrimaryRuntimeVersion(data) {
379
384
  const runtime = data?.runtime;
380
385
  const bootIdRaw = runtime?.bootId;
@@ -382,16 +387,20 @@ function handlePrimaryRuntimeVersion(data) {
382
387
  const bootId = String(bootIdRaw);
383
388
  if (!bootId) return;
384
389
  const storedBootId = readRuntimeBootId();
390
+ const loadedAssetKey = getLoadedRuntimeAssetKey();
391
+ const needsShellReload = loadedAssetKey !== bootId;
385
392
 
386
393
  if (!primaryServerBootId) {
387
394
  primaryServerBootId = bootId;
388
- if (storedBootId === bootId) {
395
+ if (storedBootId === bootId && !needsShellReload) {
389
396
  return;
390
397
  }
391
398
  const persisted = persistRuntimeBootId(bootId);
392
- if (storedBootId && persisted && !runtimeReloadScheduled) {
399
+ if (persisted && needsShellReload && !runtimeReloadScheduled) {
393
400
  runtimeReloadScheduled = true;
394
- console.info('[Runtime] Syncing app shell cache key with server boot id.');
401
+ console.info(
402
+ '[Runtime] Syncing app shell cache key with server boot id.'
403
+ );
395
404
  window.location.reload();
396
405
  }
397
406
  return;
@@ -400,6 +409,13 @@ function handlePrimaryRuntimeVersion(data) {
400
409
  if (storedBootId !== bootId) {
401
410
  persistRuntimeBootId(bootId);
402
411
  }
412
+ if (needsShellReload && !runtimeReloadScheduled) {
413
+ runtimeReloadScheduled = true;
414
+ console.info(
415
+ '[Runtime] Reloading app shell to match server boot id.'
416
+ );
417
+ window.location.reload();
418
+ }
403
419
  return;
404
420
  }
405
421
  if (runtimeReloadScheduled) return;
@@ -703,6 +719,8 @@ class EditorManager {
703
719
  this.agentCommandMenu = null;
704
720
  this.agentCommandSuggestions = [];
705
721
  this.agentCommandIndex = 0;
722
+ this.agentCommandMenuStateKey = '';
723
+ this.agentCommandMenuToken = 0;
706
724
  this.isApplyingAgentPromptState = false;
707
725
  this.suppressAgentCommandMenu = false;
708
726
  this.agentEmbeddedEditors = [];
@@ -1106,6 +1124,14 @@ class EditorManager {
1106
1124
  });
1107
1125
  this.agentPrompt.addEventListener('blur', () => {
1108
1126
  setTimeout(() => {
1127
+ if (
1128
+ document.activeElement?.classList?.contains(
1129
+ 'xterm-helper-textarea'
1130
+ )
1131
+ && this.agentCommandSuggestions.length > 0
1132
+ ) {
1133
+ return;
1134
+ }
1109
1135
  this.hideAgentCommandMenu();
1110
1136
  }, 120);
1111
1137
  });
@@ -1134,14 +1160,24 @@ class EditorManager {
1134
1160
  ? state.agentTabs.get(activeTabKey)
1135
1161
  : null;
1136
1162
 
1163
+ if (
1164
+ agentTab
1165
+ && this.agentCommandSuggestions.length > 0
1166
+ && Number.isInteger(agentTab.promptHistoryIndex)
1167
+ ) {
1168
+ this.exitAgentPromptHistoryBrowsing(agentTab);
1169
+ }
1170
+
1137
1171
  if (this.agentCommandSuggestions.length > 0) {
1138
1172
  if (event.key === 'ArrowDown') {
1139
1173
  event.preventDefault();
1174
+ event.stopImmediatePropagation();
1140
1175
  this.moveAgentCommandSelection(1);
1141
1176
  return;
1142
1177
  }
1143
1178
  if (event.key === 'ArrowUp') {
1144
1179
  event.preventDefault();
1180
+ event.stopImmediatePropagation();
1145
1181
  this.moveAgentCommandSelection(-1);
1146
1182
  return;
1147
1183
  }
@@ -1156,11 +1192,13 @@ class EditorManager {
1156
1192
  )
1157
1193
  ) {
1158
1194
  event.preventDefault();
1159
- this.applyAgentCommandSuggestion();
1195
+ event.stopImmediatePropagation();
1196
+ void this.applyAgentCommandSuggestion();
1160
1197
  return;
1161
1198
  }
1162
1199
  if (event.key === 'Escape') {
1163
1200
  event.preventDefault();
1201
+ event.stopImmediatePropagation();
1164
1202
  this.hideAgentCommandMenu();
1165
1203
  return;
1166
1204
  }
@@ -1556,9 +1594,7 @@ class EditorManager {
1556
1594
  const hasAgentTabs = getAgentTabsForSession(this.currentSession).length > 0;
1557
1595
  const compact = this.hasCompactWorkspaceTabs(this.currentSession);
1558
1596
  const hasTabs = compact || hasOpenFiles || hasAgentTabs;
1559
- const shouldShow = compact
1560
- ? true
1561
- : state.isVisible || hasOpenFiles || hasAgentTabs;
1597
+ const shouldShow = hasTabs;
1562
1598
 
1563
1599
  this.tabsContainer.style.display = hasTabs ? 'flex' : 'none';
1564
1600
  this.pane.style.display = shouldShow ? 'flex' : 'none';
@@ -1644,8 +1680,7 @@ class EditorManager {
1644
1680
  const state = session.editorState;
1645
1681
 
1646
1682
  // Only render tabs and content, file tree is persistent in sidebar
1647
- const shouldShowWorkspace = state.isVisible
1648
- || this.hasVisibleWorkspaceTabs(session);
1683
+ const shouldShowWorkspace = this.hasVisibleWorkspaceTabs(session);
1649
1684
  if (shouldShowWorkspace) {
1650
1685
  if (state.isVisible) {
1651
1686
  this.refreshSessionTree(session);
@@ -1654,8 +1689,6 @@ class EditorManager {
1654
1689
  const activeKey = this.getActiveWorkspaceTabKey(session);
1655
1690
  if (activeKey) {
1656
1691
  this.activateWorkspaceTab(activeKey, true);
1657
- } else {
1658
- this.showEmptyState();
1659
1692
  }
1660
1693
  }
1661
1694
 
@@ -3155,6 +3188,17 @@ class EditorManager {
3155
3188
  const attachments = Array.isArray(agentTab.pendingAttachments)
3156
3189
  ? [...agentTab.pendingAttachments]
3157
3190
  : [];
3191
+ const promptIntent = getAgentPromptIntent(
3192
+ agentTab,
3193
+ this.agentPrompt.value || ''
3194
+ );
3195
+ if (promptIntent.kind === 'resume') {
3196
+ alert('Select a previous session from the /resume menu.', {
3197
+ type: 'warning',
3198
+ title: getAgentBaseName(agentTab)
3199
+ });
3200
+ return;
3201
+ }
3158
3202
  if (!text && attachments.length === 0) {
3159
3203
  if (canAutostartQueuedAgentPrompt(agentTab)) {
3160
3204
  await drainQueuedAgentPrompt(agentTab);
@@ -3329,7 +3373,21 @@ class EditorManager {
3329
3373
  if (this.suppressAgentCommandMenu) {
3330
3374
  this.hideAgentCommandMenu();
3331
3375
  } else {
3332
- this.renderAgentCommandMenu(activeAgentTab);
3376
+ const promptValue = this.agentPrompt?.value || '';
3377
+ const promptIntent = getAgentPromptIntent(
3378
+ activeAgentTab,
3379
+ promptValue
3380
+ );
3381
+ const nextMenuStateKey = [
3382
+ activeAgentTab?.key || '',
3383
+ promptIntent.kind,
3384
+ promptValue
3385
+ ].join('::');
3386
+ const menuVisible = this.agentCommandMenu
3387
+ && this.agentCommandMenu.style.display !== 'none';
3388
+ if (!menuVisible || this.agentCommandMenuStateKey !== nextMenuStateKey) {
3389
+ this.renderAgentCommandMenu(activeAgentTab);
3390
+ }
3333
3391
  }
3334
3392
  if (
3335
3393
  activeAgentTab
@@ -3500,12 +3558,7 @@ class EditorManager {
3500
3558
  this.agentScrollBottomButton.style.display = shouldShow ? '' : 'none';
3501
3559
  }
3502
3560
 
3503
- renderAgentCommandMenu(agentTab = null) {
3504
- if (!this.agentCommandMenu) return;
3505
- const suggestions = getAgentCommandSuggestions(
3506
- agentTab,
3507
- this.agentPrompt?.value || ''
3508
- );
3561
+ #renderAgentCommandSuggestions(suggestions) {
3509
3562
  this.agentCommandSuggestions = suggestions;
3510
3563
  if (suggestions.length === 0) {
3511
3564
  this.hideAgentCommandMenu();
@@ -3527,7 +3580,11 @@ class EditorManager {
3527
3580
  }
3528
3581
  const name = document.createElement('span');
3529
3582
  name.className = 'agent-command-option-name';
3530
- name.textContent = `/${command.name}`;
3583
+ name.textContent = command.kind === 'resume_session'
3584
+ ? command.displayName || command.title || command.sessionId
3585
+ : command.kind === 'info'
3586
+ ? command.label || ''
3587
+ : `/${command.name}`;
3531
3588
  button.appendChild(name);
3532
3589
  if (command.description) {
3533
3590
  const meta = document.createElement('span');
@@ -3535,12 +3592,16 @@ class EditorManager {
3535
3592
  meta.textContent = command.description;
3536
3593
  button.appendChild(meta);
3537
3594
  }
3595
+ if (command.kind === 'info') {
3596
+ button.disabled = true;
3597
+ }
3538
3598
  button.addEventListener('mousedown', (event) => {
3539
3599
  event.preventDefault();
3540
3600
  });
3541
3601
  button.addEventListener('click', () => {
3602
+ if (command.kind === 'info') return;
3542
3603
  this.agentCommandIndex = index;
3543
- this.applyAgentCommandSuggestion();
3604
+ void this.applyAgentCommandSuggestion();
3544
3605
  });
3545
3606
  this.agentCommandMenu.appendChild(button);
3546
3607
  }
@@ -3554,10 +3615,95 @@ class EditorManager {
3554
3615
  }
3555
3616
  }
3556
3617
 
3618
+ async renderAgentCommandMenu(agentTab = null) {
3619
+ if (!this.agentCommandMenu) return;
3620
+ const promptValue = this.agentPrompt?.value || '';
3621
+ const token = this.agentCommandMenuToken + 1;
3622
+ this.agentCommandMenuToken = token;
3623
+ const intent = getAgentPromptIntent(agentTab, promptValue);
3624
+ const menuStateKey = [
3625
+ agentTab?.key || '',
3626
+ intent.kind,
3627
+ promptValue
3628
+ ].join('::');
3629
+
3630
+ if (!agentTab || intent.kind === 'none' || intent.kind === 'other') {
3631
+ this.hideAgentCommandMenu();
3632
+ return;
3633
+ }
3634
+
3635
+ if (Number.isInteger(agentTab.promptHistoryIndex)) {
3636
+ this.exitAgentPromptHistoryBrowsing(agentTab);
3637
+ }
3638
+
3639
+ if (intent.kind === 'resume') {
3640
+ const hasLoadedResumeSuggestions = (
3641
+ this.agentCommandMenuStateKey === menuStateKey
3642
+ && this.agentCommandSuggestions.length > 0
3643
+ && !(
3644
+ this.agentCommandSuggestions.length === 1
3645
+ && this.agentCommandSuggestions[0]?.kind === 'info'
3646
+ && /loading previous sessions/i.test(
3647
+ this.agentCommandSuggestions[0]?.label || ''
3648
+ )
3649
+ )
3650
+ );
3651
+ if (hasLoadedResumeSuggestions) {
3652
+ this.#renderAgentCommandSuggestions(
3653
+ this.agentCommandSuggestions
3654
+ );
3655
+ return;
3656
+ }
3657
+ this.agentCommandMenuStateKey = menuStateKey;
3658
+ this.#renderAgentCommandSuggestions([{
3659
+ kind: 'info',
3660
+ label: 'Loading previous sessions…',
3661
+ description: ''
3662
+ }]);
3663
+ try {
3664
+ const sessions = await agentTab.listResumeSessions();
3665
+ if (this.agentCommandMenuToken !== token) {
3666
+ return;
3667
+ }
3668
+ const suggestions = getAgentResumeSuggestions(
3669
+ agentTab,
3670
+ promptValue,
3671
+ sessions
3672
+ );
3673
+ if (suggestions.length === 0) {
3674
+ this.#renderAgentCommandSuggestions([{
3675
+ kind: 'info',
3676
+ label: 'No previous sessions found',
3677
+ description: ''
3678
+ }]);
3679
+ return;
3680
+ }
3681
+ this.#renderAgentCommandSuggestions(suggestions);
3682
+ } catch (error) {
3683
+ if (this.agentCommandMenuToken !== token) {
3684
+ return;
3685
+ }
3686
+ this.#renderAgentCommandSuggestions([{
3687
+ kind: 'info',
3688
+ label: 'Unable to load previous sessions',
3689
+ description: error?.message || ''
3690
+ }]);
3691
+ }
3692
+ return;
3693
+ }
3694
+
3695
+ this.agentCommandMenuStateKey = menuStateKey;
3696
+ this.#renderAgentCommandSuggestions(
3697
+ getAgentCommandSuggestions(agentTab, promptValue)
3698
+ );
3699
+ }
3700
+
3557
3701
  hideAgentCommandMenu() {
3558
3702
  if (!this.agentCommandMenu) return;
3703
+ this.agentCommandMenuToken += 1;
3559
3704
  this.agentCommandSuggestions = [];
3560
3705
  this.agentCommandIndex = 0;
3706
+ this.agentCommandMenuStateKey = '';
3561
3707
  this.agentCommandMenu.style.display = 'none';
3562
3708
  this.agentCommandMenu.innerHTML = '';
3563
3709
  }
@@ -3568,7 +3714,7 @@ class EditorManager {
3568
3714
  this.agentCommandIndex = nextIndex < 0
3569
3715
  ? this.agentCommandSuggestions.length - 1
3570
3716
  : nextIndex % this.agentCommandSuggestions.length;
3571
- this.renderAgentCommandMenu(getActiveAgentTab());
3717
+ this.#renderAgentCommandSuggestions(this.agentCommandSuggestions);
3572
3718
  }
3573
3719
 
3574
3720
  setAgentPromptValue(value, agentTab = null, options = {}) {
@@ -3739,9 +3885,40 @@ class EditorManager {
3739
3885
  agentTab.promptDraft = this.agentPrompt?.value || '';
3740
3886
  }
3741
3887
 
3742
- applyAgentCommandSuggestion() {
3888
+ async applyAgentCommandSuggestion() {
3743
3889
  const command = this.agentCommandSuggestions[this.agentCommandIndex];
3744
3890
  if (!command) return;
3891
+ if (command.kind === 'info') {
3892
+ return;
3893
+ }
3894
+ if (command.kind === 'resume_session') {
3895
+ const agentTab = getActiveAgentTab();
3896
+ if (!agentTab) return;
3897
+ const session = agentTab.getLinkedSession();
3898
+ if (!session) return;
3899
+ try {
3900
+ if (command.openTabKey) {
3901
+ const existingTab = state.agentTabs.get(command.openTabKey);
3902
+ const existingSession = existingTab?.getLinkedSession() || null;
3903
+ if (existingTab && existingSession) {
3904
+ await activateAgentTab(existingSession, existingTab, {
3905
+ switchSession: true
3906
+ });
3907
+ } else {
3908
+ await resumeAgentTabFromHistory(session, agentTab, command);
3909
+ }
3910
+ } else {
3911
+ await resumeAgentTabFromHistory(session, agentTab, command);
3912
+ }
3913
+ this.hideAgentCommandMenu();
3914
+ } catch (error) {
3915
+ alert(error.message, {
3916
+ type: 'error',
3917
+ title: getAgentBaseName(agentTab)
3918
+ });
3919
+ }
3920
+ return;
3921
+ }
3745
3922
  const suffix = command.inputHint
3746
3923
  ? ` ${command.inputHint}`
3747
3924
  : ' ';
@@ -5205,6 +5382,9 @@ class AgentTab {
5205
5382
  this.scrollToBottomOnNextRender = true;
5206
5383
  this.busySyncTimer = null;
5207
5384
  this.planHistory = [];
5385
+ this.resumeSessions = [];
5386
+ this.resumeSessionsLoadedAt = 0;
5387
+ this.resumeSessionsPromise = null;
5208
5388
  this.update(data);
5209
5389
  this.connect();
5210
5390
  }
@@ -5224,6 +5404,7 @@ class AgentTab {
5224
5404
  }
5225
5405
 
5226
5406
  update(data) {
5407
+ const previousResumeCacheKey = `${this.agentId || ''}:${this.cwd || ''}`;
5227
5408
  this.runtimeId = data.runtimeId || '';
5228
5409
  this.runtimeKey = data.runtimeKey || '';
5229
5410
  this.acpSessionId = data.acpSessionId || '';
@@ -5244,9 +5425,17 @@ class AgentTab {
5244
5425
  this.availableCommands = Array.isArray(data.availableCommands)
5245
5426
  ? data.availableCommands
5246
5427
  : [];
5428
+ this.sessionCapabilities = normalizeAgentSessionCapabilities(
5429
+ data.sessionCapabilities || this.sessionCapabilities
5430
+ );
5247
5431
  this.configOptions = Array.isArray(data.configOptions)
5248
5432
  ? data.configOptions
5249
5433
  : [];
5434
+ const nextResumeCacheKey = `${this.agentId || ''}:${this.cwd || ''}`;
5435
+ if (previousResumeCacheKey !== nextResumeCacheKey) {
5436
+ this.resumeSessions = [];
5437
+ this.resumeSessionsLoadedAt = 0;
5438
+ }
5250
5439
  const nextPlan = Array.isArray(data.plan)
5251
5440
  ? data.plan.map((entry) => this.#normalizePlanEntry(entry))
5252
5441
  : [];
@@ -5314,6 +5503,49 @@ class AgentTab {
5314
5503
  this.#syncBusyWatchdog();
5315
5504
  }
5316
5505
 
5506
+ async listResumeSessions({ force = false } = {}) {
5507
+ if (!supportsAgentResumeCommand(this)) {
5508
+ return [];
5509
+ }
5510
+ if (
5511
+ !force
5512
+ && this.resumeSessionsLoadedAt > 0
5513
+ && (Date.now() - this.resumeSessionsLoadedAt) < 30 * 1000
5514
+ ) {
5515
+ return this.resumeSessions;
5516
+ }
5517
+ if (this.resumeSessionsPromise) {
5518
+ return this.resumeSessionsPromise;
5519
+ }
5520
+
5521
+ this.resumeSessionsPromise = (async () => {
5522
+ const cwd = this.cwd || this.getLinkedSession()?.cwd || '';
5523
+ const params = new URLSearchParams({
5524
+ agentId: this.agentId,
5525
+ cwd
5526
+ });
5527
+ const response = await this.server.fetch(
5528
+ `/api/agents/sessions?${params.toString()}`
5529
+ );
5530
+ if (!response.ok) {
5531
+ await throwResponseError(
5532
+ response,
5533
+ 'Failed to load previous sessions'
5534
+ );
5535
+ }
5536
+ const data = await response.json();
5537
+ this.resumeSessions = normalizeListedAgentSessions(data.sessions);
5538
+ this.resumeSessionsLoadedAt = Date.now();
5539
+ return this.resumeSessions;
5540
+ })();
5541
+
5542
+ try {
5543
+ return await this.resumeSessionsPromise;
5544
+ } finally {
5545
+ this.resumeSessionsPromise = null;
5546
+ }
5547
+ }
5548
+
5317
5549
  connect() {
5318
5550
  if (!this.server.isAuthenticated) return;
5319
5551
  if (
@@ -5887,6 +6119,23 @@ class AgentTab {
5887
6119
 
5888
6120
  applyInventory(data) {
5889
6121
  const previousSession = this.getLinkedSession();
6122
+ const previousSnapshot = JSON.stringify({
6123
+ runtimeId: this.runtimeId || '',
6124
+ runtimeKey: this.runtimeKey || '',
6125
+ acpSessionId: this.acpSessionId || '',
6126
+ agentId: this.agentId || '',
6127
+ agentLabel: this.agentLabel || '',
6128
+ title: this.title || '',
6129
+ commandLabel: this.commandLabel || '',
6130
+ terminalSessionId: this.terminalSessionId || '',
6131
+ cwd: this.cwd || '',
6132
+ createdAt: this.createdAt || '',
6133
+ status: this.status || 'ready',
6134
+ busy: !!this.busy,
6135
+ errorMessage: this.errorMessage || '',
6136
+ currentModeId: this.currentModeId || '',
6137
+ sessionCapabilities: this.sessionCapabilities || null
6138
+ });
5890
6139
  this.runtimeId = data.runtimeId || this.runtimeId || '';
5891
6140
  this.runtimeKey = data.runtimeKey || this.runtimeKey || '';
5892
6141
  this.acpSessionId = data.acpSessionId || this.acpSessionId || '';
@@ -5901,7 +6150,32 @@ class AgentTab {
5901
6150
  this.busy = typeof data.busy === 'boolean' ? data.busy : this.busy;
5902
6151
  this.errorMessage = data.errorMessage || this.errorMessage || '';
5903
6152
  this.currentModeId = data.currentModeId || this.currentModeId || '';
6153
+ this.sessionCapabilities = normalizeAgentSessionCapabilities(
6154
+ data.sessionCapabilities || this.sessionCapabilities
6155
+ );
5904
6156
  const nextSession = this.getLinkedSession();
6157
+ const nextSnapshot = JSON.stringify({
6158
+ runtimeId: this.runtimeId || '',
6159
+ runtimeKey: this.runtimeKey || '',
6160
+ acpSessionId: this.acpSessionId || '',
6161
+ agentId: this.agentId || '',
6162
+ agentLabel: this.agentLabel || '',
6163
+ title: this.title || '',
6164
+ commandLabel: this.commandLabel || '',
6165
+ terminalSessionId: this.terminalSessionId || '',
6166
+ cwd: this.cwd || '',
6167
+ createdAt: this.createdAt || '',
6168
+ status: this.status || 'ready',
6169
+ busy: !!this.busy,
6170
+ errorMessage: this.errorMessage || '',
6171
+ currentModeId: this.currentModeId || '',
6172
+ sessionCapabilities: this.sessionCapabilities || null
6173
+ });
6174
+ const changed = previousSnapshot !== nextSnapshot
6175
+ || previousSession?.key !== nextSession?.key;
6176
+ if (!changed) {
6177
+ return false;
6178
+ }
5905
6179
  previousSession?.updateTabUI();
5906
6180
  if (nextSession && nextSession !== previousSession) {
5907
6181
  nextSession.updateTabUI();
@@ -5909,6 +6183,7 @@ class AgentTab {
5909
6183
  if (nextSession) {
5910
6184
  refreshWorkspaceIfSessionActive(nextSession);
5911
6185
  }
6186
+ return true;
5912
6187
  }
5913
6188
 
5914
6189
  async #waitForSettled(timeoutMs = 5000) {
@@ -6455,6 +6730,27 @@ function normalizeAgentConfigOptionOptions(options) {
6455
6730
  return flattened;
6456
6731
  }
6457
6732
 
6733
+ function normalizeAgentSessionCapabilities(sessionCapabilities) {
6734
+ const source = (
6735
+ sessionCapabilities && typeof sessionCapabilities === 'object'
6736
+ )
6737
+ ? sessionCapabilities
6738
+ : {};
6739
+ return {
6740
+ load: !!source.load,
6741
+ list: !!source.list,
6742
+ resume: !!source.resume,
6743
+ fork: !!source.fork
6744
+ };
6745
+ }
6746
+
6747
+ function supportsAgentResumeCommand(agentTab) {
6748
+ const capabilities = normalizeAgentSessionCapabilities(
6749
+ agentTab?.sessionCapabilities
6750
+ );
6751
+ return !!(capabilities.load && capabilities.list);
6752
+ }
6753
+
6458
6754
  function getAgentConfigOptionById(agentTab, configId) {
6459
6755
  return normalizeAgentConfigOptions(agentTab?.configOptions).find(
6460
6756
  (option) => option.id === configId
@@ -6555,6 +6851,7 @@ function normalizeAgentCommands(commands) {
6555
6851
  : '';
6556
6852
  if (!name) return null;
6557
6853
  return {
6854
+ kind: 'command',
6558
6855
  name,
6559
6856
  description: command?.description || '',
6560
6857
  inputHint: command?.input?.hint || ''
@@ -6563,6 +6860,107 @@ function normalizeAgentCommands(commands) {
6563
6860
  .filter(Boolean);
6564
6861
  }
6565
6862
 
6863
+ function normalizeListedAgentSessions(sessions) {
6864
+ if (!Array.isArray(sessions)) return [];
6865
+ const normalized = sessions
6866
+ .map((session, index) => {
6867
+ const sessionId = String(session?.sessionId || '').trim();
6868
+ const cwd = String(session?.cwd || '').trim();
6869
+ if (!sessionId || !cwd) return null;
6870
+ return {
6871
+ kind: 'resume_session',
6872
+ sortIndex: index,
6873
+ sessionId,
6874
+ cwd,
6875
+ title: typeof session?.title === 'string'
6876
+ ? session.title
6877
+ : '',
6878
+ updatedAt: typeof session?.updatedAt === 'string'
6879
+ ? session.updatedAt
6880
+ : '',
6881
+ relativeUpdatedAt: typeof session?.relativeUpdatedAt === 'string'
6882
+ ? session.relativeUpdatedAt
6883
+ : ''
6884
+ };
6885
+ })
6886
+ .filter(Boolean);
6887
+ normalized.sort((left, right) => {
6888
+ const leftTime = Date.parse(left.updatedAt || '') || 0;
6889
+ const rightTime = Date.parse(right.updatedAt || '') || 0;
6890
+ if (leftTime !== rightTime) {
6891
+ return rightTime - leftTime;
6892
+ }
6893
+ return left.sortIndex - right.sortIndex;
6894
+ });
6895
+ return normalized;
6896
+ }
6897
+
6898
+ function getOpenAgentSessionsForServer(serverId, agentId = '') {
6899
+ const entries = Array.from(state.agentTabs.values())
6900
+ .filter((tab) => (
6901
+ tab.serverId === serverId
6902
+ && (!agentId || tab.agentId === agentId)
6903
+ ))
6904
+ .map((tab) => [String(tab.acpSessionId || '').trim(), tab])
6905
+ .filter(([sessionId]) => !!sessionId);
6906
+ return new Map(entries);
6907
+ }
6908
+
6909
+ function buildAgentResumeSessionMeta(sessionInfo) {
6910
+ const parts = [];
6911
+ const relativeUpdatedAt = String(
6912
+ sessionInfo?.relativeUpdatedAt || ''
6913
+ ).trim();
6914
+ if (relativeUpdatedAt) {
6915
+ parts.push(relativeUpdatedAt);
6916
+ }
6917
+ const timeLabel = getAgentMessageTimeLabel({
6918
+ createdAt: sessionInfo?.updatedAt || ''
6919
+ });
6920
+ if (timeLabel && !relativeUpdatedAt) {
6921
+ parts.push(timeLabel);
6922
+ }
6923
+ const cwd = String(sessionInfo?.cwd || '').trim();
6924
+ if (cwd) {
6925
+ parts.push(shortenPath(cwd, 48));
6926
+ }
6927
+ return parts.join(' · ');
6928
+ }
6929
+
6930
+ function getAgentPromptIntent(agentTab, promptValue) {
6931
+ const source = String(promptValue || '').replace(/^\s+/, '');
6932
+ const firstLine = source.split('\n', 1)[0] || '';
6933
+ if (!firstLine.startsWith('/')) {
6934
+ return { kind: 'none', query: '', commandName: '' };
6935
+ }
6936
+ const body = firstLine.slice(1);
6937
+ const [commandNameRaw = '', ...restParts] = body.split(/\s+/);
6938
+ const commandName = commandNameRaw.toLowerCase();
6939
+ const query = restParts.join(' ').trim();
6940
+ if (!commandName) {
6941
+ return { kind: 'commands', query: '', commandName: '' };
6942
+ }
6943
+ if (commandName === 'resume' && supportsAgentResumeCommand(agentTab)) {
6944
+ return {
6945
+ kind: 'resume',
6946
+ query,
6947
+ commandName
6948
+ };
6949
+ }
6950
+ if (!/\s/.test(body)) {
6951
+ return {
6952
+ kind: 'commands',
6953
+ query: commandName,
6954
+ commandName
6955
+ };
6956
+ }
6957
+ return {
6958
+ kind: 'other',
6959
+ query,
6960
+ commandName
6961
+ };
6962
+ }
6963
+
6566
6964
  function bindSingleTapActivation(element, onActivate, options = {}) {
6567
6965
  if (!element || typeof onActivate !== 'function') {
6568
6966
  return;
@@ -6724,17 +7122,19 @@ function selectAgentMessageText(previousText, nextText) {
6724
7122
  }
6725
7123
 
6726
7124
  function getAgentCommandSuggestions(agentTab, promptValue) {
6727
- if (!agentTab?.availableCommands) return [];
6728
- const source = String(promptValue || '');
6729
- const trimmed = source.replace(/^\s+/, '');
6730
- const firstLine = trimmed.split('\n', 1)[0] || '';
6731
- if (!firstLine.startsWith('/')) return [];
6732
-
6733
- const commandToken = firstLine.slice(1);
6734
- if (/\s/.test(commandToken)) return [];
6735
-
6736
- const query = commandToken.toLowerCase();
6737
- const commands = normalizeAgentCommands(agentTab.availableCommands);
7125
+ const intent = getAgentPromptIntent(agentTab, promptValue);
7126
+ if (intent.kind !== 'commands') return [];
7127
+
7128
+ const commands = normalizeAgentCommands(agentTab?.availableCommands);
7129
+ if (supportsAgentResumeCommand(agentTab)) {
7130
+ commands.unshift({
7131
+ kind: 'command',
7132
+ name: 'resume',
7133
+ description: 'Continue from a previous session',
7134
+ inputHint: ''
7135
+ });
7136
+ }
7137
+ const query = String(intent.query || '').toLowerCase();
6738
7138
  const ranked = commands.filter((command) => {
6739
7139
  const name = command.name.toLowerCase();
6740
7140
  return !query || name.startsWith(query) || name.includes(query);
@@ -6752,6 +7152,52 @@ function getAgentCommandSuggestions(agentTab, promptValue) {
6752
7152
  return ranked.slice(0, 8);
6753
7153
  }
6754
7154
 
7155
+ function getAgentResumeSuggestions(agentTab, promptValue, sessions = []) {
7156
+ const intent = getAgentPromptIntent(agentTab, promptValue);
7157
+ if (intent.kind !== 'resume') return [];
7158
+ const query = String(intent.query || '').toLowerCase();
7159
+ const openSessions = getOpenAgentSessionsForServer(
7160
+ agentTab?.serverId,
7161
+ agentTab?.agentId
7162
+ );
7163
+ const currentSessionId = String(agentTab?.acpSessionId || '').trim();
7164
+ return normalizeListedAgentSessions(sessions)
7165
+ .filter((session) => session.sessionId !== currentSessionId)
7166
+ .map((session, index) => {
7167
+ const displayName = String(
7168
+ session.title || shortenPath(session.cwd, 36)
7169
+ ).toLowerCase();
7170
+ const cwd = String(session.cwd || '').toLowerCase();
7171
+ const sessionId = String(session.sessionId || '').toLowerCase();
7172
+ const titleMatch = !query || displayName.includes(query);
7173
+ const otherMatch = !query || cwd.includes(query) || sessionId.includes(query);
7174
+ return {
7175
+ session,
7176
+ index,
7177
+ titleMatch,
7178
+ matched: titleMatch || otherMatch
7179
+ };
7180
+ })
7181
+ .filter(({ matched }) => matched)
7182
+ .sort((left, right) => {
7183
+ if (left.titleMatch !== right.titleMatch) {
7184
+ return left.titleMatch ? -1 : 1;
7185
+ }
7186
+ return left.index - right.index;
7187
+ })
7188
+ .map(({ session }) => session)
7189
+ .slice(0, 12)
7190
+ .map((session) => ({
7191
+ ...session,
7192
+ openTabKey: openSessions.get(session.sessionId)?.key || '',
7193
+ displayName: session.title || shortenPath(session.cwd, 36),
7194
+ description: [
7195
+ buildAgentResumeSessionMeta(session) || session.sessionId,
7196
+ openSessions.has(session.sessionId) ? 'Already open' : ''
7197
+ ].filter(Boolean).join(' · ')
7198
+ }));
7199
+ }
7200
+
6755
7201
  function getCurrentAgentModeLabel(agentTab) {
6756
7202
  const currentModeId = agentTab?.currentModeId || '';
6757
7203
  if (!currentModeId) return '';
@@ -9100,13 +9546,19 @@ function upsertAgentInventoryTab(server, data) {
9100
9546
  const key = makeAgentTabKey(server.id, data.id);
9101
9547
  const existing = state.agentTabs.get(key);
9102
9548
  if (existing) {
9103
- existing.applyInventory(data);
9549
+ const changed = existing.applyInventory(data);
9104
9550
  existing.connect();
9105
- return existing;
9551
+ return {
9552
+ agentTab: existing,
9553
+ changed
9554
+ };
9106
9555
  }
9107
9556
  const agentTab = new AgentTab(data, server);
9108
9557
  state.agentTabs.set(key, agentTab);
9109
- return agentTab;
9558
+ return {
9559
+ agentTab,
9560
+ changed: true
9561
+ };
9110
9562
  }
9111
9563
 
9112
9564
  function reconcileAgentInventory(server, inventory) {
@@ -9118,8 +9570,11 @@ function reconcileAgentInventory(server, inventory) {
9118
9570
  const touchedSessions = new Set();
9119
9571
 
9120
9572
  for (const tabData of Array.isArray(inventory.tabs) ? inventory.tabs : []) {
9121
- const agentTab = upsertAgentInventoryTab(server, tabData);
9573
+ const { agentTab, changed } = upsertAgentInventoryTab(server, tabData);
9122
9574
  seenKeys.add(agentTab.key);
9575
+ if (!changed) {
9576
+ continue;
9577
+ }
9123
9578
  const session = agentTab.getLinkedSession();
9124
9579
  if (session) {
9125
9580
  touchedSessions.add(session.key);
@@ -9331,16 +9786,23 @@ async function createAgentTab(session, agentId, options = {}) {
9331
9786
  await throwResponseError(response, 'Failed to create agent tab');
9332
9787
  }
9333
9788
  const data = await response.json();
9334
- const agentTab = upsertAgentTab(session.server, data);
9789
+ return await activateAgentTab(
9790
+ session,
9791
+ upsertAgentTab(session.server, data)
9792
+ );
9793
+ }
9794
+
9795
+ async function activateAgentTab(session, agentTab, options = {}) {
9796
+ if (!session || !agentTab) return null;
9797
+ const shouldSwitchSession = !!options.switchSession;
9798
+ if (shouldSwitchSession && state.activeSessionKey !== session.key) {
9799
+ await switchToSession(session.key, { scrollTabIntoView: true });
9800
+ }
9335
9801
  session.workspaceState.activeTabKey = agentTab.key;
9336
9802
  noteRecentAgentTab(session, agentTab.key);
9337
9803
  session.saveState();
9338
9804
  if (state.activeSessionKey === session.key) {
9339
- if (editorManager.currentSession?.key !== session.key) {
9340
- editorManager.switchTo(session);
9341
- }
9342
- editorManager.activateAgentTab(agentTab.key);
9343
- editorManager.updateEditorPaneVisibility();
9805
+ restoreWorkspaceForSession(session);
9344
9806
  requestAnimationFrame(() => {
9345
9807
  editorManager.agentPrompt?.focus();
9346
9808
  });
@@ -9350,6 +9812,29 @@ async function createAgentTab(session, agentId, options = {}) {
9350
9812
  return agentTab;
9351
9813
  }
9352
9814
 
9815
+ async function resumeAgentTabFromHistory(session, agentTab, historySession) {
9816
+ if (!session || !agentTab || !historySession?.sessionId) return null;
9817
+ const response = await session.server.fetch('/api/agents/tabs/resume', {
9818
+ method: 'POST',
9819
+ headers: { 'Content-Type': 'application/json' },
9820
+ body: JSON.stringify({
9821
+ agentId: agentTab.agentId,
9822
+ cwd: agentTab.cwd || session.cwd || session.initialCwd || '/',
9823
+ terminalSessionId: session.id,
9824
+ sessionId: historySession.sessionId,
9825
+ title: historySession.title || ''
9826
+ })
9827
+ });
9828
+ if (!response.ok) {
9829
+ await throwResponseError(response, 'Failed to resume agent session');
9830
+ }
9831
+ const data = await response.json();
9832
+ return await activateAgentTab(
9833
+ session,
9834
+ upsertAgentTab(session.server, data)
9835
+ );
9836
+ }
9837
+
9353
9838
  function getServerEndpointKey(server) {
9354
9839
  if (!server) return '';
9355
9840
  return getServerEndpointKeyFromUrl(server.baseUrl);
@@ -11686,6 +12171,52 @@ if (shortcutsModal) {
11686
12171
  });
11687
12172
  }
11688
12173
 
12174
+ function handleAgentCommandMenuShortcut(event) {
12175
+ const agentCommandMenuOpen = !!(
12176
+ editorManager?.agentCommandMenu
12177
+ && editorManager.agentCommandMenu.style.display !== 'none'
12178
+ && editorManager.agentCommandSuggestions.length > 0
12179
+ );
12180
+ const eventFromAgentPrompt = editorManager?.agentPrompt
12181
+ && event.target === editorManager.agentPrompt;
12182
+ if (
12183
+ !agentCommandMenuOpen
12184
+ || eventFromAgentPrompt
12185
+ || event.ctrlKey
12186
+ || event.metaKey
12187
+ || event.altKey
12188
+ ) {
12189
+ return false;
12190
+ }
12191
+ if (event.key === 'Escape') {
12192
+ event.preventDefault();
12193
+ editorManager.hideAgentCommandMenu();
12194
+ return true;
12195
+ }
12196
+ if (event.key === 'ArrowDown') {
12197
+ event.preventDefault();
12198
+ editorManager.moveAgentCommandSelection(1);
12199
+ return true;
12200
+ }
12201
+ if (event.key === 'ArrowUp') {
12202
+ event.preventDefault();
12203
+ editorManager.moveAgentCommandSelection(-1);
12204
+ return true;
12205
+ }
12206
+ if (event.key === 'Tab' || event.key === 'Enter') {
12207
+ event.preventDefault();
12208
+ void editorManager.applyAgentCommandSuggestion();
12209
+ return true;
12210
+ }
12211
+ return false;
12212
+ }
12213
+
12214
+ document.addEventListener('keydown', (e) => {
12215
+ if (handleAgentCommandMenuShortcut(e)) {
12216
+ e.stopImmediatePropagation();
12217
+ }
12218
+ }, true);
12219
+
11689
12220
  // Keyboard Shortcuts
11690
12221
  document.addEventListener('keydown', (e) => {
11691
12222
  if (