colana 1.0.0-beta.64 → 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 +1 -1
- package/public/app.js +14 -2
- package/server/agent-control-routes.js +15 -0
- package/server/pty-manager.js +21 -2
package/package.json
CHANGED
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
|
-
//
|
|
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
|
-
|
|
2128
|
+
if (_personalAgentAutoStartTimer) clearTimeout(_personalAgentAutoStartTimer);
|
|
2129
|
+
_personalAgentAutoStartTimer = setTimeout(() => {
|
|
2130
|
+
_personalAgentAutoStartTimer = null;
|
|
2119
2131
|
if (!state.personalAgent.sessionId) launchPersonalAgent();
|
|
2120
2132
|
}, 5000);
|
|
2121
2133
|
}
|
|
@@ -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) {
|
package/server/pty-manager.js
CHANGED
|
@@ -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
|
-
*
|
|
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);
|