tabminal 3.0.37 → 3.0.39
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 +55 -18
- package/src/acp-manager.mjs +157 -1
- package/src/server.mjs +2 -1
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -8213,6 +8213,14 @@ class EditorManager {
|
|
|
8213
8213
|
if (!agentTab) return;
|
|
8214
8214
|
const session = agentTab.getLinkedSession();
|
|
8215
8215
|
if (!session) return;
|
|
8216
|
+
this.#renderAgentCommandSuggestions([{
|
|
8217
|
+
kind: 'info',
|
|
8218
|
+
label: 'Opening previous session...',
|
|
8219
|
+
description: command.displayName
|
|
8220
|
+
|| command.title
|
|
8221
|
+
|| command.sessionId
|
|
8222
|
+
|| ''
|
|
8223
|
+
}]);
|
|
8216
8224
|
try {
|
|
8217
8225
|
let targetAgentTab = null;
|
|
8218
8226
|
let targetPromptDraft = '';
|
|
@@ -8265,11 +8273,13 @@ class EditorManager {
|
|
|
8265
8273
|
targetPromptDraft,
|
|
8266
8274
|
targetAgentTab || getActiveAgentTab() || agentTab
|
|
8267
8275
|
);
|
|
8276
|
+
this.hideAgentCommandMenu();
|
|
8268
8277
|
} catch (error) {
|
|
8269
8278
|
alert(error.message, {
|
|
8270
8279
|
type: 'error',
|
|
8271
8280
|
title: getAgentBaseName(agentTab)
|
|
8272
8281
|
});
|
|
8282
|
+
this.hideAgentCommandMenu();
|
|
8273
8283
|
}
|
|
8274
8284
|
return;
|
|
8275
8285
|
}
|
|
@@ -14719,27 +14729,54 @@ async function activateAgentTab(session, agentTab, options = {}) {
|
|
|
14719
14729
|
return agentTab;
|
|
14720
14730
|
}
|
|
14721
14731
|
|
|
14732
|
+
const pendingAgentHistoryResumes = new Map();
|
|
14733
|
+
|
|
14734
|
+
function getAgentHistoryResumeKey(session, agentTab, historySession) {
|
|
14735
|
+
return [
|
|
14736
|
+
session?.server?.id || '',
|
|
14737
|
+
session?.key || '',
|
|
14738
|
+
agentTab?.agentId || '',
|
|
14739
|
+
String(historySession?.sessionId || '').trim()
|
|
14740
|
+
].join('\0');
|
|
14741
|
+
}
|
|
14742
|
+
|
|
14722
14743
|
async function resumeAgentTabFromHistory(session, agentTab, historySession) {
|
|
14723
14744
|
if (!session || !agentTab || !historySession?.sessionId) return null;
|
|
14724
|
-
const
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14745
|
+
const resumeKey = getAgentHistoryResumeKey(session, agentTab, historySession);
|
|
14746
|
+
const pendingResume = pendingAgentHistoryResumes.get(resumeKey);
|
|
14747
|
+
if (pendingResume) {
|
|
14748
|
+
return await pendingResume;
|
|
14749
|
+
}
|
|
14750
|
+
|
|
14751
|
+
const resumePromise = (async () => {
|
|
14752
|
+
const response = await session.server.fetch('/api/agents/tabs/resume', {
|
|
14753
|
+
method: 'POST',
|
|
14754
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14755
|
+
body: JSON.stringify({
|
|
14756
|
+
agentId: agentTab.agentId,
|
|
14757
|
+
cwd: agentTab.cwd || session.cwd || session.initialCwd || '/',
|
|
14758
|
+
terminalSessionId: session.id,
|
|
14759
|
+
sessionId: historySession.sessionId,
|
|
14760
|
+
targetTabId: agentTab.id,
|
|
14761
|
+
title: historySession.title || ''
|
|
14762
|
+
})
|
|
14763
|
+
});
|
|
14764
|
+
if (!response.ok) {
|
|
14765
|
+
await throwResponseError(response, 'Failed to resume agent session');
|
|
14766
|
+
}
|
|
14767
|
+
const data = await response.json();
|
|
14768
|
+
return await activateAgentTab(
|
|
14769
|
+
session,
|
|
14770
|
+
upsertAgentTab(session.server, data)
|
|
14771
|
+
);
|
|
14772
|
+
})();
|
|
14773
|
+
pendingAgentHistoryResumes.set(resumeKey, resumePromise);
|
|
14774
|
+
|
|
14775
|
+
try {
|
|
14776
|
+
return await resumePromise;
|
|
14777
|
+
} finally {
|
|
14778
|
+
pendingAgentHistoryResumes.delete(resumeKey);
|
|
14737
14779
|
}
|
|
14738
|
-
const data = await response.json();
|
|
14739
|
-
return await activateAgentTab(
|
|
14740
|
-
session,
|
|
14741
|
-
upsertAgentTab(session.server, data)
|
|
14742
|
-
);
|
|
14743
14780
|
}
|
|
14744
14781
|
|
|
14745
14782
|
function getServerEndpointKey(server) {
|
package/src/acp-manager.mjs
CHANGED
|
@@ -2733,6 +2733,108 @@ class AcpRuntime extends EventEmitter {
|
|
|
2733
2733
|
}
|
|
2734
2734
|
}
|
|
2735
2735
|
|
|
2736
|
+
#resetTabForSessionLoad(tab, meta) {
|
|
2737
|
+
tab.acpSessionId = String(meta.acpSessionId || '').trim();
|
|
2738
|
+
tab.terminalSessionId = meta.terminalSessionId || tab.terminalSessionId;
|
|
2739
|
+
tab.cwd = meta.cwd || tab.cwd;
|
|
2740
|
+
tab.createdAt = new Date().toISOString();
|
|
2741
|
+
tab.title = meta.title || '';
|
|
2742
|
+
tab.status = 'restoring';
|
|
2743
|
+
tab.busy = true;
|
|
2744
|
+
tab.errorMessage = '';
|
|
2745
|
+
tab.messages = [];
|
|
2746
|
+
tab.toolCalls = new Map();
|
|
2747
|
+
tab.permissions = new Map();
|
|
2748
|
+
tab.syntheticStreams = new Map();
|
|
2749
|
+
tab.syntheticStreamTurn = 0;
|
|
2750
|
+
tab.pendingUserEcho = null;
|
|
2751
|
+
tab.plan = [];
|
|
2752
|
+
tab.usage = null;
|
|
2753
|
+
tab.terminals = new Map();
|
|
2754
|
+
tab.messageCounter = 0;
|
|
2755
|
+
tab.timelineCounter = 0;
|
|
2756
|
+
tab.currentModeId = '';
|
|
2757
|
+
tab.availableModes = [];
|
|
2758
|
+
tab.availableCommands = [];
|
|
2759
|
+
tab.configOptions = [];
|
|
2760
|
+
tab.restoreCapture = createRestoreCaptureState([]);
|
|
2761
|
+
tab.restoreCapture.nextTimelineOrder = () =>
|
|
2762
|
+
this.#nextTimelineOrder(tab);
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
#restoreSerializedTab(tab, snapshot) {
|
|
2766
|
+
tab.acpSessionId = snapshot.acpSessionId;
|
|
2767
|
+
tab.terminalSessionId = snapshot.terminalSessionId || '';
|
|
2768
|
+
tab.cwd = snapshot.cwd || tab.cwd;
|
|
2769
|
+
tab.createdAt = snapshot.createdAt || tab.createdAt;
|
|
2770
|
+
tab.status = snapshot.status || 'ready';
|
|
2771
|
+
tab.busy = !!snapshot.busy;
|
|
2772
|
+
tab.errorMessage = snapshot.errorMessage || '';
|
|
2773
|
+
tab.syntheticStreams = new Map();
|
|
2774
|
+
tab.pendingUserEcho = null;
|
|
2775
|
+
tab.restoreCapture = null;
|
|
2776
|
+
restorePersistedTabSnapshot(tab, snapshot);
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
async resumeIntoTab(tabId, meta) {
|
|
2780
|
+
await this.start();
|
|
2781
|
+
this.clearIdleShutdown();
|
|
2782
|
+
|
|
2783
|
+
const sessionCapabilities = this.#getSessionCapabilities();
|
|
2784
|
+
if (!sessionCapabilities.load) {
|
|
2785
|
+
throw new Error(
|
|
2786
|
+
`${this.definition.label} does not support session restore`
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
const tab = this.tabs.get(tabId);
|
|
2791
|
+
if (!tab) {
|
|
2792
|
+
throw new Error('Agent tab not found');
|
|
2793
|
+
}
|
|
2794
|
+
if (tab.busy) {
|
|
2795
|
+
throw new Error('Agent tab is already running');
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
const targetSessionId = String(meta.acpSessionId || '').trim();
|
|
2799
|
+
if (!targetSessionId) {
|
|
2800
|
+
throw new Error('sessionId is required');
|
|
2801
|
+
}
|
|
2802
|
+
const existingTabId = this.sessionToTabId.get(targetSessionId);
|
|
2803
|
+
if (existingTabId && existingTabId !== tab.id) {
|
|
2804
|
+
throw new Error('Session is already open');
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
const previousSnapshot = this.serializeTab(tab);
|
|
2808
|
+
this.sessionToTabId.delete(tab.acpSessionId);
|
|
2809
|
+
this.#resetTabForSessionLoad(tab, {
|
|
2810
|
+
...meta,
|
|
2811
|
+
acpSessionId: targetSessionId
|
|
2812
|
+
});
|
|
2813
|
+
this.sessionToTabId.set(tab.acpSessionId, tab.id);
|
|
2814
|
+
this.#broadcast(tab, {
|
|
2815
|
+
type: 'snapshot',
|
|
2816
|
+
tab: this.serializeTab(tab)
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
try {
|
|
2820
|
+
await this.#loadSessionIntoTab(tab, meta);
|
|
2821
|
+
this.#broadcast(tab, {
|
|
2822
|
+
type: 'snapshot',
|
|
2823
|
+
tab: this.serializeTab(tab)
|
|
2824
|
+
});
|
|
2825
|
+
return this.serializeTab(tab);
|
|
2826
|
+
} catch (error) {
|
|
2827
|
+
this.sessionToTabId.delete(tab.acpSessionId);
|
|
2828
|
+
this.#restoreSerializedTab(tab, previousSnapshot);
|
|
2829
|
+
this.sessionToTabId.set(tab.acpSessionId, tab.id);
|
|
2830
|
+
this.#broadcast(tab, {
|
|
2831
|
+
type: 'snapshot',
|
|
2832
|
+
tab: this.serializeTab(tab)
|
|
2833
|
+
});
|
|
2834
|
+
throw error;
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2736
2838
|
async #loadSessionIntoTab(tab, meta) {
|
|
2737
2839
|
try {
|
|
2738
2840
|
const response = await this.connection.loadSession({
|
|
@@ -3890,6 +3992,7 @@ export class AcpManager {
|
|
|
3890
3992
|
this.loadConfigs = options.loadConfigs || persistence.loadAgentConfigs;
|
|
3891
3993
|
this.saveConfigs = options.saveConfigs || persistence.saveAgentConfigs;
|
|
3892
3994
|
this.persistenceChain = Promise.resolve();
|
|
3995
|
+
this.pendingResumeTabs = new Map();
|
|
3893
3996
|
this.transcriptPersistDelayMs = options.transcriptPersistDelayMs
|
|
3894
3997
|
|| DEFAULT_TRANSCRIPT_PERSIST_DELAY_MS;
|
|
3895
3998
|
this.persistTabsTimer = null;
|
|
@@ -4569,12 +4672,58 @@ export class AcpManager {
|
|
|
4569
4672
|
return existingTab;
|
|
4570
4673
|
}
|
|
4571
4674
|
|
|
4675
|
+
const resumeKey = [
|
|
4676
|
+
definition.id,
|
|
4677
|
+
String(options.sessionId || '').trim()
|
|
4678
|
+
].join('\0');
|
|
4679
|
+
const pendingResume = this.pendingResumeTabs.get(resumeKey);
|
|
4680
|
+
if (pendingResume) {
|
|
4681
|
+
return await pendingResume;
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4572
4684
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
4685
|
+
const targetTabId = String(options.targetTabId || '').trim();
|
|
4686
|
+
if (targetTabId) {
|
|
4687
|
+
const tabEntry = this.tabs.get(targetTabId);
|
|
4688
|
+
if (!tabEntry) {
|
|
4689
|
+
throw new Error('Agent tab not found');
|
|
4690
|
+
}
|
|
4691
|
+
const currentSerialized = tabEntry.serialize();
|
|
4692
|
+
if (currentSerialized.agentId !== definition.id) {
|
|
4693
|
+
throw new Error('Agent mismatch');
|
|
4694
|
+
}
|
|
4695
|
+
const resumePromise = (async () => {
|
|
4696
|
+
const rawSerialized = await tabEntry.runtime.resumeIntoTab(
|
|
4697
|
+
targetTabId,
|
|
4698
|
+
{
|
|
4699
|
+
acpSessionId: options.sessionId,
|
|
4700
|
+
cwd,
|
|
4701
|
+
terminalSessionId: options.terminalSessionId || '',
|
|
4702
|
+
title: options.title || ''
|
|
4703
|
+
}
|
|
4704
|
+
);
|
|
4705
|
+
const serialized = this.#applyRuntimeMetadataFallback(
|
|
4706
|
+
tabEntry.runtime,
|
|
4707
|
+
rawSerialized
|
|
4708
|
+
);
|
|
4709
|
+
this.#clearDefinitionAvailabilityOverride(definition.id);
|
|
4710
|
+
await this.persistTabs();
|
|
4711
|
+
return serialized;
|
|
4712
|
+
})();
|
|
4713
|
+
this.pendingResumeTabs.set(resumeKey, resumePromise);
|
|
4714
|
+
|
|
4715
|
+
try {
|
|
4716
|
+
return await resumePromise;
|
|
4717
|
+
} finally {
|
|
4718
|
+
this.pendingResumeTabs.delete(resumeKey);
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4573
4722
|
const { runtimeEntry, createdRuntime, runtimeStoreKey } =
|
|
4574
4723
|
this.#ensureRuntimeEntry(definition, cwd);
|
|
4575
4724
|
const tabId = crypto.randomUUID();
|
|
4576
4725
|
|
|
4577
|
-
|
|
4726
|
+
const resumePromise = (async () => {
|
|
4578
4727
|
const rawSerialized = await runtimeEntry.runtime.resumeTab({
|
|
4579
4728
|
id: tabId,
|
|
4580
4729
|
acpSessionId: options.sessionId,
|
|
@@ -4603,6 +4752,11 @@ export class AcpManager {
|
|
|
4603
4752
|
this.#clearDefinitionAvailabilityOverride(definition.id);
|
|
4604
4753
|
await this.persistTabs();
|
|
4605
4754
|
return tabEntry.serialize();
|
|
4755
|
+
})();
|
|
4756
|
+
this.pendingResumeTabs.set(resumeKey, resumePromise);
|
|
4757
|
+
|
|
4758
|
+
try {
|
|
4759
|
+
return await resumePromise;
|
|
4606
4760
|
} catch (error) {
|
|
4607
4761
|
const shouldDisposeRuntime = createdRuntime
|
|
4608
4762
|
|| runtimeEntry.runtime.tabs.size === 0;
|
|
@@ -4610,6 +4764,8 @@ export class AcpManager {
|
|
|
4610
4764
|
await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
|
|
4611
4765
|
}
|
|
4612
4766
|
throw error;
|
|
4767
|
+
} finally {
|
|
4768
|
+
this.pendingResumeTabs.delete(resumeKey);
|
|
4613
4769
|
}
|
|
4614
4770
|
}
|
|
4615
4771
|
|
package/src/server.mjs
CHANGED
|
@@ -615,7 +615,7 @@ router.post('/api/agents/tabs', async (ctx) => {
|
|
|
615
615
|
});
|
|
616
616
|
|
|
617
617
|
router.post('/api/agents/tabs/resume', async (ctx) => {
|
|
618
|
-
const { agentId, cwd, terminalSessionId, sessionId, title } =
|
|
618
|
+
const { agentId, cwd, terminalSessionId, sessionId, targetTabId, title } =
|
|
619
619
|
ctx.request.body || {};
|
|
620
620
|
if (!agentId || typeof agentId !== 'string') {
|
|
621
621
|
ctx.status = 400;
|
|
@@ -639,6 +639,7 @@ router.post('/api/agents/tabs/resume', async (ctx) => {
|
|
|
639
639
|
agentId,
|
|
640
640
|
cwd,
|
|
641
641
|
sessionId,
|
|
642
|
+
targetTabId: typeof targetTabId === 'string' ? targetTabId : '',
|
|
642
643
|
title: typeof title === 'string' ? title : '',
|
|
643
644
|
terminalSessionId: typeof terminalSessionId === 'string'
|
|
644
645
|
? terminalSessionId
|