tabminal 3.0.38 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabminal",
3
- "version": "3.0.38",
3
+ "version": "3.0.39",
4
4
  "description": "Tab(ter)minal, a Cloud-Native terminal and ACP agent workspace for desktop, tablet, and phone.",
5
5
  "type": "module",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -14757,6 +14757,7 @@ async function resumeAgentTabFromHistory(session, agentTab, historySession) {
14757
14757
  cwd: agentTab.cwd || session.cwd || session.initialCwd || '/',
14758
14758
  terminalSessionId: session.id,
14759
14759
  sessionId: historySession.sessionId,
14760
+ targetTabId: agentTab.id,
14760
14761
  title: historySession.title || ''
14761
14762
  })
14762
14763
  });
@@ -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({
@@ -4580,6 +4682,43 @@ export class AcpManager {
4580
4682
  }
4581
4683
 
4582
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
+
4583
4722
  const { runtimeEntry, createdRuntime, runtimeStoreKey } =
4584
4723
  this.#ensureRuntimeEntry(definition, cwd);
4585
4724
  const tabId = crypto.randomUUID();
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