@cloudcli-ai/cloudcli 1.34.0 → 1.35.1
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/README.de.md +9 -1
- package/README.ja.md +8 -0
- package/README.ko.md +9 -1
- package/README.md +23 -5
- package/README.ru.md +9 -1
- package/README.tr.md +8 -1
- package/README.zh-CN.md +9 -1
- package/README.zh-TW.md +9 -1
- package/dist/api-docs.html +36 -18
- package/dist/assets/index-BvX30NW5.css +32 -0
- package/dist/assets/index-D1IdCTT6.js +1418 -0
- package/dist/index.html +12 -4
- package/dist-server/server/browser-use-mcp.js +350 -0
- package/dist-server/server/browser-use-mcp.js.map +1 -0
- package/dist-server/server/claude-sdk.js +25 -5
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +14 -6
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/cursor-cli.js +27 -11
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +17 -2
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/index.js +136 -40
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +1 -1
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +23 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +26 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +4 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +123 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +1 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +5 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +24 -14
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +21 -8
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-cli.test.js +19 -1
- package/dist-server/server/opencode-cli.test.js.map +1 -1
- package/dist-server/server/routes/settings.js +1 -1
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +1 -176
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
- package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
- package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +73 -1
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/voice-proxy.js +226 -0
- package/dist-server/server/voice-proxy.js.map +1 -0
- package/electron/assets/logo-macos.icns +0 -0
- package/electron/assets/logo-macos.png +0 -0
- package/electron/assets/logo-windows.ico +0 -0
- package/electron/cloud.js +260 -0
- package/electron/desktopNotifications.js +378 -0
- package/electron/desktopWindow.js +766 -0
- package/electron/launcher/index.html +14 -0
- package/electron/launcher/launcher.css +801 -0
- package/electron/launcher/launcher.js +687 -0
- package/electron/localServer.js +550 -0
- package/electron/main.js +944 -0
- package/electron/preload.cjs +60 -0
- package/electron/scripts/generate-macos-icon.js +62 -0
- package/electron/serverInstaller.js +277 -0
- package/electron/tabs.js +87 -0
- package/electron/viewHost.js +331 -0
- package/package.json +79 -3
- package/public/api-docs.html +36 -18
- package/scripts/release/build-server-bundle.js +176 -0
- package/scripts/release/prepare-desktop-app.js +152 -0
- package/server/browser-use-mcp.ts +384 -0
- package/server/claude-sdk.js +27 -5
- package/server/cli.js +15 -6
- package/server/cursor-cli.js +27 -11
- package/server/gemini-cli.js +17 -2
- package/server/index.js +137 -47
- package/server/load-env.js +1 -1
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +836 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/database/index.ts +1 -0
- package/server/modules/database/migrations.ts +25 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +12 -1
- package/server/modules/database/repositories/sessions.db.ts +227 -23
- package/server/modules/database/schema.ts +27 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
- package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
- package/server/modules/notifications/index.ts +13 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/projects/services/project-management.service.ts +0 -8
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
- package/server/modules/providers/README.md +1 -1
- package/server/modules/providers/index.ts +1 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
- package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
- package/server/modules/providers/provider.routes.ts +167 -1
- package/server/modules/providers/services/mcp.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
- package/server/modules/providers/services/provider-models.service.ts +1 -1
- package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
- package/server/modules/providers/services/sessions.service.ts +79 -4
- package/server/modules/providers/services/skills.service.ts +25 -1
- package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
- package/server/modules/providers/tests/skills.test.ts +212 -0
- package/server/modules/websocket/README.md +33 -27
- package/server/modules/websocket/index.ts +1 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
- package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
- package/server/modules/websocket/services/websocket-server.service.ts +6 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
- package/server/openai-codex.js +24 -14
- package/server/opencode-cli.js +21 -8
- package/server/opencode-cli.test.js +20 -1
- package/server/routes/settings.js +6 -1
- package/server/services/notification-orchestrator.js +3 -224
- package/server/services/tests/notification-orchestrator.test.js +80 -0
- package/server/shared/interfaces.ts +15 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +84 -1
- package/server/shared/utils.ts +95 -1
- package/server/voice-proxy.js +224 -0
- package/dist/assets/index-73Xw3i9v.css +0 -32
- package/dist/assets/index-BIHaviaA.js +0 -1361
- package/dist/modelConstants.js +0 -854
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
- package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
- package/public/modelConstants.js +0 -854
- /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
- /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
- /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
package/server/claude-sdk.js
CHANGED
|
@@ -28,10 +28,14 @@ import {
|
|
|
28
28
|
} from './services/notification-orchestrator.js';
|
|
29
29
|
import { sessionsService } from './modules/providers/services/sessions.service.js';
|
|
30
30
|
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
|
|
31
|
-
import { createNormalizedMessage } from './shared/utils.js';
|
|
31
|
+
import { createCompleteMessage, createNormalizedMessage } from './shared/utils.js';
|
|
32
32
|
|
|
33
33
|
const activeSessions = new Map();
|
|
34
34
|
const pendingToolApprovals = new Map();
|
|
35
|
+
// Sessions cancelled via abort-session. The abort handler already sent the
|
|
36
|
+
// terminal `complete` (aborted: true) to the client, so the run loop must not
|
|
37
|
+
// emit a second one when its generator winds down.
|
|
38
|
+
const abortedSessionIds = new Set();
|
|
35
39
|
|
|
36
40
|
const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEOUT_MS, 10) || 55000;
|
|
37
41
|
|
|
@@ -731,14 +735,18 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
731
735
|
// Clean up temporary image files
|
|
732
736
|
await cleanupTempFiles(tempImagePaths, tempDir);
|
|
733
737
|
|
|
734
|
-
// Send completion event
|
|
735
|
-
|
|
738
|
+
// Send the terminal completion event — skipped for aborted runs, whose
|
|
739
|
+
// terminal `complete` (aborted: true) was already sent by abort-session.
|
|
740
|
+
const wasAborted = capturedSessionId ? abortedSessionIds.delete(capturedSessionId) : false;
|
|
741
|
+
if (!wasAborted) {
|
|
742
|
+
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
|
|
743
|
+
}
|
|
736
744
|
notifyRunStopped({
|
|
737
745
|
userId: ws?.userId || null,
|
|
738
746
|
provider: 'claude',
|
|
739
747
|
sessionId: capturedSessionId || sessionId || null,
|
|
740
748
|
sessionName: sessionSummary,
|
|
741
|
-
stopReason: 'completed'
|
|
749
|
+
stopReason: wasAborted ? 'aborted' : 'completed'
|
|
742
750
|
});
|
|
743
751
|
// Complete
|
|
744
752
|
|
|
@@ -753,14 +761,22 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
753
761
|
// Clean up temporary image files on error
|
|
754
762
|
await cleanupTempFiles(tempImagePaths, tempDir);
|
|
755
763
|
|
|
764
|
+
const wasAborted = capturedSessionId ? abortedSessionIds.delete(capturedSessionId) : false;
|
|
765
|
+
if (wasAborted) {
|
|
766
|
+
// The abort already produced the terminal complete; a generator throw
|
|
767
|
+
// caused by interrupt() is expected noise, not a user-facing error.
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
|
|
756
771
|
// Check if Claude CLI is installed for a clearer error message
|
|
757
772
|
const installed = await providerAuthService.isProviderInstalled('claude');
|
|
758
773
|
const errorContent = !installed
|
|
759
774
|
? 'Claude Code is not installed. Please install it first: https://docs.anthropic.com/en/docs/claude-code'
|
|
760
775
|
: error.message;
|
|
761
776
|
|
|
762
|
-
// Send error to WebSocket
|
|
777
|
+
// Send error to WebSocket, then the terminal complete
|
|
763
778
|
ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
779
|
+
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
|
|
764
780
|
notifyRunFailed({
|
|
765
781
|
userId: ws?.userId || null,
|
|
766
782
|
provider: 'claude',
|
|
@@ -787,6 +803,10 @@ async function abortClaudeSDKSession(sessionId) {
|
|
|
787
803
|
try {
|
|
788
804
|
console.log(`Aborting SDK session: ${sessionId}`);
|
|
789
805
|
|
|
806
|
+
// Mark before interrupting so the run loop knows not to emit its own
|
|
807
|
+
// terminal complete (the abort handler sends the aborted one).
|
|
808
|
+
abortedSessionIds.add(sessionId);
|
|
809
|
+
|
|
790
810
|
// Call interrupt() on the query instance
|
|
791
811
|
await session.instance.interrupt();
|
|
792
812
|
|
|
@@ -802,6 +822,8 @@ async function abortClaudeSDKSession(sessionId) {
|
|
|
802
822
|
return true;
|
|
803
823
|
} catch (error) {
|
|
804
824
|
console.error(`Error aborting session ${sessionId}:`, error);
|
|
825
|
+
// The run keeps going; let it emit its own terminal complete.
|
|
826
|
+
abortedSessionIds.delete(sessionId);
|
|
805
827
|
return false;
|
|
806
828
|
}
|
|
807
829
|
}
|
package/server/cli.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* (no args) - Start the server (default)
|
|
9
9
|
* start - Start the server
|
|
10
10
|
* sandbox - Manage Docker sandbox environments
|
|
11
|
+
* browser-use-mcp - Run Browser MCP stdio server
|
|
11
12
|
* status - Show configuration and data locations
|
|
12
13
|
* help - Show help information
|
|
13
14
|
* version - Show version information
|
|
@@ -154,12 +155,13 @@ Usage:
|
|
|
154
155
|
cloudcli [command] [options]
|
|
155
156
|
|
|
156
157
|
Commands:
|
|
157
|
-
start
|
|
158
|
-
sandbox
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
start Start the CloudCLI server (default)
|
|
159
|
+
sandbox Manage Docker sandbox environments
|
|
160
|
+
browser-use-mcp Run the Browser MCP stdio server
|
|
161
|
+
status Show configuration and data locations
|
|
162
|
+
update Update to the latest version
|
|
163
|
+
help Show this help information
|
|
164
|
+
version Show version information
|
|
163
165
|
|
|
164
166
|
Options:
|
|
165
167
|
-p, --port <port> Set server port (default: 3001)
|
|
@@ -605,6 +607,10 @@ async function startServer() {
|
|
|
605
607
|
await import('./index.js');
|
|
606
608
|
}
|
|
607
609
|
|
|
610
|
+
async function startBrowserUseMcp() {
|
|
611
|
+
await import('./browser-use-mcp.js');
|
|
612
|
+
}
|
|
613
|
+
|
|
608
614
|
// Parse CLI arguments
|
|
609
615
|
function parseArgs(args) {
|
|
610
616
|
const parsed = { command: 'start', options: {} };
|
|
@@ -658,6 +664,9 @@ async function main() {
|
|
|
658
664
|
case 'sandbox':
|
|
659
665
|
await sandboxCommand(remainingArgs || []);
|
|
660
666
|
break;
|
|
667
|
+
case 'browser-use-mcp':
|
|
668
|
+
await startBrowserUseMcp();
|
|
669
|
+
break;
|
|
661
670
|
case 'status':
|
|
662
671
|
case 'info':
|
|
663
672
|
showStatus();
|
package/server/cursor-cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { notifyRunFailed, notifyRunStopped } from './services/notification-orche
|
|
|
4
4
|
import { sessionsService } from './modules/providers/services/sessions.service.js';
|
|
5
5
|
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
|
|
6
6
|
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
|
|
7
|
-
import { createNormalizedMessage } from './shared/utils.js';
|
|
7
|
+
import { createCompleteMessage, createNormalizedMessage } from './shared/utils.js';
|
|
8
8
|
|
|
9
9
|
// Use cross-spawn on Windows for better command execution
|
|
10
10
|
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
@@ -34,6 +34,10 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
34
34
|
let sessionCreatedSent = false; // Track if we've already sent session-created event
|
|
35
35
|
let hasRetriedWithTrust = false;
|
|
36
36
|
let settled = false;
|
|
37
|
+
// The unified lifecycle contract requires exactly one terminal `complete`
|
|
38
|
+
// per run. Cursor surfaces completion twice (the `result` JSON line and
|
|
39
|
+
// the process close), so the first emission wins.
|
|
40
|
+
let completeSent = false;
|
|
37
41
|
|
|
38
42
|
// Use tools settings passed from frontend, or defaults
|
|
39
43
|
const settings = toolsSettings || {
|
|
@@ -197,15 +201,15 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
197
201
|
break;
|
|
198
202
|
|
|
199
203
|
case 'result': {
|
|
200
|
-
// Session complete —
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
204
|
+
// Session complete — terminal lifecycle event for this run
|
|
205
|
+
if (!completeSent) {
|
|
206
|
+
completeSent = true;
|
|
207
|
+
ws.send(createCompleteMessage({
|
|
208
|
+
provider: 'cursor',
|
|
209
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
210
|
+
exitCode: response.subtype === 'success' ? 0 : 1,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
209
213
|
break;
|
|
210
214
|
}
|
|
211
215
|
|
|
@@ -271,7 +275,12 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
271
275
|
return;
|
|
272
276
|
}
|
|
273
277
|
|
|
274
|
-
|
|
278
|
+
// Terminal complete — unless the `result` line already sent it, or the
|
|
279
|
+
// run was aborted (abort-session sent the aborted complete).
|
|
280
|
+
if (!completeSent && !cursorProcess.aborted) {
|
|
281
|
+
completeSent = true;
|
|
282
|
+
ws.send(createCompleteMessage({ provider: 'cursor', sessionId: finalSessionId, exitCode: code }));
|
|
283
|
+
}
|
|
275
284
|
|
|
276
285
|
if (code === 0) {
|
|
277
286
|
notifyTerminalState({ code });
|
|
@@ -297,6 +306,10 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
297
306
|
: error.message;
|
|
298
307
|
|
|
299
308
|
ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'cursor' }));
|
|
309
|
+
if (!completeSent && !cursorProcess.aborted) {
|
|
310
|
+
completeSent = true;
|
|
311
|
+
ws.send(createCompleteMessage({ provider: 'cursor', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
|
|
312
|
+
}
|
|
300
313
|
notifyTerminalState({ error });
|
|
301
314
|
|
|
302
315
|
settleOnce(() => reject(error));
|
|
@@ -314,6 +327,9 @@ function abortCursorSession(sessionId) {
|
|
|
314
327
|
const process = activeCursorProcesses.get(sessionId);
|
|
315
328
|
if (process) {
|
|
316
329
|
console.log(`Aborting Cursor session: ${sessionId}`);
|
|
330
|
+
// The abort handler sends the terminal complete (aborted: true); flag the
|
|
331
|
+
// process so its close handler does not emit a second one.
|
|
332
|
+
process.aborted = true;
|
|
317
333
|
process.kill('SIGTERM');
|
|
318
334
|
activeCursorProcesses.delete(sessionId);
|
|
319
335
|
return true;
|
package/server/gemini-cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import GeminiResponseHandler from './gemini-response-handler.js';
|
|
|
10
10
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
|
11
11
|
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
|
|
12
12
|
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
|
|
13
|
-
import { createNormalizedMessage } from './shared/utils.js';
|
|
13
|
+
import { createCompleteMessage, createNormalizedMessage } from './shared/utils.js';
|
|
14
14
|
|
|
15
15
|
// Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
|
|
16
16
|
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
@@ -129,6 +129,9 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
129
129
|
let capturedSessionId = sessionId; // Track session ID throughout the process
|
|
130
130
|
let sessionCreatedSent = false; // Track if we've already sent session-created event
|
|
131
131
|
let assistantBlocks = []; // Accumulate the full response blocks including tools
|
|
132
|
+
// Unified lifecycle contract: exactly one terminal `complete` per run
|
|
133
|
+
// (close and error handlers can both fire for spawn failures).
|
|
134
|
+
let completeSent = false;
|
|
132
135
|
|
|
133
136
|
// Use tools settings passed from frontend, or defaults
|
|
134
137
|
const settings = toolsSettings || {
|
|
@@ -486,7 +489,12 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
486
489
|
sessionManager.addMessage(finalSessionId, 'assistant', assistantBlocks);
|
|
487
490
|
}
|
|
488
491
|
|
|
489
|
-
|
|
492
|
+
// Terminal complete — skipped for aborted runs (abort-session
|
|
493
|
+
// already sent the aborted complete on this run's behalf).
|
|
494
|
+
if (!completeSent && !geminiProcess.aborted) {
|
|
495
|
+
completeSent = true;
|
|
496
|
+
ws.send(createCompleteMessage({ provider: 'gemini', sessionId: finalSessionId, exitCode: code }));
|
|
497
|
+
}
|
|
490
498
|
|
|
491
499
|
// Clean up temporary image files if any
|
|
492
500
|
if (geminiProcess.tempImagePaths && geminiProcess.tempImagePaths.length > 0) {
|
|
@@ -566,6 +574,10 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
566
574
|
|
|
567
575
|
const errorSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId;
|
|
568
576
|
ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: errorSessionId, provider: 'gemini' }));
|
|
577
|
+
if (!completeSent && !geminiProcess.aborted) {
|
|
578
|
+
completeSent = true;
|
|
579
|
+
ws.send(createCompleteMessage({ provider: 'gemini', sessionId: errorSessionId, exitCode: 1 }));
|
|
580
|
+
}
|
|
569
581
|
notifyTerminalState({ error });
|
|
570
582
|
|
|
571
583
|
reject(error);
|
|
@@ -590,6 +602,9 @@ function abortGeminiSession(sessionId) {
|
|
|
590
602
|
|
|
591
603
|
if (geminiProc) {
|
|
592
604
|
try {
|
|
605
|
+
// The abort handler sends the terminal complete (aborted: true);
|
|
606
|
+
// flag the process so its close handler does not emit a second one.
|
|
607
|
+
geminiProc.aborted = true;
|
|
593
608
|
geminiProc.kill('SIGTERM');
|
|
594
609
|
setTimeout(() => {
|
|
595
610
|
if (activeGeminiProcesses.has(processKey)) {
|
package/server/index.js
CHANGED
|
@@ -22,35 +22,24 @@ import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
|
|
22
22
|
import {
|
|
23
23
|
queryClaudeSDK,
|
|
24
24
|
abortClaudeSDKSession,
|
|
25
|
-
isClaudeSDKSessionActive,
|
|
26
|
-
getActiveClaudeSDKSessions,
|
|
27
25
|
resolveToolApproval,
|
|
28
26
|
getPendingApprovalsForSession,
|
|
29
|
-
reconnectSessionWriter,
|
|
30
27
|
} from './claude-sdk.js';
|
|
31
28
|
import {
|
|
32
29
|
spawnCursor,
|
|
33
30
|
abortCursorSession,
|
|
34
|
-
isCursorSessionActive,
|
|
35
|
-
getActiveCursorSessions,
|
|
36
31
|
} from './cursor-cli.js';
|
|
37
32
|
import {
|
|
38
33
|
queryCodex,
|
|
39
34
|
abortCodexSession,
|
|
40
|
-
isCodexSessionActive,
|
|
41
|
-
getActiveCodexSessions,
|
|
42
35
|
} from './openai-codex.js';
|
|
43
36
|
import {
|
|
44
37
|
spawnGemini,
|
|
45
38
|
abortGeminiSession,
|
|
46
|
-
isGeminiSessionActive,
|
|
47
|
-
getActiveGeminiSessions,
|
|
48
39
|
} from './gemini-cli.js';
|
|
49
40
|
import {
|
|
50
41
|
spawnOpenCode,
|
|
51
42
|
abortOpenCodeSession,
|
|
52
|
-
isOpenCodeSessionActive,
|
|
53
|
-
getActiveOpenCodeSessions,
|
|
54
43
|
} from './opencode-cli.js';
|
|
55
44
|
import sessionManager from './sessionManager.js';
|
|
56
45
|
import {
|
|
@@ -68,10 +57,15 @@ import commandsRoutes from './routes/commands.js';
|
|
|
68
57
|
import settingsRoutes from './routes/settings.js';
|
|
69
58
|
import agentRoutes from './routes/agent.js';
|
|
70
59
|
import projectModuleRoutes from './modules/projects/projects.routes.js';
|
|
60
|
+
import notificationRoutes from './modules/notifications/notifications.routes.js';
|
|
71
61
|
import userRoutes from './routes/user.js';
|
|
72
62
|
import geminiRoutes from './routes/gemini.js';
|
|
73
63
|
import pluginsRoutes from './routes/plugins.js';
|
|
74
64
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
65
|
+
import voiceRoutes from './voice-proxy.js';
|
|
66
|
+
import browserUseRoutes from './modules/browser-use/browser-use.routes.js';
|
|
67
|
+
import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js';
|
|
68
|
+
import { browserUseService } from './modules/browser-use/browser-use.service.js';
|
|
75
69
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
76
70
|
import { initializeDatabase, projectsDb, sessionsDb } from './modules/database/index.js';
|
|
77
71
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
@@ -84,6 +78,19 @@ const __dirname = getModuleDir(import.meta.url);
|
|
|
84
78
|
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
|
85
79
|
const APP_ROOT = findAppRoot(__dirname);
|
|
86
80
|
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
|
|
81
|
+
// Version of the code that is actually running, captured once at process
|
|
82
|
+
// startup. This intentionally does NOT re-read package.json per request: after
|
|
83
|
+
// an update replaces the files on disk, package.json reflects the NEW version
|
|
84
|
+
// while this long-lived process still runs the OLD code. The frontend bundle is
|
|
85
|
+
// rebuilt on update, so a mismatch between this value and the frontend's
|
|
86
|
+
// build-time version means the server was updated but not restarted.
|
|
87
|
+
const RUNNING_VERSION = (() => {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(fs.readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')).version || null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
87
94
|
const MAX_FILE_UPLOAD_SIZE_MB = 200;
|
|
88
95
|
const MAX_FILE_UPLOAD_SIZE_BYTES = MAX_FILE_UPLOAD_SIZE_MB * 1024 * 1024;
|
|
89
96
|
const MAX_FILE_UPLOAD_COUNT = 20;
|
|
@@ -105,32 +112,35 @@ const wss = createWebSocketServer(server, {
|
|
|
105
112
|
authenticateWebSocket,
|
|
106
113
|
},
|
|
107
114
|
chat: {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
spawnFns: {
|
|
116
|
+
claude: queryClaudeSDK,
|
|
117
|
+
cursor: spawnCursor,
|
|
118
|
+
codex: queryCodex,
|
|
119
|
+
gemini: spawnGemini,
|
|
120
|
+
opencode: spawnOpenCode,
|
|
121
|
+
},
|
|
122
|
+
abortFns: {
|
|
123
|
+
claude: abortClaudeSDKSession,
|
|
124
|
+
cursor: abortCursorSession,
|
|
125
|
+
codex: abortCodexSession,
|
|
126
|
+
gemini: abortGeminiSession,
|
|
127
|
+
opencode: abortOpenCodeSession,
|
|
128
|
+
},
|
|
118
129
|
resolveToolApproval,
|
|
119
|
-
isClaudeSDKSessionActive,
|
|
120
|
-
isCursorSessionActive,
|
|
121
|
-
isCodexSessionActive,
|
|
122
|
-
isGeminiSessionActive,
|
|
123
|
-
isOpenCodeSessionActive,
|
|
124
|
-
reconnectSessionWriter,
|
|
125
130
|
getPendingApprovalsForSession,
|
|
126
|
-
getActiveClaudeSDKSessions,
|
|
127
|
-
getActiveCursorSessions,
|
|
128
|
-
getActiveCodexSessions,
|
|
129
|
-
getActiveGeminiSessions,
|
|
130
|
-
getActiveOpenCodeSessions,
|
|
131
131
|
},
|
|
132
132
|
shell: {
|
|
133
|
-
|
|
133
|
+
resolveProviderSessionId: (sessionId, provider) => {
|
|
134
|
+
const dbSession = sessionsDb.getSessionById(sessionId);
|
|
135
|
+
const legacyGeminiSession =
|
|
136
|
+
provider === 'gemini' ? sessionManager.getSession(sessionId) : null;
|
|
137
|
+
|
|
138
|
+
if (dbSession) {
|
|
139
|
+
return dbSession.provider_session_id ?? legacyGeminiSession?.cliSessionId ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return legacyGeminiSession?.cliSessionId;
|
|
143
|
+
},
|
|
134
144
|
stripAnsiSequences,
|
|
135
145
|
normalizeDetectedUrl,
|
|
136
146
|
extractUrlsFromText,
|
|
@@ -161,7 +171,8 @@ app.get('/health', (req, res) => {
|
|
|
161
171
|
res.json({
|
|
162
172
|
status: 'ok',
|
|
163
173
|
timestamp: new Date().toISOString(),
|
|
164
|
-
installMode
|
|
174
|
+
installMode,
|
|
175
|
+
version: RUNNING_VERSION
|
|
165
176
|
});
|
|
166
177
|
});
|
|
167
178
|
|
|
@@ -192,6 +203,8 @@ app.use('/api/commands', authenticateToken, commandsRoutes);
|
|
|
192
203
|
// Settings API Routes (protected)
|
|
193
204
|
app.use('/api/settings', authenticateToken, settingsRoutes);
|
|
194
205
|
|
|
206
|
+
app.use('/api/notifications', authenticateToken, notificationRoutes);
|
|
207
|
+
|
|
195
208
|
// User API Routes (protected)
|
|
196
209
|
app.use('/api/user', authenticateToken, userRoutes);
|
|
197
210
|
|
|
@@ -201,12 +214,20 @@ app.use('/api/gemini', authenticateToken, geminiRoutes);
|
|
|
201
214
|
// Plugins API Routes (protected)
|
|
202
215
|
app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
|
203
216
|
|
|
217
|
+
// Browser MCP bridge API (local token protected)
|
|
218
|
+
app.use('/api/browser-use-mcp', browserUseMcpRoutes);
|
|
219
|
+
|
|
220
|
+
// Browser API Routes (protected)
|
|
221
|
+
app.use('/api/browser-use', authenticateToken, browserUseRoutes);
|
|
222
|
+
|
|
204
223
|
// Unified provider MCP routes (protected)
|
|
205
224
|
app.use('/api/providers', authenticateToken, providerRoutes);
|
|
206
225
|
|
|
207
226
|
// Agent API Routes (uses API key authentication)
|
|
208
227
|
app.use('/api/agent', agentRoutes);
|
|
209
228
|
|
|
229
|
+
app.use('/api/voice', authenticateToken, voiceRoutes);
|
|
230
|
+
|
|
210
231
|
// Serve public files (like api-docs.html)
|
|
211
232
|
app.use(express.static(path.join(APP_ROOT, 'public')));
|
|
212
233
|
|
|
@@ -1143,7 +1164,6 @@ app.post('/api/projects/:projectId/upload-images', authenticateToken, async (req
|
|
|
1143
1164
|
app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticateToken, async (req, res) => {
|
|
1144
1165
|
try {
|
|
1145
1166
|
const { projectId, sessionId } = req.params;
|
|
1146
|
-
const { provider = 'claude' } = req.query;
|
|
1147
1167
|
const homeDir = os.homedir();
|
|
1148
1168
|
|
|
1149
1169
|
// Allow only safe characters in sessionId
|
|
@@ -1152,6 +1172,18 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1152
1172
|
return res.status(400).json({ error: 'Invalid sessionId' });
|
|
1153
1173
|
}
|
|
1154
1174
|
|
|
1175
|
+
// Provider artifacts on disk (JSONL file names, OpenCode sqlite rows)
|
|
1176
|
+
// are keyed by the provider-native session id, while the caller sends
|
|
1177
|
+
// the app-facing id. Resolve provider and id mapping from the indexed
|
|
1178
|
+
// session row so the frontend does not choose provider-specific paths.
|
|
1179
|
+
const sessionRow = sessionsDb.getSessionById(safeSessionId);
|
|
1180
|
+
if (!sessionRow) {
|
|
1181
|
+
return res.status(404).json({ error: 'Session not found', sessionId: safeSessionId });
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const provider = sessionRow.provider || 'claude';
|
|
1185
|
+
const providerNativeSessionId = sessionRow?.provider_session_id || safeSessionId;
|
|
1186
|
+
|
|
1155
1187
|
// Handle Cursor sessions - they use SQLite and don't have token usage info
|
|
1156
1188
|
if (provider === 'cursor') {
|
|
1157
1189
|
return res.json({
|
|
@@ -1252,7 +1284,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1252
1284
|
tokens_cache_write AS cacheWriteTokens
|
|
1253
1285
|
FROM session
|
|
1254
1286
|
WHERE id = ?
|
|
1255
|
-
`).get(
|
|
1287
|
+
`).get(providerNativeSessionId);
|
|
1256
1288
|
|
|
1257
1289
|
if (!row) {
|
|
1258
1290
|
return res.status(404).json({ error: 'OpenCode session not found', sessionId: safeSessionId });
|
|
@@ -1293,7 +1325,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1293
1325
|
if (entry.isDirectory()) {
|
|
1294
1326
|
const found = await findSessionFile(fullPath);
|
|
1295
1327
|
if (found) return found;
|
|
1296
|
-
} else if (entry.name.includes(
|
|
1328
|
+
} else if (entry.name.includes(providerNativeSessionId) && entry.name.endsWith('.jsonl')) {
|
|
1297
1329
|
return fullPath;
|
|
1298
1330
|
}
|
|
1299
1331
|
}
|
|
@@ -1377,12 +1409,19 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1377
1409
|
const encodedPath = projectPath.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
1378
1410
|
const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath);
|
|
1379
1411
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
//
|
|
1383
|
-
|
|
1384
|
-
if (
|
|
1385
|
-
|
|
1412
|
+
// Prefer the indexed transcript path (already produced by the trusted
|
|
1413
|
+
// session synchronizer); fall back to the conventional location
|
|
1414
|
+
// derived from the provider-native session id.
|
|
1415
|
+
let jsonlPath = sessionRow?.jsonl_path;
|
|
1416
|
+
if (!jsonlPath) {
|
|
1417
|
+
jsonlPath = path.join(projectDir, `${providerNativeSessionId}.jsonl`);
|
|
1418
|
+
|
|
1419
|
+
// Constrain the constructed path to projectDir (the id is
|
|
1420
|
+
// caller-influenced in this fallback branch).
|
|
1421
|
+
const rel = path.relative(path.resolve(projectDir), path.resolve(jsonlPath));
|
|
1422
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
1423
|
+
return res.status(400).json({ error: 'Invalid path' });
|
|
1424
|
+
}
|
|
1386
1425
|
}
|
|
1387
1426
|
|
|
1388
1427
|
// Read and parse the JSONL file
|
|
@@ -1646,6 +1685,40 @@ const SERVER_PORT = process.env.SERVER_PORT || 3001;
|
|
|
1646
1685
|
const HOST = process.env.HOST || '0.0.0.0';
|
|
1647
1686
|
const DISPLAY_HOST = getConnectableHost(HOST);
|
|
1648
1687
|
const VITE_PORT = process.env.VITE_PORT || 5173;
|
|
1688
|
+
const LOCAL_SERVER_MARKER_PATH = path.join(os.homedir(), '.cloudcli', 'local-server.json');
|
|
1689
|
+
|
|
1690
|
+
async function writeLocalServerMarker() {
|
|
1691
|
+
const marker = {
|
|
1692
|
+
pid: process.pid,
|
|
1693
|
+
host: HOST,
|
|
1694
|
+
port: Number.parseInt(String(SERVER_PORT), 10),
|
|
1695
|
+
url: `http://${DISPLAY_HOST}:${SERVER_PORT}`,
|
|
1696
|
+
installMode,
|
|
1697
|
+
appRoot: APP_ROOT,
|
|
1698
|
+
updatedAt: new Date().toISOString(),
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
await fsPromises.mkdir(path.dirname(LOCAL_SERVER_MARKER_PATH), { recursive: true });
|
|
1702
|
+
await fsPromises.writeFile(LOCAL_SERVER_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf8');
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
async function removeLocalServerMarker() {
|
|
1706
|
+
try {
|
|
1707
|
+
const raw = await fsPromises.readFile(LOCAL_SERVER_MARKER_PATH, 'utf8');
|
|
1708
|
+
const marker = JSON.parse(raw);
|
|
1709
|
+
if (marker.pid && marker.pid !== process.pid) return;
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
if (error.code === 'ENOENT') return;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
try {
|
|
1715
|
+
await fsPromises.unlink(LOCAL_SERVER_MARKER_PATH);
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
if (error.code !== 'ENOENT') {
|
|
1718
|
+
console.warn('[WARN] Could not remove local server marker:', error.message);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1649
1722
|
|
|
1650
1723
|
// Initialize database and start server
|
|
1651
1724
|
async function startServer() {
|
|
@@ -1672,6 +1745,9 @@ async function startServer() {
|
|
|
1672
1745
|
|
|
1673
1746
|
server.listen(SERVER_PORT, HOST, async () => {
|
|
1674
1747
|
const appInstallPath = APP_ROOT;
|
|
1748
|
+
await writeLocalServerMarker().catch((error) => {
|
|
1749
|
+
console.warn('[WARN] Could not write local server marker:', error.message);
|
|
1750
|
+
});
|
|
1675
1751
|
|
|
1676
1752
|
console.log('');
|
|
1677
1753
|
console.log(c.dim('═'.repeat(63)));
|
|
@@ -1694,12 +1770,26 @@ async function startServer() {
|
|
|
1694
1770
|
|
|
1695
1771
|
await closeSessionsWatcher();
|
|
1696
1772
|
// Clean up plugin processes on shutdown
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1773
|
+
const shutdownRuntimeServices = async () => {
|
|
1774
|
+
try {
|
|
1775
|
+
await browserUseService.stopAllSessions();
|
|
1776
|
+
} catch (err) {
|
|
1777
|
+
console.error('[Browser] Error stopping sessions during shutdown:', err?.message || err);
|
|
1778
|
+
}
|
|
1779
|
+
try {
|
|
1780
|
+
await stopAllPlugins();
|
|
1781
|
+
} catch (err) {
|
|
1782
|
+
console.error('[Plugins] Error stopping plugins during shutdown:', err?.message || err);
|
|
1783
|
+
}
|
|
1784
|
+
try {
|
|
1785
|
+
await removeLocalServerMarker();
|
|
1786
|
+
} catch (err) {
|
|
1787
|
+
console.error('[Local Server] Error removing server marker during shutdown:', err?.message || err);
|
|
1788
|
+
}
|
|
1699
1789
|
process.exit(0);
|
|
1700
1790
|
};
|
|
1701
|
-
process.on('SIGTERM', () => void
|
|
1702
|
-
process.on('SIGINT', () => void
|
|
1791
|
+
process.on('SIGTERM', () => void shutdownRuntimeServices());
|
|
1792
|
+
process.on('SIGINT', () => void shutdownRuntimeServices());
|
|
1703
1793
|
} catch (error) {
|
|
1704
1794
|
console.error('[ERROR] Failed to start server:', error);
|
|
1705
1795
|
process.exit(1);
|
package/server/load-env.js
CHANGED
|
@@ -22,7 +22,7 @@ try {
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
} catch (e) {
|
|
25
|
-
console.
|
|
25
|
+
console.error('No .env file found or error reading it:', e.message);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Keep the default database in a stable user-level location so rebuilding dist-server
|