@epiphytic/claudecodeui 1.2.0 → 1.2.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/dist/assets/index-BGneYLVE.css +32 -0
- package/dist/assets/{index-DqxzEd_8.js → index-sqmQ9jF8.js} +182 -182
- package/dist/index.html +2 -2
- package/dist/sw.js +25 -1
- package/package.json +2 -1
- package/public/api-docs.html +879 -0
- package/public/clear-cache.html +85 -0
- package/public/convert-icons.md +53 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +9 -0
- package/public/generate-icons.js +49 -0
- package/public/icons/claude-ai-icon.svg +1 -0
- package/public/icons/codex-white.svg +3 -0
- package/public/icons/codex.svg +3 -0
- package/public/icons/cursor-white.svg +12 -0
- package/public/icons/cursor.svg +1 -0
- package/public/icons/generate-icons.md +19 -0
- package/public/icons/icon-128x128.png +0 -0
- package/public/icons/icon-128x128.svg +12 -0
- package/public/icons/icon-144x144.png +0 -0
- package/public/icons/icon-144x144.svg +12 -0
- package/public/icons/icon-152x152.png +0 -0
- package/public/icons/icon-152x152.svg +12 -0
- package/public/icons/icon-192x192.png +0 -0
- package/public/icons/icon-192x192.svg +12 -0
- package/public/icons/icon-384x384.png +0 -0
- package/public/icons/icon-384x384.svg +12 -0
- package/public/icons/icon-512x512.png +0 -0
- package/public/icons/icon-512x512.svg +12 -0
- package/public/icons/icon-72x72.png +0 -0
- package/public/icons/icon-72x72.svg +12 -0
- package/public/icons/icon-96x96.png +0 -0
- package/public/icons/icon-96x96.svg +12 -0
- package/public/icons/icon-template.svg +12 -0
- package/public/logo-128.png +0 -0
- package/public/logo-256.png +0 -0
- package/public/logo-32.png +0 -0
- package/public/logo-512.png +0 -0
- package/public/logo-64.png +0 -0
- package/public/logo.svg +17 -0
- package/public/manifest.json +61 -0
- package/public/screenshots/cli-selection.png +0 -0
- package/public/screenshots/desktop-main.png +0 -0
- package/public/screenshots/mobile-chat.png +0 -0
- package/public/screenshots/tools-modal.png +0 -0
- package/public/sw.js +131 -0
- package/server/external-session-detector.js +188 -48
- package/server/index.js +141 -1
- package/dist/assets/index-r43D8sh4.css +0 -32
|
@@ -27,10 +27,16 @@ const currentPid = process.pid;
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Detect external Claude processes
|
|
30
|
-
* @returns {Array<{ pid: number, command: string, cwd: string | null }
|
|
30
|
+
* @returns {{ processes: Array<{ pid: number, command: string, cwd: string | null }>, detectionAvailable: boolean, error: string | null }}
|
|
31
31
|
*/
|
|
32
32
|
function detectClaudeProcesses() {
|
|
33
33
|
const processes = [];
|
|
34
|
+
let detectionAvailable = true;
|
|
35
|
+
let error = null;
|
|
36
|
+
|
|
37
|
+
console.log("[ExternalSessionDetector] detectClaudeProcesses() called");
|
|
38
|
+
console.log("[ExternalSessionDetector] Platform:", os.platform());
|
|
39
|
+
console.log("[ExternalSessionDetector] Current PID:", currentPid);
|
|
34
40
|
|
|
35
41
|
if (os.platform() === "win32") {
|
|
36
42
|
// Windows: use wmic or tasklist
|
|
@@ -58,72 +64,165 @@ function detectClaudeProcesses() {
|
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
}
|
|
67
|
+
} else if (result.error) {
|
|
68
|
+
detectionAvailable = false;
|
|
69
|
+
error = `wmic not available: ${result.error.message}`;
|
|
61
70
|
}
|
|
62
|
-
} catch {
|
|
63
|
-
|
|
71
|
+
} catch (e) {
|
|
72
|
+
detectionAvailable = false;
|
|
73
|
+
error = `Windows process detection failed: ${e.message}`;
|
|
64
74
|
}
|
|
65
75
|
} else {
|
|
66
76
|
// Unix: use pgrep and ps
|
|
67
77
|
try {
|
|
68
|
-
// First,
|
|
69
|
-
const
|
|
78
|
+
// First, check if pgrep is available
|
|
79
|
+
const pgrepCheck = spawnSync("which", ["pgrep"], {
|
|
70
80
|
encoding: "utf8",
|
|
71
81
|
stdio: "pipe",
|
|
72
82
|
});
|
|
83
|
+
console.log(
|
|
84
|
+
"[ExternalSessionDetector] pgrep available:",
|
|
85
|
+
pgrepCheck.status === 0,
|
|
86
|
+
);
|
|
73
87
|
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
// Skip our own process and child processes
|
|
81
|
-
if (pid === currentPid) continue;
|
|
82
|
-
|
|
83
|
-
// Get command details
|
|
84
|
-
const psResult = spawnSync("ps", ["-p", String(pid), "-o", "args="], {
|
|
88
|
+
if (pgrepCheck.status !== 0) {
|
|
89
|
+
// pgrep not available, try ps aux as fallback
|
|
90
|
+
console.log("[ExternalSessionDetector] Using ps aux fallback");
|
|
91
|
+
try {
|
|
92
|
+
const psResult = spawnSync("ps", ["aux"], {
|
|
85
93
|
encoding: "utf8",
|
|
86
94
|
stdio: "pipe",
|
|
87
95
|
});
|
|
88
96
|
|
|
89
97
|
if (psResult.status === 0) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
106
|
-
if (
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
const lines = psResult.stdout.split("\n");
|
|
99
|
+
const claudeLines = lines.filter(
|
|
100
|
+
(line) =>
|
|
101
|
+
line.toLowerCase().includes("claude") &&
|
|
102
|
+
!line.includes(String(currentPid)),
|
|
103
|
+
);
|
|
104
|
+
console.log(
|
|
105
|
+
"[ExternalSessionDetector] ps aux found",
|
|
106
|
+
claudeLines.length,
|
|
107
|
+
'lines containing "claude"',
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
for (const line of claudeLines) {
|
|
111
|
+
const parts = line.trim().split(/\s+/);
|
|
112
|
+
if (parts.length >= 2) {
|
|
113
|
+
const pid = parseInt(parts[1], 10);
|
|
114
|
+
if (!isNaN(pid) && pid !== currentPid) {
|
|
115
|
+
const command = parts.slice(10).join(" ");
|
|
116
|
+
const isExternal = isExternalClaudeProcess(command);
|
|
117
|
+
console.log(
|
|
118
|
+
`[ExternalSessionDetector] PID ${pid}: "${command.slice(0, 60)}..." isExternal=${isExternal}`,
|
|
119
|
+
);
|
|
120
|
+
if (isExternal) {
|
|
121
|
+
processes.push({ pid, command, cwd: null });
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
|
-
} catch {
|
|
113
|
-
// lsof may not be available
|
|
114
124
|
}
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
detectionAvailable = false;
|
|
128
|
+
error = "Neither pgrep nor ps aux available";
|
|
129
|
+
console.log("[ExternalSessionDetector] ps aux failed");
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
detectionAvailable = false;
|
|
133
|
+
error = `Process detection failed: ${e.message}`;
|
|
134
|
+
console.log("[ExternalSessionDetector] ps aux exception:", e.message);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// pgrep is available, use it
|
|
138
|
+
const pgrepResult = spawnSync("pgrep", ["-f", "claude"], {
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
stdio: "pipe",
|
|
141
|
+
});
|
|
142
|
+
console.log(
|
|
143
|
+
"[ExternalSessionDetector] pgrep status:",
|
|
144
|
+
pgrepResult.status,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (pgrepResult.status === 0) {
|
|
148
|
+
const pids = pgrepResult.stdout.trim().split("\n").filter(Boolean);
|
|
149
|
+
console.log("[ExternalSessionDetector] pgrep found PIDs:", pids);
|
|
150
|
+
|
|
151
|
+
for (const pidStr of pids) {
|
|
152
|
+
const pid = parseInt(pidStr, 10);
|
|
153
|
+
|
|
154
|
+
// Skip our own process and child processes
|
|
155
|
+
if (pid === currentPid) {
|
|
156
|
+
console.log(
|
|
157
|
+
`[ExternalSessionDetector] Skipping our own PID ${pid}`,
|
|
158
|
+
);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Get command details
|
|
163
|
+
const psResult = spawnSync(
|
|
164
|
+
"ps",
|
|
165
|
+
["-p", String(pid), "-o", "args="],
|
|
166
|
+
{
|
|
167
|
+
encoding: "utf8",
|
|
168
|
+
stdio: "pipe",
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (psResult.status === 0) {
|
|
173
|
+
const command = psResult.stdout.trim();
|
|
174
|
+
const isExternal = isExternalClaudeProcess(command);
|
|
175
|
+
console.log(
|
|
176
|
+
`[ExternalSessionDetector] PID ${pid}: "${command.slice(0, 80)}..." isExternal=${isExternal}`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Filter out our own subprocesses (claude-sdk spawned by this app)
|
|
180
|
+
// and only include standalone claude CLI invocations
|
|
181
|
+
if (isExternal) {
|
|
182
|
+
// Try to get working directory via lsof
|
|
183
|
+
let cwd = null;
|
|
184
|
+
try {
|
|
185
|
+
const lsofResult = spawnSync(
|
|
186
|
+
"lsof",
|
|
187
|
+
["-p", String(pid), "-Fn"],
|
|
188
|
+
{
|
|
189
|
+
encoding: "utf8",
|
|
190
|
+
stdio: "pipe",
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
if (lsofResult.status === 0) {
|
|
194
|
+
const cwdMatch = lsofResult.stdout.match(/n(\/[^\n]+)/);
|
|
195
|
+
if (cwdMatch) {
|
|
196
|
+
cwd = cwdMatch[1];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
// lsof may not be available - not critical
|
|
201
|
+
}
|
|
115
202
|
|
|
116
|
-
|
|
203
|
+
processes.push({ pid, command, cwd });
|
|
204
|
+
}
|
|
117
205
|
}
|
|
118
206
|
}
|
|
207
|
+
} else {
|
|
208
|
+
console.log(
|
|
209
|
+
"[ExternalSessionDetector] pgrep found no claude processes (status:",
|
|
210
|
+
pgrepResult.status,
|
|
211
|
+
")",
|
|
212
|
+
);
|
|
119
213
|
}
|
|
120
214
|
}
|
|
121
|
-
} catch {
|
|
122
|
-
|
|
215
|
+
} catch (e) {
|
|
216
|
+
detectionAvailable = false;
|
|
217
|
+
error = `Unix process detection failed: ${e.message}`;
|
|
218
|
+
console.log(
|
|
219
|
+
"[ExternalSessionDetector] Unix detection exception:",
|
|
220
|
+
e.message,
|
|
221
|
+
);
|
|
123
222
|
}
|
|
124
223
|
}
|
|
125
224
|
|
|
126
|
-
return processes;
|
|
225
|
+
return { processes, detectionAvailable, error };
|
|
127
226
|
}
|
|
128
227
|
|
|
129
228
|
/**
|
|
@@ -133,18 +232,30 @@ function detectClaudeProcesses() {
|
|
|
133
232
|
*/
|
|
134
233
|
function isExternalClaudeProcess(command) {
|
|
135
234
|
// Skip node processes (SDK internals)
|
|
136
|
-
if (command.startsWith("node "))
|
|
235
|
+
if (command.startsWith("node ")) {
|
|
236
|
+
console.log("[isExternalClaudeProcess] Rejected: starts with 'node '");
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
137
239
|
|
|
138
240
|
// Skip our own server
|
|
139
|
-
if (command.includes("claudecodeui/server"))
|
|
241
|
+
if (command.includes("claudecodeui/server")) {
|
|
242
|
+
console.log(
|
|
243
|
+
"[isExternalClaudeProcess] Rejected: contains 'claudecodeui/server'",
|
|
244
|
+
);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
140
247
|
|
|
141
248
|
// Look for actual claude CLI invocations
|
|
142
|
-
|
|
249
|
+
const isExternal =
|
|
143
250
|
command.includes("claude ") ||
|
|
144
251
|
command.includes("claude-code") ||
|
|
145
252
|
command.match(/\/claude\s/) ||
|
|
146
|
-
command.endsWith("/claude")
|
|
253
|
+
command.endsWith("/claude");
|
|
254
|
+
|
|
255
|
+
console.log(
|
|
256
|
+
`[isExternalClaudeProcess] "${command.slice(0, 60)}..." => ${isExternal}`,
|
|
147
257
|
);
|
|
258
|
+
return isExternal;
|
|
148
259
|
}
|
|
149
260
|
|
|
150
261
|
/**
|
|
@@ -284,34 +395,63 @@ function processExists(pid) {
|
|
|
284
395
|
/**
|
|
285
396
|
* Main detection function - detect all external Claude sessions
|
|
286
397
|
* @param {string} projectPath - The project directory to check
|
|
287
|
-
* @returns {{ hasExternalSession: boolean, processes: Array, tmuxSessions: Array, lockFile: object }}
|
|
398
|
+
* @returns {{ hasExternalSession: boolean, processes: Array, tmuxSessions: Array, lockFile: object, detectionAvailable: boolean, detectionError: string | null }}
|
|
288
399
|
*/
|
|
289
400
|
function detectExternalClaude(projectPath) {
|
|
401
|
+
console.log("[detectExternalClaude] Called with projectPath:", projectPath);
|
|
402
|
+
|
|
290
403
|
// Check cache
|
|
291
404
|
const cacheKey = projectPath || "__global__";
|
|
292
405
|
const cached = detectionCache.get(cacheKey);
|
|
293
406
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
407
|
+
console.log("[detectExternalClaude] Returning cached result");
|
|
294
408
|
return cached.result;
|
|
295
409
|
}
|
|
410
|
+
console.log("[detectExternalClaude] Cache miss, performing fresh detection");
|
|
296
411
|
|
|
297
412
|
const result = {
|
|
298
413
|
hasExternalSession: false,
|
|
299
414
|
processes: [],
|
|
300
415
|
tmuxSessions: [],
|
|
301
416
|
lockFile: { exists: false, lockFile: null, content: null },
|
|
417
|
+
detectionAvailable: true,
|
|
418
|
+
detectionError: null,
|
|
302
419
|
};
|
|
303
420
|
|
|
304
421
|
// Detect processes
|
|
305
|
-
|
|
422
|
+
const processDetection = detectClaudeProcesses();
|
|
423
|
+
result.processes = processDetection.processes;
|
|
424
|
+
result.detectionAvailable = processDetection.detectionAvailable;
|
|
425
|
+
result.detectionError = processDetection.error;
|
|
426
|
+
console.log(
|
|
427
|
+
"[detectExternalClaude] Process detection result:",
|
|
428
|
+
processDetection.processes.length,
|
|
429
|
+
"processes, available:",
|
|
430
|
+
processDetection.detectionAvailable,
|
|
431
|
+
"error:",
|
|
432
|
+
processDetection.error,
|
|
433
|
+
);
|
|
434
|
+
|
|
306
435
|
if (projectPath) {
|
|
307
436
|
// Filter to processes in this project
|
|
437
|
+
const beforeFilter = result.processes.length;
|
|
308
438
|
result.processes = result.processes.filter(
|
|
309
439
|
(p) => !p.cwd || p.cwd.startsWith(projectPath),
|
|
310
440
|
);
|
|
441
|
+
console.log(
|
|
442
|
+
"[detectExternalClaude] Filtered processes for project:",
|
|
443
|
+
beforeFilter,
|
|
444
|
+
"->",
|
|
445
|
+
result.processes.length,
|
|
446
|
+
);
|
|
311
447
|
}
|
|
312
448
|
|
|
313
449
|
// Detect tmux sessions
|
|
314
450
|
result.tmuxSessions = detectClaudeTmuxSessions();
|
|
451
|
+
console.log(
|
|
452
|
+
"[detectExternalClaude] tmux sessions found:",
|
|
453
|
+
result.tmuxSessions.length,
|
|
454
|
+
);
|
|
315
455
|
|
|
316
456
|
// Check lock file
|
|
317
457
|
if (projectPath) {
|
package/server/index.js
CHANGED
|
@@ -379,6 +379,70 @@ const wss = new WebSocketServer({
|
|
|
379
379
|
app.locals.wss = wss;
|
|
380
380
|
|
|
381
381
|
app.use(cors());
|
|
382
|
+
|
|
383
|
+
// Request/Response logging middleware for debugging
|
|
384
|
+
app.use((req, res, next) => {
|
|
385
|
+
const startTime = Date.now();
|
|
386
|
+
const originalSend = res.send;
|
|
387
|
+
const originalJson = res.json;
|
|
388
|
+
|
|
389
|
+
// Capture response for logging
|
|
390
|
+
res.send = function (body) {
|
|
391
|
+
const duration = Date.now() - startTime;
|
|
392
|
+
const statusCode = res.statusCode;
|
|
393
|
+
|
|
394
|
+
// Log 4xx errors with headers
|
|
395
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
396
|
+
console.log(
|
|
397
|
+
`[HTTP ${statusCode}] ${req.method} ${req.originalUrl} (${duration}ms)`,
|
|
398
|
+
);
|
|
399
|
+
console.log(` Request Headers:`, JSON.stringify(req.headers, null, 2));
|
|
400
|
+
console.log(
|
|
401
|
+
` Response:`,
|
|
402
|
+
typeof body === "string" ? body.slice(0, 500) : body,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
// Log 5xx errors with full details
|
|
406
|
+
else if (statusCode >= 500) {
|
|
407
|
+
console.error(
|
|
408
|
+
`[HTTP ${statusCode}] ${req.method} ${req.originalUrl} (${duration}ms)`,
|
|
409
|
+
);
|
|
410
|
+
console.error(` Request Headers:`, JSON.stringify(req.headers, null, 2));
|
|
411
|
+
console.error(` Request Body:`, req.body);
|
|
412
|
+
console.error(` Response:`, body);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return originalSend.call(this, body);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
res.json = function (body) {
|
|
419
|
+
const duration = Date.now() - startTime;
|
|
420
|
+
const statusCode = res.statusCode;
|
|
421
|
+
|
|
422
|
+
// Log 4xx errors with headers
|
|
423
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
424
|
+
console.log(
|
|
425
|
+
`[HTTP ${statusCode}] ${req.method} ${req.originalUrl} (${duration}ms)`,
|
|
426
|
+
);
|
|
427
|
+
console.log(` Request Headers:`, JSON.stringify(req.headers, null, 2));
|
|
428
|
+
console.log(` Response:`, JSON.stringify(body, null, 2).slice(0, 500));
|
|
429
|
+
}
|
|
430
|
+
// Log 5xx errors with full details
|
|
431
|
+
else if (statusCode >= 500) {
|
|
432
|
+
console.error(
|
|
433
|
+
`[HTTP ${statusCode}] ${req.method} ${req.originalUrl} (${duration}ms)`,
|
|
434
|
+
);
|
|
435
|
+
console.error(` Request Headers:`, JSON.stringify(req.headers, null, 2));
|
|
436
|
+
console.error(` Request Body:`, req.body);
|
|
437
|
+
console.error(` Response:`, JSON.stringify(body, null, 2));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return originalJson.call(this, body);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
next();
|
|
444
|
+
});
|
|
445
|
+
|
|
382
446
|
app.use(
|
|
383
447
|
express.json({
|
|
384
448
|
limit: "50mb",
|
|
@@ -402,6 +466,56 @@ app.get("/health", (req, res) => {
|
|
|
402
466
|
});
|
|
403
467
|
});
|
|
404
468
|
|
|
469
|
+
// Explicit route for manifest.json (PWA manifest)
|
|
470
|
+
app.get("/manifest.json", (req, res) => {
|
|
471
|
+
const manifestPath = path.join(__dirname, "../public/manifest.json");
|
|
472
|
+
const distManifestPath = path.join(__dirname, "../dist/manifest.json");
|
|
473
|
+
|
|
474
|
+
console.log("[manifest.json] Request received");
|
|
475
|
+
console.log("[manifest.json] __dirname:", __dirname);
|
|
476
|
+
console.log("[manifest.json] Checking paths:");
|
|
477
|
+
console.log(
|
|
478
|
+
" - public:",
|
|
479
|
+
manifestPath,
|
|
480
|
+
"exists:",
|
|
481
|
+
fs.existsSync(manifestPath),
|
|
482
|
+
);
|
|
483
|
+
console.log(
|
|
484
|
+
" - dist:",
|
|
485
|
+
distManifestPath,
|
|
486
|
+
"exists:",
|
|
487
|
+
fs.existsSync(distManifestPath),
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// Try public first, then dist
|
|
491
|
+
if (fs.existsSync(manifestPath)) {
|
|
492
|
+
console.log("[manifest.json] Serving from public");
|
|
493
|
+
res.setHeader("Content-Type", "application/manifest+json");
|
|
494
|
+
res.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
|
|
495
|
+
res.sendFile(manifestPath, (err) => {
|
|
496
|
+
if (err) {
|
|
497
|
+
console.error("[manifest.json] sendFile error:", err);
|
|
498
|
+
res.status(500).json({ error: "Failed to send manifest.json" });
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
} else if (fs.existsSync(distManifestPath)) {
|
|
502
|
+
console.log("[manifest.json] Serving from dist");
|
|
503
|
+
res.setHeader("Content-Type", "application/manifest+json");
|
|
504
|
+
res.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
|
|
505
|
+
res.sendFile(distManifestPath, (err) => {
|
|
506
|
+
if (err) {
|
|
507
|
+
console.error("[manifest.json] sendFile error:", err);
|
|
508
|
+
res.status(500).json({ error: "Failed to send manifest.json" });
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
} else {
|
|
512
|
+
console.error("[ERROR] manifest.json not found in public or dist");
|
|
513
|
+
console.error(" Checked:", manifestPath);
|
|
514
|
+
console.error(" Checked:", distManifestPath);
|
|
515
|
+
res.status(404).json({ error: "manifest.json not found" });
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
405
519
|
// Optional API key validation (if configured)
|
|
406
520
|
app.use("/api", validateApiKey);
|
|
407
521
|
|
|
@@ -447,7 +561,7 @@ app.use("/api/sessions", authenticateToken, sessionsRoutes);
|
|
|
447
561
|
// Agent API Routes (uses API key authentication)
|
|
448
562
|
app.use("/api/agent", agentRoutes);
|
|
449
563
|
|
|
450
|
-
// Serve public files (like api-docs.html, icons)
|
|
564
|
+
// Serve public files (like api-docs.html, icons, manifest.json)
|
|
451
565
|
// Enable ETag generation for conditional requests (304 support)
|
|
452
566
|
app.use(
|
|
453
567
|
express.static(path.join(__dirname, "../public"), {
|
|
@@ -461,6 +575,9 @@ app.use(
|
|
|
461
575
|
"Cache-Control",
|
|
462
576
|
"public, max-age=604800, must-revalidate",
|
|
463
577
|
);
|
|
578
|
+
} else if (filePath.endsWith(".json")) {
|
|
579
|
+
// JSON files (like manifest.json) - short cache with revalidation
|
|
580
|
+
res.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
|
|
464
581
|
} else if (filePath.endsWith(".html")) {
|
|
465
582
|
// HTML files should not be cached
|
|
466
583
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
@@ -483,6 +600,9 @@ app.use(
|
|
|
483
600
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
484
601
|
res.setHeader("Pragma", "no-cache");
|
|
485
602
|
res.setHeader("Expires", "0");
|
|
603
|
+
} else if (filePath.endsWith(".json")) {
|
|
604
|
+
// JSON files (like manifest.json) - short cache with revalidation
|
|
605
|
+
res.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
|
|
486
606
|
} else if (
|
|
487
607
|
filePath.match(/\.(js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|ico)$/)
|
|
488
608
|
) {
|
|
@@ -1150,12 +1270,26 @@ async function handleChatMessage(ws, writer, messageData) {
|
|
|
1150
1270
|
// Handle proactive external session check (before user submits a prompt)
|
|
1151
1271
|
if (data.type === "check-external-session") {
|
|
1152
1272
|
const projectPath = data.projectPath;
|
|
1273
|
+
console.log(
|
|
1274
|
+
"[ExternalSessionCheck] Checking for external sessions:",
|
|
1275
|
+
projectPath,
|
|
1276
|
+
);
|
|
1153
1277
|
if (projectPath) {
|
|
1154
1278
|
const externalCheck = detectExternalClaude(projectPath);
|
|
1279
|
+
console.log("[ExternalSessionCheck] Result:", {
|
|
1280
|
+
hasExternalSession: externalCheck.hasExternalSession,
|
|
1281
|
+
detectionAvailable: externalCheck.detectionAvailable,
|
|
1282
|
+
detectionError: externalCheck.detectionError,
|
|
1283
|
+
processCount: externalCheck.processes.length,
|
|
1284
|
+
tmuxCount: externalCheck.tmuxSessions.length,
|
|
1285
|
+
hasLockFile: externalCheck.lockFile.exists,
|
|
1286
|
+
});
|
|
1155
1287
|
writer.send({
|
|
1156
1288
|
type: "external-session-check-result",
|
|
1157
1289
|
projectPath,
|
|
1158
1290
|
hasExternalSession: externalCheck.hasExternalSession,
|
|
1291
|
+
detectionAvailable: externalCheck.detectionAvailable,
|
|
1292
|
+
detectionError: externalCheck.detectionError,
|
|
1159
1293
|
details: externalCheck.hasExternalSession
|
|
1160
1294
|
? {
|
|
1161
1295
|
processIds: externalCheck.processes.map((p) => p.pid),
|
|
@@ -1169,6 +1303,8 @@ async function handleChatMessage(ws, writer, messageData) {
|
|
|
1169
1303
|
}
|
|
1170
1304
|
: null,
|
|
1171
1305
|
});
|
|
1306
|
+
} else {
|
|
1307
|
+
console.log("[ExternalSessionCheck] No projectPath provided");
|
|
1172
1308
|
}
|
|
1173
1309
|
return;
|
|
1174
1310
|
}
|
|
@@ -1208,6 +1344,7 @@ async function handleChatMessage(ws, writer, messageData) {
|
|
|
1208
1344
|
writer.send({
|
|
1209
1345
|
type: "external-session-detected",
|
|
1210
1346
|
projectPath,
|
|
1347
|
+
detectionAvailable: externalCheck.detectionAvailable,
|
|
1211
1348
|
details: {
|
|
1212
1349
|
processIds: externalCheck.processes.map((p) => p.pid),
|
|
1213
1350
|
tmuxSessions: externalCheck.tmuxSessions.map(
|
|
@@ -2652,6 +2789,9 @@ app.get(
|
|
|
2652
2789
|
app.get("*", (req, res) => {
|
|
2653
2790
|
// Skip requests for static assets (files with extensions)
|
|
2654
2791
|
if (path.extname(req.path)) {
|
|
2792
|
+
console.log(
|
|
2793
|
+
`[404] Static file not found: ${req.path} (not served by express.static)`,
|
|
2794
|
+
);
|
|
2655
2795
|
return res.status(404).send("Not found");
|
|
2656
2796
|
}
|
|
2657
2797
|
|