colana 1.0.0-beta.63 → 1.0.0-beta.65

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": "colana",
3
- "version": "1.0.0-beta.63",
3
+ "version": "1.0.0-beta.65",
4
4
  "description": "Agent-First. Multiplied. Multi-agent command center for AI coding agents.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
package/public/app.js CHANGED
@@ -818,10 +818,17 @@ function maximizePersonalPanel() {
818
818
  }
819
819
  }
820
820
 
821
+ // Personal agent launch guards — prevent duplicate spawns from concurrent calls
822
+ let _personalAgentLaunching = false;
823
+ let _personalAgentAutoStartTimer = null;
824
+
821
825
  /** Launch the personal agent (get/create Personal project via dedicated endpoint, start openclaw PTY) */
822
826
  async function launchPersonalAgent() {
823
827
  // Guard: don't launch if a personal agent session is already running
824
828
  if (state.personalAgent.sessionId) return;
829
+ // Guard: don't launch if another launch call is already in-flight
830
+ if (_personalAgentLaunching) return;
831
+ _personalAgentLaunching = true;
825
832
  try {
826
833
  // Get or create the personal project via dedicated endpoint (not in state.projects —
827
834
  // personal projects are architecturally separate from coding projects)
@@ -865,6 +872,8 @@ async function launchPersonalAgent() {
865
872
  } else {
866
873
  showToast('Failed to start personal agent: ' + (err.message || err), 'error');
867
874
  }
875
+ } finally {
876
+ _personalAgentLaunching = false;
868
877
  }
869
878
  }
870
879
 
