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/package.json +1 -1
- package/public/app.js +662 -44
- package/public/styles.css +1 -1
- package/src/acp-manager.mjs +422 -80
- package/src/server.mjs +87 -0
- package/src/terminal-session.mjs +102 -2
package/public/app.js
CHANGED
|
@@ -108,7 +108,7 @@ const CLOSE_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" stroke="
|
|
|
108
108
|
const AGENT_ICON_SVG = '<svg viewBox="0 0 24 24" width="17" height="17" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="7" width="10" height="10" rx="2"></rect><path d="M9 7V5"></path><path d="M15 7V5"></path><path d="M12 17v2"></path><path d="M5 12H3"></path><path d="M21 12h-2"></path><path d="M9 11h.01"></path><path d="M15 11h.01"></path><path d="M9.5 14c.7.67 1.53 1 2.5 1s1.8-.33 2.5-1"></path></svg>';
|
|
109
109
|
const TERMINAL_TAB_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="m8 10 3 2-3 2"></path><path d="M13 15h4"></path></svg>';
|
|
110
110
|
const MANAGED_TERMINAL_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="M7 12h.01"></path><path d="M12 9v6"></path><path d="M9 12h6"></path><path d="M18 8v2"></path><path d="M19 9h-2"></path></svg>';
|
|
111
|
-
const BELL_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1
|
|
111
|
+
const BELL_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2.1" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4.5a4.5 4.5 0 0 0-4.5 4.5v2.4c0 1.2-.41 2.37-1.17 3.3L5 16.5h14l-1.33-1.8a5.66 5.66 0 0 1-1.17-3.3V9A4.5 4.5 0 0 0 12 4.5"></path><path d="M10.25 19a1.75 1.75 0 0 0 3.5 0"></path></svg>';
|
|
112
112
|
const SPINNER_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"><path d="M12 3a9 9 0 1 0 9 9"></path></svg>';
|
|
113
113
|
const ATTACH_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.9" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 1 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.82-2.82l8.49-8.49"></path></svg>';
|
|
114
114
|
const CHEVRON_DOWN_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg>';
|
|
@@ -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 (
|
|
399
|
+
if (persisted && needsShellReload && !runtimeReloadScheduled) {
|
|
393
400
|
runtimeReloadScheduled = true;
|
|
394
|
-
console.info(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
: ' ';
|
|
@@ -4464,6 +4641,10 @@ class Session {
|
|
|
4464
4641
|
editorFlex: '2 1 0%'
|
|
4465
4642
|
};
|
|
4466
4643
|
this.previewRelayoutScheduled = false;
|
|
4644
|
+
this.lastTerminalControlClaimAt = 0;
|
|
4645
|
+
this.boundTerminalClaimRoot = null;
|
|
4646
|
+
this.boundTerminalClaimTextarea = null;
|
|
4647
|
+
this.boundTerminalClaimHandler = null;
|
|
4467
4648
|
this.wrapperElement = null;
|
|
4468
4649
|
this._createTerminals();
|
|
4469
4650
|
|
|
@@ -4529,6 +4710,8 @@ class Session {
|
|
|
4529
4710
|
const wasActive = state.activeSessionKey === this.key;
|
|
4530
4711
|
const previewWrapper = this.wrapperElement;
|
|
4531
4712
|
|
|
4713
|
+
this.unbindTerminalControlClaim();
|
|
4714
|
+
|
|
4532
4715
|
try {
|
|
4533
4716
|
this.previewTerm?.dispose();
|
|
4534
4717
|
} catch (e) {
|
|
@@ -4556,6 +4739,7 @@ class Session {
|
|
|
4556
4739
|
if (wasActive && terminalEl) {
|
|
4557
4740
|
terminalEl.innerHTML = '';
|
|
4558
4741
|
this.mainTerm.open(terminalEl);
|
|
4742
|
+
this.bindTerminalControlClaim();
|
|
4559
4743
|
if (this.fitMainTerminalIfVisible()) {
|
|
4560
4744
|
this.mainTerm.focus();
|
|
4561
4745
|
}
|
|
@@ -5067,6 +5251,79 @@ class Session {
|
|
|
5067
5251
|
}
|
|
5068
5252
|
}
|
|
5069
5253
|
|
|
5254
|
+
claimTerminalControl(force = false) {
|
|
5255
|
+
if (state.activeSessionKey !== this.key) {
|
|
5256
|
+
return;
|
|
5257
|
+
}
|
|
5258
|
+
if (this.socket?.readyState !== WebSocket.OPEN) {
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
const now = Date.now();
|
|
5263
|
+
if (!force && now - this.lastTerminalControlClaimAt < 250) {
|
|
5264
|
+
return;
|
|
5265
|
+
}
|
|
5266
|
+
|
|
5267
|
+
this.lastTerminalControlClaimAt = now;
|
|
5268
|
+
this.send({ type: 'claim_terminal_control' });
|
|
5269
|
+
}
|
|
5270
|
+
|
|
5271
|
+
bindTerminalControlClaim() {
|
|
5272
|
+
this.unbindTerminalControlClaim();
|
|
5273
|
+
|
|
5274
|
+
const root = this.mainTerm?.element;
|
|
5275
|
+
if (!root) {
|
|
5276
|
+
return;
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
const textarea = this.mainTerm.textarea
|
|
5280
|
+
|| root.querySelector('textarea');
|
|
5281
|
+
const handler = () => this.claimTerminalControl();
|
|
5282
|
+
|
|
5283
|
+
root.addEventListener('mousedown', handler, true);
|
|
5284
|
+
root.addEventListener('touchstart', handler, true);
|
|
5285
|
+
if (textarea) {
|
|
5286
|
+
textarea.addEventListener('keydown', handler, true);
|
|
5287
|
+
textarea.addEventListener('paste', handler, true);
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
this.boundTerminalClaimRoot = root;
|
|
5291
|
+
this.boundTerminalClaimTextarea = textarea;
|
|
5292
|
+
this.boundTerminalClaimHandler = handler;
|
|
5293
|
+
}
|
|
5294
|
+
|
|
5295
|
+
unbindTerminalControlClaim() {
|
|
5296
|
+
const handler = this.boundTerminalClaimHandler;
|
|
5297
|
+
if (!handler) {
|
|
5298
|
+
return;
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5301
|
+
this.boundTerminalClaimRoot?.removeEventListener(
|
|
5302
|
+
'mousedown',
|
|
5303
|
+
handler,
|
|
5304
|
+
true
|
|
5305
|
+
);
|
|
5306
|
+
this.boundTerminalClaimRoot?.removeEventListener(
|
|
5307
|
+
'touchstart',
|
|
5308
|
+
handler,
|
|
5309
|
+
true
|
|
5310
|
+
);
|
|
5311
|
+
this.boundTerminalClaimTextarea?.removeEventListener(
|
|
5312
|
+
'keydown',
|
|
5313
|
+
handler,
|
|
5314
|
+
true
|
|
5315
|
+
);
|
|
5316
|
+
this.boundTerminalClaimTextarea?.removeEventListener(
|
|
5317
|
+
'paste',
|
|
5318
|
+
handler,
|
|
5319
|
+
true
|
|
5320
|
+
);
|
|
5321
|
+
|
|
5322
|
+
this.boundTerminalClaimRoot = null;
|
|
5323
|
+
this.boundTerminalClaimTextarea = null;
|
|
5324
|
+
this.boundTerminalClaimHandler = null;
|
|
5325
|
+
}
|
|
5326
|
+
|
|
5070
5327
|
reportResize() {
|
|
5071
5328
|
if (!this.isMainTerminalVisible()) {
|
|
5072
5329
|
return;
|
|
@@ -5084,6 +5341,7 @@ class Session {
|
|
|
5084
5341
|
this.shouldReconnect = false;
|
|
5085
5342
|
clearTimeout(this.retryTimer);
|
|
5086
5343
|
this.socket?.close();
|
|
5344
|
+
this.unbindTerminalControlClaim();
|
|
5087
5345
|
|
|
5088
5346
|
try {
|
|
5089
5347
|
if (this.previewTerm) this.previewTerm.dispose();
|
|
@@ -5124,6 +5382,9 @@ class AgentTab {
|
|
|
5124
5382
|
this.scrollToBottomOnNextRender = true;
|
|
5125
5383
|
this.busySyncTimer = null;
|
|
5126
5384
|
this.planHistory = [];
|
|
5385
|
+
this.resumeSessions = [];
|
|
5386
|
+
this.resumeSessionsLoadedAt = 0;
|
|
5387
|
+
this.resumeSessionsPromise = null;
|
|
5127
5388
|
this.update(data);
|
|
5128
5389
|
this.connect();
|
|
5129
5390
|
}
|
|
@@ -5143,6 +5404,7 @@ class AgentTab {
|
|
|
5143
5404
|
}
|
|
5144
5405
|
|
|
5145
5406
|
update(data) {
|
|
5407
|
+
const previousResumeCacheKey = `${this.agentId || ''}:${this.cwd || ''}`;
|
|
5146
5408
|
this.runtimeId = data.runtimeId || '';
|
|
5147
5409
|
this.runtimeKey = data.runtimeKey || '';
|
|
5148
5410
|
this.acpSessionId = data.acpSessionId || '';
|
|
@@ -5163,9 +5425,17 @@ class AgentTab {
|
|
|
5163
5425
|
this.availableCommands = Array.isArray(data.availableCommands)
|
|
5164
5426
|
? data.availableCommands
|
|
5165
5427
|
: [];
|
|
5428
|
+
this.sessionCapabilities = normalizeAgentSessionCapabilities(
|
|
5429
|
+
data.sessionCapabilities || this.sessionCapabilities
|
|
5430
|
+
);
|
|
5166
5431
|
this.configOptions = Array.isArray(data.configOptions)
|
|
5167
5432
|
? data.configOptions
|
|
5168
5433
|
: [];
|
|
5434
|
+
const nextResumeCacheKey = `${this.agentId || ''}:${this.cwd || ''}`;
|
|
5435
|
+
if (previousResumeCacheKey !== nextResumeCacheKey) {
|
|
5436
|
+
this.resumeSessions = [];
|
|
5437
|
+
this.resumeSessionsLoadedAt = 0;
|
|
5438
|
+
}
|
|
5169
5439
|
const nextPlan = Array.isArray(data.plan)
|
|
5170
5440
|
? data.plan.map((entry) => this.#normalizePlanEntry(entry))
|
|
5171
5441
|
: [];
|
|
@@ -5233,6 +5503,49 @@ class AgentTab {
|
|
|
5233
5503
|
this.#syncBusyWatchdog();
|
|
5234
5504
|
}
|
|
5235
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
|
+
|
|
5236
5549
|
connect() {
|
|
5237
5550
|
if (!this.server.isAuthenticated) return;
|
|
5238
5551
|
if (
|
|
@@ -5806,6 +6119,23 @@ class AgentTab {
|
|
|
5806
6119
|
|
|
5807
6120
|
applyInventory(data) {
|
|
5808
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
|
+
});
|
|
5809
6139
|
this.runtimeId = data.runtimeId || this.runtimeId || '';
|
|
5810
6140
|
this.runtimeKey = data.runtimeKey || this.runtimeKey || '';
|
|
5811
6141
|
this.acpSessionId = data.acpSessionId || this.acpSessionId || '';
|
|
@@ -5820,7 +6150,32 @@ class AgentTab {
|
|
|
5820
6150
|
this.busy = typeof data.busy === 'boolean' ? data.busy : this.busy;
|
|
5821
6151
|
this.errorMessage = data.errorMessage || this.errorMessage || '';
|
|
5822
6152
|
this.currentModeId = data.currentModeId || this.currentModeId || '';
|
|
6153
|
+
this.sessionCapabilities = normalizeAgentSessionCapabilities(
|
|
6154
|
+
data.sessionCapabilities || this.sessionCapabilities
|
|
6155
|
+
);
|
|
5823
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
|
+
}
|
|
5824
6179
|
previousSession?.updateTabUI();
|
|
5825
6180
|
if (nextSession && nextSession !== previousSession) {
|
|
5826
6181
|
nextSession.updateTabUI();
|
|
@@ -5828,6 +6183,7 @@ class AgentTab {
|
|
|
5828
6183
|
if (nextSession) {
|
|
5829
6184
|
refreshWorkspaceIfSessionActive(nextSession);
|
|
5830
6185
|
}
|
|
6186
|
+
return true;
|
|
5831
6187
|
}
|
|
5832
6188
|
|
|
5833
6189
|
async #waitForSettled(timeoutMs = 5000) {
|
|
@@ -6374,6 +6730,27 @@ function normalizeAgentConfigOptionOptions(options) {
|
|
|
6374
6730
|
return flattened;
|
|
6375
6731
|
}
|
|
6376
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
|
+
|
|
6377
6754
|
function getAgentConfigOptionById(agentTab, configId) {
|
|
6378
6755
|
return normalizeAgentConfigOptions(agentTab?.configOptions).find(
|
|
6379
6756
|
(option) => option.id === configId
|
|
@@ -6474,6 +6851,7 @@ function normalizeAgentCommands(commands) {
|
|
|
6474
6851
|
: '';
|
|
6475
6852
|
if (!name) return null;
|
|
6476
6853
|
return {
|
|
6854
|
+
kind: 'command',
|
|
6477
6855
|
name,
|
|
6478
6856
|
description: command?.description || '',
|
|
6479
6857
|
inputHint: command?.input?.hint || ''
|
|
@@ -6482,6 +6860,107 @@ function normalizeAgentCommands(commands) {
|
|
|
6482
6860
|
.filter(Boolean);
|
|
6483
6861
|
}
|
|
6484
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
|
+
|
|
6485
6964
|
function bindSingleTapActivation(element, onActivate, options = {}) {
|
|
6486
6965
|
if (!element || typeof onActivate !== 'function') {
|
|
6487
6966
|
return;
|
|
@@ -6643,17 +7122,19 @@ function selectAgentMessageText(previousText, nextText) {
|
|
|
6643
7122
|
}
|
|
6644
7123
|
|
|
6645
7124
|
function getAgentCommandSuggestions(agentTab, promptValue) {
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
const
|
|
6650
|
-
if (
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
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();
|
|
6657
7138
|
const ranked = commands.filter((command) => {
|
|
6658
7139
|
const name = command.name.toLowerCase();
|
|
6659
7140
|
return !query || name.startsWith(query) || name.includes(query);
|
|
@@ -6671,6 +7152,52 @@ function getAgentCommandSuggestions(agentTab, promptValue) {
|
|
|
6671
7152
|
return ranked.slice(0, 8);
|
|
6672
7153
|
}
|
|
6673
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
|
+
|
|
6674
7201
|
function getCurrentAgentModeLabel(agentTab) {
|
|
6675
7202
|
const currentModeId = agentTab?.currentModeId || '';
|
|
6676
7203
|
if (!currentModeId) return '';
|
|
@@ -9019,13 +9546,19 @@ function upsertAgentInventoryTab(server, data) {
|
|
|
9019
9546
|
const key = makeAgentTabKey(server.id, data.id);
|
|
9020
9547
|
const existing = state.agentTabs.get(key);
|
|
9021
9548
|
if (existing) {
|
|
9022
|
-
existing.applyInventory(data);
|
|
9549
|
+
const changed = existing.applyInventory(data);
|
|
9023
9550
|
existing.connect();
|
|
9024
|
-
return
|
|
9551
|
+
return {
|
|
9552
|
+
agentTab: existing,
|
|
9553
|
+
changed
|
|
9554
|
+
};
|
|
9025
9555
|
}
|
|
9026
9556
|
const agentTab = new AgentTab(data, server);
|
|
9027
9557
|
state.agentTabs.set(key, agentTab);
|
|
9028
|
-
return
|
|
9558
|
+
return {
|
|
9559
|
+
agentTab,
|
|
9560
|
+
changed: true
|
|
9561
|
+
};
|
|
9029
9562
|
}
|
|
9030
9563
|
|
|
9031
9564
|
function reconcileAgentInventory(server, inventory) {
|
|
@@ -9037,8 +9570,11 @@ function reconcileAgentInventory(server, inventory) {
|
|
|
9037
9570
|
const touchedSessions = new Set();
|
|
9038
9571
|
|
|
9039
9572
|
for (const tabData of Array.isArray(inventory.tabs) ? inventory.tabs : []) {
|
|
9040
|
-
const agentTab = upsertAgentInventoryTab(server, tabData);
|
|
9573
|
+
const { agentTab, changed } = upsertAgentInventoryTab(server, tabData);
|
|
9041
9574
|
seenKeys.add(agentTab.key);
|
|
9575
|
+
if (!changed) {
|
|
9576
|
+
continue;
|
|
9577
|
+
}
|
|
9042
9578
|
const session = agentTab.getLinkedSession();
|
|
9043
9579
|
if (session) {
|
|
9044
9580
|
touchedSessions.add(session.key);
|
|
@@ -9250,16 +9786,23 @@ async function createAgentTab(session, agentId, options = {}) {
|
|
|
9250
9786
|
await throwResponseError(response, 'Failed to create agent tab');
|
|
9251
9787
|
}
|
|
9252
9788
|
const data = await response.json();
|
|
9253
|
-
|
|
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
|
+
}
|
|
9254
9801
|
session.workspaceState.activeTabKey = agentTab.key;
|
|
9255
9802
|
noteRecentAgentTab(session, agentTab.key);
|
|
9256
9803
|
session.saveState();
|
|
9257
9804
|
if (state.activeSessionKey === session.key) {
|
|
9258
|
-
|
|
9259
|
-
editorManager.switchTo(session);
|
|
9260
|
-
}
|
|
9261
|
-
editorManager.activateAgentTab(agentTab.key);
|
|
9262
|
-
editorManager.updateEditorPaneVisibility();
|
|
9805
|
+
restoreWorkspaceForSession(session);
|
|
9263
9806
|
requestAnimationFrame(() => {
|
|
9264
9807
|
editorManager.agentPrompt?.focus();
|
|
9265
9808
|
});
|
|
@@ -9269,6 +9812,29 @@ async function createAgentTab(session, agentId, options = {}) {
|
|
|
9269
9812
|
return agentTab;
|
|
9270
9813
|
}
|
|
9271
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
|
+
|
|
9272
9838
|
function getServerEndpointKey(server) {
|
|
9273
9839
|
if (!server) return '';
|
|
9274
9840
|
return getServerEndpointKeyFromUrl(server.baseUrl);
|
|
@@ -10773,6 +11339,11 @@ async function switchToSession(sessionKey, options = {}) {
|
|
|
10773
11339
|
return;
|
|
10774
11340
|
}
|
|
10775
11341
|
|
|
11342
|
+
const previousSession = state.activeSessionKey
|
|
11343
|
+
? state.sessions.get(state.activeSessionKey)
|
|
11344
|
+
: null;
|
|
11345
|
+
previousSession?.unbindTerminalControlClaim();
|
|
11346
|
+
|
|
10776
11347
|
state.activeSessionKey = sessionKey;
|
|
10777
11348
|
renderTabs();
|
|
10778
11349
|
if (scrollTabIntoView) {
|
|
@@ -10789,6 +11360,7 @@ async function switchToSession(sessionKey, options = {}) {
|
|
|
10789
11360
|
|
|
10790
11361
|
// Mount new session
|
|
10791
11362
|
session.mainTerm.open(terminalEl);
|
|
11363
|
+
session.bindTerminalControlClaim();
|
|
10792
11364
|
session.fitMainTerminalIfVisible();
|
|
10793
11365
|
if (session.isMainTerminalVisible()) {
|
|
10794
11366
|
session.mainTerm.focus();
|
|
@@ -11599,6 +12171,52 @@ if (shortcutsModal) {
|
|
|
11599
12171
|
});
|
|
11600
12172
|
}
|
|
11601
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
|
+
|
|
11602
12220
|
// Keyboard Shortcuts
|
|
11603
12221
|
document.addEventListener('keydown', (e) => {
|
|
11604
12222
|
if (
|