@cloudcli-ai/cloudcli 1.30.0 → 1.31.0
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/dist/assets/{index-DI22MZMb.css → index-DF8TTVHl.css} +1 -1
- package/dist/assets/index-DbZAcc9z.js +1262 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +115 -955
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/middleware/auth.js +1 -1
- package/dist-server/server/middleware/auth.js.map +1 -1
- package/dist-server/server/modules/database/connection.js +125 -0
- package/dist-server/server/modules/database/connection.js.map +1 -0
- package/dist-server/server/modules/database/index.js +13 -0
- package/dist-server/server/modules/database/index.js.map +1 -0
- package/dist-server/server/modules/database/init-db.js +18 -0
- package/dist-server/server/modules/database/init-db.js.map +1 -0
- package/dist-server/server/modules/database/migrations.js +370 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -0
- package/dist-server/server/modules/database/repositories/api-keys.js +72 -0
- package/dist-server/server/modules/database/repositories/api-keys.js.map +1 -0
- package/dist-server/server/modules/database/repositories/app-config.js +47 -0
- package/dist-server/server/modules/database/repositories/app-config.js.map +1 -0
- package/dist-server/server/modules/database/repositories/credentials.js +68 -0
- package/dist-server/server/modules/database/repositories/credentials.js.map +1 -0
- package/dist-server/server/modules/database/repositories/github-tokens.js +54 -0
- package/dist-server/server/modules/database/repositories/github-tokens.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +72 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -0
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js +64 -0
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/repositories/projects.db.js +160 -0
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +49 -0
- package/dist-server/server/modules/database/repositories/push-subscriptions.js.map +1 -0
- package/dist-server/server/modules/database/repositories/scan-state.db.js +31 -0
- package/dist-server/server/modules/database/repositories/scan-state.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +109 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/users.js +88 -0
- package/dist-server/server/modules/database/repositories/users.js.map +1 -0
- package/dist-server/server/modules/database/repositories/vapid-keys.js +38 -0
- package/dist-server/server/modules/database/repositories/vapid-keys.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +143 -0
- package/dist-server/server/modules/database/schema.js.map +1 -0
- package/dist-server/server/modules/projects/index.js +4 -0
- package/dist-server/server/modules/projects/index.js.map +1 -0
- package/dist-server/server/modules/projects/projects.routes.js +188 -0
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js +219 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-delete.service.js +65 -0
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +93 -0
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-star.service.js +60 -0
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js +167 -0
- package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +169 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js +126 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-management.service.test.js +85 -0
- package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-star.service.test.js +95 -0
- package/dist-server/server/modules/projects/tests/project-star.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js +87 -0
- package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/index.js +4 -0
- package/dist-server/server/modules/providers/index.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +63 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +144 -7
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +71 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +225 -13
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +127 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +296 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +277 -10
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +172 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +868 -0
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +56 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +234 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions.service.js +78 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +3 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +188 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +52 -0
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +334 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js +32 -0
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +36 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-state.service.js +14 -0
- package/dist-server/server/modules/websocket/services/websocket-state.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js +32 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js.map +1 -0
- package/dist-server/server/routes/agent.js +10 -17
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/auth.js +3 -1
- package/dist-server/server/routes/auth.js.map +1 -1
- package/dist-server/server/routes/commands.js +1 -1
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/routes/gemini.js +2 -2
- package/dist-server/server/routes/gemini.js.map +1 -1
- package/dist-server/server/routes/git.js +33 -29
- package/dist-server/server/routes/git.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/routes/taskmaster.js +97 -110
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/dist-server/server/routes/user.js +1 -1
- package/dist-server/server/routes/user.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +2 -2
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/vapid-keys.js +2 -1
- package/dist-server/server/services/vapid-keys.js.map +1 -1
- package/dist-server/server/shared/types.js +0 -1
- package/dist-server/server/shared/types.js.map +1 -1
- package/dist-server/server/shared/utils.js +408 -7
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/utils/taskmaster-websocket.js +18 -12
- package/dist-server/server/utils/taskmaster-websocket.js.map +1 -1
- package/dist-server/shared/modelConstants.js +2 -1
- package/dist-server/shared/modelConstants.js.map +1 -1
- package/package.json +3 -1
- package/server/index.js +146 -1012
- package/server/middleware/auth.js +1 -1
- package/server/modules/database/connection.ts +143 -0
- package/server/modules/database/index.ts +12 -0
- package/server/modules/database/init-db.ts +17 -0
- package/server/modules/database/migrations.ts +442 -0
- package/server/modules/database/repositories/api-keys.ts +119 -0
- package/server/modules/database/repositories/app-config.ts +53 -0
- package/server/modules/database/repositories/credentials.ts +106 -0
- package/server/modules/database/repositories/github-tokens.ts +100 -0
- package/server/modules/database/repositories/notification-preferences.ts +103 -0
- package/server/modules/database/repositories/projects.db.integration.test.ts +72 -0
- package/server/modules/database/repositories/projects.db.ts +183 -0
- package/server/modules/database/repositories/push-subscriptions.ts +80 -0
- package/server/modules/database/repositories/scan-state.db.ts +42 -0
- package/server/modules/database/repositories/sessions.db.ts +174 -0
- package/server/modules/database/repositories/users.ts +140 -0
- package/server/modules/database/repositories/vapid-keys.ts +57 -0
- package/server/modules/database/schema.ts +152 -0
- package/server/modules/projects/index.ts +6 -0
- package/server/modules/projects/projects.routes.ts +247 -0
- package/server/modules/projects/services/project-clone.service.ts +321 -0
- package/server/modules/projects/services/project-delete.service.ts +75 -0
- package/server/modules/projects/services/project-management.service.ts +150 -0
- package/server/modules/projects/services/project-star.service.ts +78 -0
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +285 -0
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -0
- package/server/modules/projects/tests/project-management.service.test.ts +117 -0
- package/server/modules/projects/tests/project-star.service.test.ts +123 -0
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -0
- package/server/modules/providers/index.ts +4 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +110 -0
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +182 -13
- package/server/modules/providers/list/claude/claude.provider.ts +3 -1
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +119 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +262 -17
- package/server/modules/providers/list/codex/codex.provider.ts +3 -1
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +176 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +3 -1
- package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +401 -0
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +327 -13
- package/server/modules/providers/list/gemini/gemini.provider.ts +3 -1
- package/server/modules/providers/provider.routes.ts +220 -12
- package/server/modules/providers/services/session-conversations-search.service.ts +1167 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +74 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +283 -0
- package/server/modules/providers/services/sessions.service.ts +89 -4
- package/server/modules/providers/shared/base/abstract.provider.ts +8 -1
- package/server/modules/websocket/README.md +267 -0
- package/server/modules/websocket/index.ts +2 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +271 -0
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +453 -0
- package/server/modules/websocket/services/websocket-auth.service.ts +54 -0
- package/server/modules/websocket/services/websocket-server.service.ts +58 -0
- package/server/modules/websocket/services/websocket-state.service.ts +16 -0
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -0
- package/server/routes/agent.js +11 -16
- package/server/routes/auth.js +4 -2
- package/server/routes/commands.js +1 -1
- package/server/routes/gemini.js +3 -2
- package/server/routes/git.js +33 -28
- package/server/routes/settings.js +1 -1
- package/server/routes/taskmaster.js +108 -111
- package/server/routes/user.js +1 -1
- package/server/services/notification-orchestrator.js +3 -2
- package/server/services/vapid-keys.js +2 -1
- package/server/shared/interfaces.ts +39 -1
- package/server/shared/types.ts +185 -30
- package/server/shared/utils.ts +485 -9
- package/server/utils/taskmaster-websocket.js +19 -13
- package/shared/modelConstants.js +2 -1
- package/dist/assets/index-RX-W_g_Q.js +0 -1262
- package/dist-server/server/database/db.js +0 -535
- package/dist-server/server/database/db.js.map +0 -1
- package/dist-server/server/database/schema.js +0 -97
- package/dist-server/server/database/schema.js.map +0 -1
- package/dist-server/server/projects.js +0 -2325
- package/dist-server/server/projects.js.map +0 -1
- package/dist-server/server/routes/codex.js +0 -18
- package/dist-server/server/routes/codex.js.map +0 -1
- package/dist-server/server/routes/messages.js +0 -56
- package/dist-server/server/routes/messages.js.map +0 -1
- package/dist-server/server/routes/projects.js +0 -505
- package/dist-server/server/routes/projects.js.map +0 -1
- package/server/database/db.js +0 -593
- package/server/database/schema.js +0 -102
- package/server/projects.js +0 -2555
- package/server/routes/codex.js +0 -19
- package/server/routes/messages.js +0 -61
- package/server/routes/projects.js +0 -548
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import pty, { type IPty } from 'node-pty';
|
|
6
|
+
import { WebSocket, type RawData } from 'ws';
|
|
7
|
+
|
|
8
|
+
import { parseIncomingJsonObject } from '@/shared/utils.js';
|
|
9
|
+
|
|
10
|
+
type ShellIncomingMessage = {
|
|
11
|
+
type?: string;
|
|
12
|
+
data?: string;
|
|
13
|
+
cols?: number;
|
|
14
|
+
rows?: number;
|
|
15
|
+
projectPath?: string;
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
hasSession?: boolean;
|
|
18
|
+
provider?: string;
|
|
19
|
+
initialCommand?: string;
|
|
20
|
+
isPlainShell?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type PtySessionEntry = {
|
|
24
|
+
pty: IPty;
|
|
25
|
+
ws: WebSocket | null;
|
|
26
|
+
buffer: string[];
|
|
27
|
+
timeoutId: NodeJS.Timeout | null;
|
|
28
|
+
projectPath: string;
|
|
29
|
+
sessionId: string | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ptySessionsMap = new Map<string, PtySessionEntry>();
|
|
33
|
+
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
|
|
34
|
+
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
|
|
35
|
+
|
|
36
|
+
type ShellWebSocketDependencies = {
|
|
37
|
+
getSessionById: (sessionId: string) => { cliSessionId?: string } | null | undefined;
|
|
38
|
+
stripAnsiSequences: (content: string) => string;
|
|
39
|
+
normalizeDetectedUrl: (url: string) => string | null;
|
|
40
|
+
extractUrlsFromText: (content: string) => string[];
|
|
41
|
+
shouldAutoOpenUrlFromOutput: (content: string) => boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reads a string field from untyped payloads and falls back when absent.
|
|
46
|
+
*/
|
|
47
|
+
function readString(value: unknown, fallback = ''): string {
|
|
48
|
+
return typeof value === 'string' ? value : fallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reads a boolean field from untyped payloads and falls back when absent.
|
|
53
|
+
*/
|
|
54
|
+
function readBoolean(value: unknown, fallback = false): boolean {
|
|
55
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reads a finite number field from untyped payloads and falls back when absent.
|
|
60
|
+
*/
|
|
61
|
+
function readNumber(value: unknown, fallback: number): number {
|
|
62
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parses incoming websocket shell messages and keeps processing safe when
|
|
67
|
+
* malformed payloads are received.
|
|
68
|
+
*/
|
|
69
|
+
function parseShellMessage(rawMessage: RawData): ShellIncomingMessage | null {
|
|
70
|
+
const payload = parseIncomingJsonObject(rawMessage);
|
|
71
|
+
if (!payload) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return payload as ShellIncomingMessage;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolves provider command line for plain shell and agent-backed shell modes.
|
|
80
|
+
*/
|
|
81
|
+
function buildShellCommand(
|
|
82
|
+
message: ShellIncomingMessage,
|
|
83
|
+
dependencies: ShellWebSocketDependencies
|
|
84
|
+
): string {
|
|
85
|
+
const hasSession = readBoolean(message.hasSession);
|
|
86
|
+
const sessionId = readString(message.sessionId);
|
|
87
|
+
const initialCommand = readString(message.initialCommand);
|
|
88
|
+
const provider = readString(message.provider, 'claude');
|
|
89
|
+
const safeSessionIdPattern = /^[a-zA-Z0-9_.\-:]+$/;
|
|
90
|
+
const isPlainShell =
|
|
91
|
+
readBoolean(message.isPlainShell) ||
|
|
92
|
+
(!!initialCommand && !hasSession) ||
|
|
93
|
+
provider === 'plain-shell';
|
|
94
|
+
|
|
95
|
+
if (isPlainShell) {
|
|
96
|
+
return initialCommand;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (provider === 'cursor') {
|
|
100
|
+
if (hasSession && sessionId) {
|
|
101
|
+
return `cursor-agent --resume="${sessionId}"`;
|
|
102
|
+
}
|
|
103
|
+
return 'cursor-agent';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (provider === 'codex') {
|
|
107
|
+
if (hasSession && sessionId) {
|
|
108
|
+
if (os.platform() === 'win32') {
|
|
109
|
+
return `codex resume "${sessionId}"; if ($LASTEXITCODE -ne 0) { codex }`;
|
|
110
|
+
}
|
|
111
|
+
return `codex resume "${sessionId}" || codex`;
|
|
112
|
+
}
|
|
113
|
+
return 'codex';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (provider === 'gemini') {
|
|
117
|
+
const command = initialCommand || 'gemini';
|
|
118
|
+
let resumeId = sessionId;
|
|
119
|
+
if (hasSession && sessionId) {
|
|
120
|
+
try {
|
|
121
|
+
const existingSession = dependencies.getSessionById(sessionId);
|
|
122
|
+
if (existingSession && existingSession.cliSessionId) {
|
|
123
|
+
resumeId = existingSession.cliSessionId;
|
|
124
|
+
if (!safeSessionIdPattern.test(resumeId)) {
|
|
125
|
+
resumeId = '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Failed to get Gemini CLI session ID:', error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (hasSession && resumeId) {
|
|
134
|
+
return `${command} --resume "${resumeId}"`;
|
|
135
|
+
}
|
|
136
|
+
return command;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const command = initialCommand || 'claude';
|
|
140
|
+
if (hasSession && sessionId) {
|
|
141
|
+
if (os.platform() === 'win32') {
|
|
142
|
+
return `claude --resume "${sessionId}"; if ($LASTEXITCODE -ne 0) { claude }`;
|
|
143
|
+
}
|
|
144
|
+
return `claude --resume "${sessionId}" || claude`;
|
|
145
|
+
}
|
|
146
|
+
return command;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Handles websocket connections used by the standalone shell terminal UI.
|
|
151
|
+
*/
|
|
152
|
+
export function handleShellConnection(
|
|
153
|
+
ws: WebSocket,
|
|
154
|
+
dependencies: ShellWebSocketDependencies
|
|
155
|
+
): void {
|
|
156
|
+
console.log('[INFO] Shell websocket connected');
|
|
157
|
+
|
|
158
|
+
let shellProcess: IPty | null = null;
|
|
159
|
+
let ptySessionKey: string | null = null;
|
|
160
|
+
let urlDetectionBuffer = '';
|
|
161
|
+
const announcedAuthUrls = new Set<string>();
|
|
162
|
+
|
|
163
|
+
ws.on('message', async (rawMessage) => {
|
|
164
|
+
try {
|
|
165
|
+
const data = parseShellMessage(rawMessage);
|
|
166
|
+
if (!data?.type) {
|
|
167
|
+
throw new Error('Invalid websocket payload');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (data.type === 'init') {
|
|
171
|
+
const projectPath = readString(data.projectPath, process.cwd());
|
|
172
|
+
const sessionId = readString(data.sessionId) || null;
|
|
173
|
+
const hasSession = readBoolean(data.hasSession);
|
|
174
|
+
const provider = readString(data.provider, 'claude');
|
|
175
|
+
const initialCommand = readString(data.initialCommand);
|
|
176
|
+
const isPlainShell =
|
|
177
|
+
readBoolean(data.isPlainShell) ||
|
|
178
|
+
(!!initialCommand && !hasSession) ||
|
|
179
|
+
provider === 'plain-shell';
|
|
180
|
+
|
|
181
|
+
urlDetectionBuffer = '';
|
|
182
|
+
announcedAuthUrls.clear();
|
|
183
|
+
|
|
184
|
+
const isLoginCommand =
|
|
185
|
+
!!initialCommand &&
|
|
186
|
+
(initialCommand.includes('setup-token') ||
|
|
187
|
+
initialCommand.includes('cursor-agent login') ||
|
|
188
|
+
initialCommand.includes('auth login'));
|
|
189
|
+
|
|
190
|
+
const commandSuffix =
|
|
191
|
+
isPlainShell && initialCommand
|
|
192
|
+
? `_cmd_${Buffer.from(initialCommand).toString('base64').slice(0, 16)}`
|
|
193
|
+
: '';
|
|
194
|
+
ptySessionKey = `${projectPath}_${sessionId ?? 'default'}${commandSuffix}`;
|
|
195
|
+
|
|
196
|
+
if (isLoginCommand) {
|
|
197
|
+
const oldSession = ptySessionsMap.get(ptySessionKey);
|
|
198
|
+
if (oldSession) {
|
|
199
|
+
if (oldSession.timeoutId) {
|
|
200
|
+
clearTimeout(oldSession.timeoutId);
|
|
201
|
+
}
|
|
202
|
+
oldSession.pty.kill();
|
|
203
|
+
ptySessionsMap.delete(ptySessionKey);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const existingSession = isLoginCommand ? null : ptySessionsMap.get(ptySessionKey);
|
|
208
|
+
if (existingSession) {
|
|
209
|
+
shellProcess = existingSession.pty;
|
|
210
|
+
if (existingSession.timeoutId) {
|
|
211
|
+
clearTimeout(existingSession.timeoutId);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
ws.send(
|
|
215
|
+
JSON.stringify({
|
|
216
|
+
type: 'output',
|
|
217
|
+
data: '\x1b[36m[Reconnected to existing session]\x1b[0m\r\n',
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (existingSession.buffer.length > 0) {
|
|
222
|
+
existingSession.buffer.forEach((bufferedData) => {
|
|
223
|
+
ws.send(
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
type: 'output',
|
|
226
|
+
data: bufferedData,
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
existingSession.ws = ws;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
237
|
+
try {
|
|
238
|
+
const stats = fs.statSync(resolvedProjectPath);
|
|
239
|
+
if (!stats.isDirectory()) {
|
|
240
|
+
throw new Error('Not a directory');
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Invalid project path' }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const safeSessionIdPattern = /^[a-zA-Z0-9_.\-:]+$/;
|
|
248
|
+
if (sessionId && !safeSessionIdPattern.test(sessionId)) {
|
|
249
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Invalid session ID' }));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const shellCommand = buildShellCommand(data, dependencies);
|
|
254
|
+
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
|
|
255
|
+
const shellArgs =
|
|
256
|
+
os.platform() === 'win32' ? ['-Command', shellCommand] : ['-c', shellCommand];
|
|
257
|
+
const termCols = readNumber(data.cols, 80);
|
|
258
|
+
const termRows = readNumber(data.rows, 24);
|
|
259
|
+
|
|
260
|
+
shellProcess = pty.spawn(shell, shellArgs, {
|
|
261
|
+
name: 'xterm-256color',
|
|
262
|
+
cols: termCols,
|
|
263
|
+
rows: termRows,
|
|
264
|
+
cwd: resolvedProjectPath,
|
|
265
|
+
env: {
|
|
266
|
+
...process.env,
|
|
267
|
+
TERM: 'xterm-256color',
|
|
268
|
+
COLORTERM: 'truecolor',
|
|
269
|
+
FORCE_COLOR: '3',
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
ptySessionsMap.set(ptySessionKey, {
|
|
274
|
+
pty: shellProcess,
|
|
275
|
+
ws,
|
|
276
|
+
buffer: [],
|
|
277
|
+
timeoutId: null,
|
|
278
|
+
projectPath,
|
|
279
|
+
sessionId,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
shellProcess.onData((chunk) => {
|
|
283
|
+
if (!ptySessionKey) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const session = ptySessionsMap.get(ptySessionKey);
|
|
288
|
+
if (!session) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (session.buffer.length < 5000) {
|
|
293
|
+
session.buffer.push(chunk);
|
|
294
|
+
} else {
|
|
295
|
+
session.buffer.shift();
|
|
296
|
+
session.buffer.push(chunk);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
300
|
+
let outputData = chunk;
|
|
301
|
+
const cleanChunk = dependencies.stripAnsiSequences(chunk);
|
|
302
|
+
urlDetectionBuffer = `${urlDetectionBuffer}${cleanChunk}`.slice(-SHELL_URL_PARSE_BUFFER_LIMIT);
|
|
303
|
+
|
|
304
|
+
outputData = outputData.replace(
|
|
305
|
+
/OPEN_URL:\s*(https?:\/\/[^\s\x1b\x07]+)/g,
|
|
306
|
+
'[INFO] Opening in browser: $1'
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const emitAuthUrl = (detectedUrl: string, autoOpen = false) => {
|
|
310
|
+
const normalizedUrl = dependencies.normalizeDetectedUrl(detectedUrl);
|
|
311
|
+
if (!normalizedUrl) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const isNewUrl = !announcedAuthUrls.has(normalizedUrl);
|
|
316
|
+
if (isNewUrl) {
|
|
317
|
+
announcedAuthUrls.add(normalizedUrl);
|
|
318
|
+
session.ws?.send(
|
|
319
|
+
JSON.stringify({
|
|
320
|
+
type: 'auth_url',
|
|
321
|
+
url: normalizedUrl,
|
|
322
|
+
autoOpen,
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const normalizedDetectedUrls = dependencies.extractUrlsFromText(urlDetectionBuffer)
|
|
329
|
+
.map((url) => dependencies.normalizeDetectedUrl(url))
|
|
330
|
+
.filter((url): url is string => Boolean(url));
|
|
331
|
+
|
|
332
|
+
const dedupedDetectedUrls = Array.from(new Set(normalizedDetectedUrls)).filter(
|
|
333
|
+
(url, _, urls) =>
|
|
334
|
+
!urls.some((otherUrl) => otherUrl !== url && otherUrl.startsWith(url))
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
dedupedDetectedUrls.forEach((url) => emitAuthUrl(url, false));
|
|
338
|
+
|
|
339
|
+
if (
|
|
340
|
+
dependencies.shouldAutoOpenUrlFromOutput(cleanChunk) &&
|
|
341
|
+
dedupedDetectedUrls.length > 0
|
|
342
|
+
) {
|
|
343
|
+
const bestUrl = dedupedDetectedUrls.reduce((longest, current) =>
|
|
344
|
+
current.length > longest.length ? current : longest
|
|
345
|
+
);
|
|
346
|
+
emitAuthUrl(bestUrl, true);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
session.ws.send(
|
|
350
|
+
JSON.stringify({
|
|
351
|
+
type: 'output',
|
|
352
|
+
data: outputData,
|
|
353
|
+
})
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
shellProcess.onExit((exitCode) => {
|
|
359
|
+
if (!ptySessionKey) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const session = ptySessionsMap.get(ptySessionKey);
|
|
364
|
+
if (session && session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
365
|
+
session.ws.send(
|
|
366
|
+
JSON.stringify({
|
|
367
|
+
type: 'output',
|
|
368
|
+
data: `\r\n\x1b[33mProcess exited with code ${exitCode.exitCode}${
|
|
369
|
+
exitCode.signal != null ? ` (${exitCode.signal})` : ''
|
|
370
|
+
}\x1b[0m\r\n`,
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (session?.timeoutId) {
|
|
376
|
+
clearTimeout(session.timeoutId);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
ptySessionsMap.delete(ptySessionKey);
|
|
380
|
+
shellProcess = null;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
let welcomeMsg = `\x1b[36mStarting terminal in: ${projectPath}\x1b[0m\r\n`;
|
|
384
|
+
if (!isPlainShell) {
|
|
385
|
+
const providerName =
|
|
386
|
+
provider === 'cursor'
|
|
387
|
+
? 'Cursor'
|
|
388
|
+
: provider === 'codex'
|
|
389
|
+
? 'Codex'
|
|
390
|
+
: provider === 'gemini'
|
|
391
|
+
? 'Gemini'
|
|
392
|
+
: 'Claude';
|
|
393
|
+
welcomeMsg = hasSession
|
|
394
|
+
? `\x1b[36mResuming ${providerName} session ${sessionId} in: ${projectPath}\x1b[0m\r\n`
|
|
395
|
+
: `\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
ws.send(
|
|
399
|
+
JSON.stringify({
|
|
400
|
+
type: 'output',
|
|
401
|
+
data: welcomeMsg,
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (data.type === 'input') {
|
|
408
|
+
if (shellProcess) {
|
|
409
|
+
shellProcess.write(readString(data.data));
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (data.type === 'resize') {
|
|
415
|
+
if (shellProcess) {
|
|
416
|
+
shellProcess.resize(readNumber(data.cols, 80), readNumber(data.rows, 24));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
421
|
+
console.error('[ERROR] Shell WebSocket error:', message);
|
|
422
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
423
|
+
ws.send(
|
|
424
|
+
JSON.stringify({
|
|
425
|
+
type: 'output',
|
|
426
|
+
data: `\r\n\x1b[31mError: ${message}\x1b[0m\r\n`,
|
|
427
|
+
})
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
ws.on('close', () => {
|
|
434
|
+
if (!ptySessionKey) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const session = ptySessionsMap.get(ptySessionKey);
|
|
439
|
+
if (!session) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
session.ws = null;
|
|
444
|
+
session.timeoutId = setTimeout(() => {
|
|
445
|
+
session.pty.kill();
|
|
446
|
+
ptySessionsMap.delete(ptySessionKey as string);
|
|
447
|
+
}, PTY_SESSION_TIMEOUT);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
ws.on('error', (error) => {
|
|
451
|
+
console.error('[ERROR] Shell WebSocket error:', error);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { VerifyClientCallbackSync } from 'ws';
|
|
2
|
+
|
|
3
|
+
import type { AuthenticatedWebSocketRequest } from '@/shared/types.js';
|
|
4
|
+
|
|
5
|
+
type WebSocketAuthDependencies = {
|
|
6
|
+
isPlatform: boolean;
|
|
7
|
+
authenticateWebSocket: (token: string | null) => {
|
|
8
|
+
id?: string | number;
|
|
9
|
+
userId?: string | number;
|
|
10
|
+
username?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
} | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Authenticates websocket upgrade requests before the `connection` handler runs.
|
|
17
|
+
*/
|
|
18
|
+
export function verifyWebSocketClient(
|
|
19
|
+
info: Parameters<VerifyClientCallbackSync<AuthenticatedWebSocketRequest>>[0],
|
|
20
|
+
dependencies: WebSocketAuthDependencies
|
|
21
|
+
): boolean {
|
|
22
|
+
const request = info.req as AuthenticatedWebSocketRequest;
|
|
23
|
+
console.log('WebSocket connection attempt to:', request.url);
|
|
24
|
+
|
|
25
|
+
// Platform mode: use the first DB user and skip token checks.
|
|
26
|
+
if (dependencies.isPlatform) {
|
|
27
|
+
const user = dependencies.authenticateWebSocket(null);
|
|
28
|
+
if (!user) {
|
|
29
|
+
console.log('[WARN] Platform mode: No user found in database');
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
request.user = user;
|
|
34
|
+
console.log('[OK] Platform mode WebSocket authenticated for user:', user.username);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// OSS mode: read JWT from query string first, then Authorization header.
|
|
39
|
+
const upgradeUrl = new URL(request.url ?? '/', 'http://localhost');
|
|
40
|
+
const token =
|
|
41
|
+
upgradeUrl.searchParams.get('token') ??
|
|
42
|
+
request.headers.authorization?.split(' ')[1] ??
|
|
43
|
+
null;
|
|
44
|
+
|
|
45
|
+
const user = dependencies.authenticateWebSocket(token);
|
|
46
|
+
if (!user) {
|
|
47
|
+
console.log('[WARN] WebSocket authentication failed');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
request.user = user;
|
|
52
|
+
console.log('[OK] WebSocket authenticated for user:', user.username);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Server as HttpServer } from 'node:http';
|
|
2
|
+
|
|
3
|
+
import { WebSocketServer, type VerifyClientCallbackSync } from 'ws';
|
|
4
|
+
|
|
5
|
+
import { handleChatConnection } from '@/modules/websocket/services/chat-websocket.service.js';
|
|
6
|
+
import { verifyWebSocketClient } from '@/modules/websocket/services/websocket-auth.service.js';
|
|
7
|
+
import { handlePluginWsProxy } from '@/modules/websocket/services/plugin-websocket-proxy.service.js';
|
|
8
|
+
import { handleShellConnection } from '@/modules/websocket/services/shell-websocket.service.js';
|
|
9
|
+
import type { AuthenticatedWebSocketRequest } from '@/shared/types.js';
|
|
10
|
+
|
|
11
|
+
type WebSocketServerDependencies = {
|
|
12
|
+
verifyClient: Parameters<typeof verifyWebSocketClient>[1];
|
|
13
|
+
chat: Parameters<typeof handleChatConnection>[2];
|
|
14
|
+
shell: Parameters<typeof handleShellConnection>[1];
|
|
15
|
+
getPluginPort: Parameters<typeof handlePluginWsProxy>[2];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates and wires the server-wide websocket gateway used for chat, shell, and
|
|
20
|
+
* plugin proxy routes.
|
|
21
|
+
*/
|
|
22
|
+
export function createWebSocketServer(
|
|
23
|
+
server: HttpServer,
|
|
24
|
+
dependencies: WebSocketServerDependencies
|
|
25
|
+
): WebSocketServer {
|
|
26
|
+
const wss = new WebSocketServer({
|
|
27
|
+
server,
|
|
28
|
+
verifyClient: ((
|
|
29
|
+
info: Parameters<VerifyClientCallbackSync<AuthenticatedWebSocketRequest>>[0]
|
|
30
|
+
) => verifyWebSocketClient(info, dependencies.verifyClient)),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
wss.on('connection', (ws, request) => {
|
|
34
|
+
const incomingRequest = request as AuthenticatedWebSocketRequest;
|
|
35
|
+
const url = incomingRequest.url ?? '/';
|
|
36
|
+
const pathname = new URL(url, 'http://localhost').pathname;
|
|
37
|
+
|
|
38
|
+
if (pathname === '/shell') {
|
|
39
|
+
handleShellConnection(ws, dependencies.shell);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (pathname === '/ws') {
|
|
44
|
+
handleChatConnection(ws, incomingRequest, dependencies.chat);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (pathname.startsWith('/plugin-ws/')) {
|
|
49
|
+
handlePluginWsProxy(ws, pathname, dependencies.getPluginPort);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('[WARN] Unknown WebSocket path:', pathname);
|
|
54
|
+
ws.close();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return wss;
|
|
58
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RealtimeClientConnection } from '@/shared/types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Numeric readyState for an open WebSocket connection.
|
|
5
|
+
*
|
|
6
|
+
* We keep this in module state so services that broadcast updates do not need
|
|
7
|
+
* to import `ws` directly just to compare open/closed state.
|
|
8
|
+
*/
|
|
9
|
+
export const WS_OPEN_STATE = 1;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Shared registry of active chat WebSocket connections.
|
|
13
|
+
*
|
|
14
|
+
* Project/session services publish realtime updates by iterating this set.
|
|
15
|
+
*/
|
|
16
|
+
export const connectedClients = new Set<RealtimeClientConnection>();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { WS_OPEN_STATE } from '@/modules/websocket/services/websocket-state.service.js';
|
|
2
|
+
import type { RealtimeClientConnection } from '@/shared/types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Thin transport adapter that gives WebSocket connections the same interface as
|
|
6
|
+
* SSE writers used by API routes (`send`, `setSessionId`, `getSessionId`).
|
|
7
|
+
*/
|
|
8
|
+
export class WebSocketWriter {
|
|
9
|
+
ws: RealtimeClientConnection;
|
|
10
|
+
sessionId: string | null;
|
|
11
|
+
userId: string | number | null;
|
|
12
|
+
isWebSocketWriter: boolean;
|
|
13
|
+
|
|
14
|
+
constructor(ws: RealtimeClientConnection, userId: string | number | null = null) {
|
|
15
|
+
this.ws = ws;
|
|
16
|
+
this.sessionId = null;
|
|
17
|
+
this.userId = userId;
|
|
18
|
+
this.isWebSocketWriter = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
send(data: unknown): void {
|
|
22
|
+
if (this.ws.readyState === WS_OPEN_STATE) {
|
|
23
|
+
this.ws.send(JSON.stringify(data));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
updateWebSocket(newRawWs: RealtimeClientConnection): void {
|
|
28
|
+
this.ws = newRawWs;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setSessionId(sessionId: string): void {
|
|
32
|
+
this.sessionId = sessionId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getSessionId(): string | null {
|
|
36
|
+
return this.sessionId;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/server/routes/agent.js
CHANGED
|
@@ -4,8 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { promises as fs } from 'fs';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
|
-
import { userDb, apiKeysDb, githubTokensDb } from '../database/
|
|
8
|
-
import { addProjectManually } from '../projects.js';
|
|
7
|
+
import { userDb, apiKeysDb, githubTokensDb, projectsDb } from '../modules/database/index.js';
|
|
9
8
|
import { queryClaudeSDK } from '../claude-sdk.js';
|
|
10
9
|
import { spawnCursor } from '../cursor-cli.js';
|
|
11
10
|
import { queryCodex } from '../openai-codex.js';
|
|
@@ -13,6 +12,7 @@ import { spawnGemini } from '../gemini-cli.js';
|
|
|
13
12
|
import { Octokit } from '@octokit/rest';
|
|
14
13
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
|
15
14
|
import { IS_PLATFORM } from '../constants/config.js';
|
|
15
|
+
import { normalizeProjectPath } from '../shared/utils.js';
|
|
16
16
|
|
|
17
17
|
const router = express.Router();
|
|
18
18
|
|
|
@@ -890,7 +890,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
|
|
890
890
|
finalProjectPath = await cloneGitHubRepo(githubUrl.trim(), tokenToUse, targetPath);
|
|
891
891
|
} else {
|
|
892
892
|
// Use existing project path
|
|
893
|
-
finalProjectPath = path.resolve(projectPath);
|
|
893
|
+
finalProjectPath = normalizeProjectPath(path.resolve(projectPath));
|
|
894
894
|
|
|
895
895
|
// Verify the path exists
|
|
896
896
|
try {
|
|
@@ -900,19 +900,14 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
|
|
900
900
|
}
|
|
901
901
|
}
|
|
902
902
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
console.log('📦 Using existing project registration for:', finalProjectPath);
|
|
912
|
-
project = { path: finalProjectPath };
|
|
913
|
-
} else {
|
|
914
|
-
throw error;
|
|
915
|
-
}
|
|
903
|
+
finalProjectPath = normalizeProjectPath(finalProjectPath);
|
|
904
|
+
|
|
905
|
+
// Register project path in DB (or reuse existing active registration)
|
|
906
|
+
const registrationResult = projectsDb.createProjectPath(finalProjectPath, null);
|
|
907
|
+
if (registrationResult.outcome === 'active_conflict') {
|
|
908
|
+
console.log('Project registration already exists for:', finalProjectPath);
|
|
909
|
+
} else {
|
|
910
|
+
console.log('Project registered:', registrationResult.project);
|
|
916
911
|
}
|
|
917
912
|
|
|
918
913
|
// Set up writer based on streaming mode
|
package/server/routes/auth.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import bcrypt from 'bcrypt';
|
|
3
|
-
import { userDb
|
|
3
|
+
import { userDb } from '../modules/database/index.js';
|
|
4
|
+
import { getConnection } from '../modules/database/connection.js';
|
|
4
5
|
import { generateToken, authenticateToken } from '../middleware/auth.js';
|
|
5
6
|
|
|
6
7
|
const router = express.Router();
|
|
8
|
+
const db = getConnection();
|
|
7
9
|
|
|
8
10
|
// Check auth status and setup requirements
|
|
9
11
|
router.get('/status', async (req, res) => {
|
|
@@ -132,4 +134,4 @@ router.post('/logout', authenticateToken, (req, res) => {
|
|
|
132
134
|
res.json({ success: true, message: 'Logged out successfully' });
|
|
133
135
|
});
|
|
134
136
|
|
|
135
|
-
export default router;
|
|
137
|
+
export default router;
|