@@ -2111,11 +2120,14 @@ function handleWebSocketMessage(message) {
2111
2120
 
2112
2121
  // Auto-start personal agent if setting enabled.
2113
2122
  // Delay 5s (not 2s) so server-side auto-resume (3s) completes first.
2114
- // Re-check sessionId before launching to avoid duplicate sessions.
2123
+ // Debounce: clear any pending timer so only one auto-start attempt runs,
2124
+ // even if multiple activeSessionsUpdated events arrive in quick succession.
2115
2125
  if (localStorage.getItem('colana_personalAutostart') === 'true'
2116
2126
  && state.personalAgent.configured
2117
2127
  && !state.personalAgent.sessionId) {
2118
- setTimeout(() => {
2128
+ if (_personalAgentAutoStartTimer) clearTimeout(_personalAgentAutoStartTimer);
2129
+ _personalAgentAutoStartTimer = setTimeout(() => {
2130
+ _personalAgentAutoStartTimer = null;
2119
2131
  if (!state.personalAgent.sessionId) launchPersonalAgent();
2120
2132
  }, 5000);
2121
2133
  }
package/public/styles.css CHANGED
@@ -4062,6 +4062,24 @@ select.form-input option {
4062
4062
 
4063
4063
  .pty-container .xterm-viewport {
4064
4064
  scrollbar-width: thin;
4065
+ scrollbar-color: var(--text-faint) transparent;
4066
+ }
4067
+
4068
+ .pty-container .xterm-viewport::-webkit-scrollbar {
4069
+ width: 6px;
4070
+ }
4071
+
4072
+ .pty-container .xterm-viewport::-webkit-scrollbar-track {
4073
+ background: transparent;
4074
+ }
4075
+
4076
+ .pty-container .xterm-viewport::-webkit-scrollbar-thumb {
4077
+ background: var(--text-faint);
4078
+ border-radius: 3px;
4079
+ }
4080
+
4081
+ .pty-container .xterm-viewport::-webkit-scrollbar-thumb:hover {
4082
+ background: var(--text-muted);
4065
4083
  }
4066
4084
 
4067
4085
  /* Feature 2: Ctrl+F Terminal Search Bar */
@@ -4731,6 +4749,24 @@ select.form-input option {
4731
4749
 
4732
4750
  .split-pane-terminal .xterm-viewport {
4733
4751
  scrollbar-width: thin;
4752
+ scrollbar-color: var(--text-faint) transparent;
4753
+ }
4754
+
4755
+ .split-pane-terminal .xterm-viewport::-webkit-scrollbar {
4756
+ width: 6px;
4757
+ }
4758
+
4759
+ .split-pane-terminal .xterm-viewport::-webkit-scrollbar-track {
4760
+ background: transparent;
4761
+ }
4762
+
4763
+ .split-pane-terminal .xterm-viewport::-webkit-scrollbar-thumb {
4764
+ background: var(--text-faint);
4765
+ border-radius: 3px;
4766
+ }
4767
+
4768
+ .split-pane-terminal .xterm-viewport::-webkit-scrollbar-thumb:hover {
4769
+ background: var(--text-muted);
4734
4770
  }
4735
4771
 
4736
4772
  /* Split-pane layout picker menu */
@@ -74,6 +74,21 @@ export function createAgentControlRoutes(app, { agentLimiter, ptyManager, ptyAva
74
74
  : 'gemini';
75
75
  const providerConfig = config.providers[provider];
76
76
 
77
+ // Prevent duplicate personal agent sessions.
78
+ // If a personal provider is already running, return the existing session
79
+ // instead of spawning a second one. Guards against race conditions between
80
+ // server-side auto-resume and frontend auto-start.
81
+ // Checked BEFORE preflight to avoid unnecessary binary validation when
82
+ // the session is already live.
83
+ if (providerConfig?.isPersonal) {
84
+ const existingPersonal = activeSessions.find(
85
+ s => s.provider === provider && (s.status === 'running' || s.status === 'starting')
86
+ );
87
+ if (existingPersonal) {
88
+ return res.status(200).json(existingPersonal);
89
+ }
90
+ }
91
+
77
92
  // Pre-flight validation: project path, CLI binary, provider-specific checks
78
93
  const check = preflightCheck(provider, project.path);
79
94
  if (!check.ok) {
@@ -28,7 +28,7 @@ import { syncBeforeSpawn } from './context-sync.js';
28
28
  import { trackEvent } from './analytics.js';
29
29
  import { addNotification as persistNotification } from './personal-chat-store.js';
30
30
  import { shouldAutoIsolate, createWorktree } from './worktree-manager.js';
31
- import { syncAuthProfilesFromEnv, getOrCreateGatewayToken, ensureGatewayAuthToken, getOpenClawConfig, syncGatewayTokenToEnv } from './openclaw-config.js';
31
+ import { syncAuthProfilesFromEnv, getOrCreateGatewayToken, ensureGatewayAuthToken, getOpenClawConfig, syncGatewayTokenToEnv, getOpenClawAuthToken } from './openclaw-config.js';
32
32
 
33
33
  // Provider-specific regex patterns for detecting session IDs from PTY output
34
34
  const SESSION_ID_PATTERNS = {
@@ -477,11 +477,28 @@ async function killGatewayOnPort(port) {
477
477
  } catch { /* best effort */ }
478
478
  }
479
479
 
480
+ // Singleton promise guard: only one ensureOpenClawGateway call runs at a time.
481
+ // Concurrent callers piggyback on the in-flight promise.
482
+ let _gatewayEnsurePromise = null;
483
+
480
484
  /**
481
485
  * Ensure the OpenClaw gateway daemon is running on the given port.
482
- * If not, spawn it detached and poll until it's reachable (up to 8 s).
486
+ * Uses a singleton promise so concurrent callers coalesce into one execution.
487
+ * After completion, subsequent calls run fresh (not cached).
483
488
  */
484
489
  async function ensureOpenClawGateway(port) {
490
+ if (_gatewayEnsurePromise) return _gatewayEnsurePromise;
491
+ _gatewayEnsurePromise = _ensureOpenClawGatewayImpl(port).finally(() => {
492
+ _gatewayEnsurePromise = null;
493
+ });
494
+ return _gatewayEnsurePromise;
495
+ }
496
+
497
+ /**
498
+ * Implementation: ensure the OpenClaw gateway daemon is running on the given port.
499
+ * If not, spawn it detached and poll until it's reachable (up to 8 s).
500
+ */
501
+ async function _ensureOpenClawGatewayImpl(port) {
485
502
  // Always sync API keys into auth-profiles.json — even when the gateway is
486
503
  // already running — so keys saved via Settings are picked up on next request.
487
504
  const authSync = syncAuthProfilesFromEnv();
@@ -2272,6 +2289,8 @@ export async function restartOpenClawGateway() {
2272
2289
 
2273
2290
  if (isRunning) {
2274
2291
  console.log('[pty-manager] Restarting OpenClaw gateway to reload MCP config...');
2292
+ // Clear any in-flight singleton promise so the ensure call below runs fresh
2293
+ _gatewayEnsurePromise = null;
2275
2294
  // Clear marker before kill so ensureOpenClawGateway knows to respawn
2276
2295
  try { fs.unlinkSync(GATEWAY_MARKER_PATH); } catch { /* may not exist */ }
2277
2296
  await killGatewayOnPort(gwPort);