@pixelbyte-software/pixcode 1.51.2 → 1.51.4
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/CODE_OF_CONDUCT.md +41 -41
- package/CONTRIBUTING.md +155 -155
- package/LICENSE +718 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +419 -419
- package/README.ru.md +169 -169
- package/README.tr.md +298 -298
- package/README.zh-CN.md +167 -167
- package/SECURITY.md +46 -46
- package/dist/api-automation.html +110 -110
- package/dist/api-docs.html +548 -548
- package/dist/assets/index-B9N-gfOQ.css +32 -0
- package/dist/assets/{index-EN9ngyxf.js → index-HfGHXhD6.js} +175 -175
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- package/dist/favicon.svg +8 -8
- package/dist/features.html +133 -133
- package/dist/generate-icons.js +48 -48
- package/dist/humans.txt +15 -15
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +59 -59
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1696 -1696
- package/dist/orchestration.html +125 -125
- package/dist/robots.txt +4 -4
- package/dist/site.css +692 -692
- package/dist/sitemap.xml +51 -51
- package/dist/sw.js +132 -132
- package/dist-server/server/cli.js +96 -96
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/database/db.js +14 -2
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +191 -31
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/middleware/auth.js +16 -5
- package/dist-server/server/middleware/auth.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js +84 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js +43 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js.map +1 -0
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +55 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/index.js +1 -0
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/routes/auth.js +12 -5
- package/dist-server/server/routes/auth.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +29 -17
- package/dist-server/server/routes/git.js.map +1 -1
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/routes/platformization.js +7 -6
- package/dist-server/server/routes/platformization.js.map +1 -1
- package/dist-server/server/services/hermes-gateway.js +310 -0
- package/dist-server/server/services/hermes-gateway.js.map +1 -1
- package/dist-server/server/services/platformization.js +58 -2
- package/dist-server/server/services/platformization.js.map +1 -1
- package/dist-server/server/services/public-api-manifest.js +59 -51
- package/dist-server/server/services/public-api-manifest.js.map +1 -1
- package/package.json +222 -222
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/github/create-v1.38-issues.mjs +351 -351
- package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
- package/scripts/hermes/configure-pixcode-mcp.mjs +165 -163
- package/scripts/hermes/pixcode-mcp-server.mjs +1009 -958
- package/scripts/smoke/changes-panel-layout.mjs +48 -48
- package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
- package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
- package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
- package/scripts/smoke/chat-session-state.mjs +19 -19
- package/scripts/smoke/code-editor-theme.mjs +55 -55
- package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
- package/scripts/smoke/command-center-agent-writes.mjs +79 -79
- package/scripts/smoke/command-center-non-git.mjs +46 -46
- package/scripts/smoke/context-packet.mjs +43 -43
- package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
- package/scripts/smoke/daemon-entrypoint.mjs +20 -20
- package/scripts/smoke/default-landing-routing.mjs +33 -33
- package/scripts/smoke/desktop-native-notifications.mjs +30 -30
- package/scripts/smoke/desktop-tray-icon.mjs +33 -33
- package/scripts/smoke/discord-release-workflow.mjs +24 -24
- package/scripts/smoke/git-install-update.mjs +255 -255
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
- package/scripts/smoke/hermes-api-install.mjs +56 -56
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -367
- package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
- package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
- package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
- package/scripts/smoke/hermes-rest-gateway.mjs +79 -70
- package/scripts/smoke/hermes-rest-live.mjs +42 -42
- package/scripts/smoke/hermes-roundtrip.mjs +167 -167
- package/scripts/smoke/hermes-settings-commands.mjs +349 -346
- package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
- package/scripts/smoke/live-view-diagnostics.mjs +53 -53
- package/scripts/smoke/live-view-environment.mjs +92 -92
- package/scripts/smoke/live-view-integration.mjs +450 -450
- package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
- package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
- package/scripts/smoke/model-registry.mjs +36 -36
- package/scripts/smoke/multi-project-ui.mjs +45 -45
- package/scripts/smoke/multi-worker-slots.mjs +42 -42
- package/scripts/smoke/notification-center.mjs +87 -87
- package/scripts/smoke/notification-inapp-preference.mjs +23 -23
- package/scripts/smoke/notification-taxonomy.mjs +58 -58
- package/scripts/smoke/orchestration-api.mjs +172 -172
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
- package/scripts/smoke/orchestration-live-run.mjs +176 -176
- package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
- package/scripts/smoke/orchestration-model-sync.mjs +30 -30
- package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
- package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
- package/scripts/smoke/permission-policy.mjs +50 -50
- package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
- package/scripts/smoke/provider-rest-api.mjs +124 -124
- package/scripts/smoke/provider-selection-status.mjs +52 -52
- package/scripts/smoke/run-state-refresh.mjs +52 -52
- package/scripts/smoke/runtime-manager.mjs +99 -99
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
- package/scripts/smoke/static-root-routing.mjs +21 -21
- package/scripts/smoke/strict-handoff-compact.mjs +60 -60
- package/scripts/smoke/taskmaster-config.mjs +24 -24
- package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
- package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
- package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
- package/scripts/smoke/telegram-control.mjs +242 -242
- package/scripts/smoke/tunnel-persistence.mjs +56 -56
- package/scripts/smoke/update-issue-progress.mjs +69 -69
- package/scripts/smoke/update-ux.mjs +55 -55
- package/scripts/smoke/v138-completion.mjs +132 -132
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
- package/scripts/smoke/v138-diagnostics.mjs +63 -63
- package/scripts/smoke/v138-issue-planner.mjs +33 -33
- package/scripts/smoke/v143-remote-control.mjs +76 -76
- package/scripts/smoke/v144-production-loop.mjs +47 -47
- package/scripts/smoke/v145-platformization.mjs +46 -46
- package/scripts/smoke/v146-control-room-ui.mjs +150 -150
- package/scripts/smoke/version-modal-autoshow.mjs +29 -29
- package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
- package/scripts/smoke/vscode-workbench-polish.mjs +461 -436
- package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
- package/scripts/smoke/workflow-templates.mjs +43 -43
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
- package/scripts/update-git-install.mjs +293 -293
- package/server/claude-sdk.js +920 -920
- package/server/cli.js +1039 -1039
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +344 -344
- package/server/daemon/manager.js +563 -563
- package/server/daemon-manager.js +964 -964
- package/server/database/db.js +908 -895
- package/server/database/json-store.js +197 -197
- package/server/gemini-cli.js +550 -550
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +201 -30
- package/server/load-env.js +35 -35
- package/server/middleware/auth.js +171 -156
- package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.test.ts +60 -0
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +101 -0
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/agent-card.ts +55 -55
- package/server/modules/orchestration/a2a/routes.ts +590 -590
- package/server/modules/orchestration/a2a/task-store.ts +178 -178
- package/server/modules/orchestration/a2a/types.ts +126 -126
- package/server/modules/orchestration/a2a/validator.ts +113 -113
- package/server/modules/orchestration/hermes/hermes.routes.ts +642 -583
- package/server/modules/orchestration/index.ts +101 -100
- package/server/modules/orchestration/preview/port-watcher.ts +112 -112
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
- package/server/modules/orchestration/preview/types.ts +19 -19
- package/server/modules/orchestration/security/permission-policy.ts +401 -401
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
- package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
- package/server/modules/orchestration/workflows/context-packet.ts +186 -186
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
- package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
- package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
- package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
- package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
- package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
- package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
- package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
- package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
- package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
- package/server/modules/orchestration/workspace/path-safety.ts +55 -55
- package/server/modules/orchestration/workspace/types.ts +52 -52
- package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
- package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
- package/server/modules/providers/index.ts +2 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +944 -944
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -462
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3008 -3008
- package/server/qwen-code-cli.js +410 -410
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1435 -1435
- package/server/routes/auth.js +154 -146
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +570 -570
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +41 -41
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1650 -1635
- package/server/routes/live-view.js +411 -411
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +62 -62
- package/server/routes/network.js +125 -125
- package/server/routes/platformization.js +198 -197
- package/server/routes/plugins.js +320 -320
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +917 -917
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +55 -55
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +140 -140
- package/server/routes/user.js +125 -125
- package/server/routes/webhooks.js +63 -63
- package/server/services/control-room.js +102 -102
- package/server/services/diagnostics.js +165 -165
- package/server/services/external-access.js +375 -375
- package/server/services/hermes-gateway.js +1562 -1247
- package/server/services/hermes-install-jobs.js +729 -729
- package/server/services/install-jobs.js +715 -715
- package/server/services/live-view.js +956 -956
- package/server/services/managed-runtimes.js +493 -493
- package/server/services/model-registry.js +144 -144
- package/server/services/notification-orchestrator.js +365 -365
- package/server/services/notification-taxonomy.js +204 -204
- package/server/services/platformization.js +844 -779
- package/server/services/production-agent-loop.js +248 -248
- package/server/services/provider-cli-versions.js +149 -149
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +396 -396
- package/server/services/public-api-manifest.js +190 -182
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +234 -234
- package/server/services/telegram/bot.js +331 -331
- package/server/services/telegram/control-center.js +979 -979
- package/server/services/telegram/telegram-http-client.js +151 -151
- package/server/services/telegram/translations.js +340 -340
- package/server/services/vapid-keys.js +36 -36
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +305 -305
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +79 -79
- package/shared/modelConstants.js +161 -161
- package/shared/networkHosts.js +22 -22
- package/dist/assets/index-DMz0zv6T.css +0 -32
|
@@ -84,7 +84,7 @@ import productionAgentLoopRoutes from './routes/production-agent-loop.js';
|
|
|
84
84
|
import platformizationRoutes from './routes/platformization.js';
|
|
85
85
|
import liveViewRoutes, { createLiveViewPublicRouter } from './routes/live-view.js';
|
|
86
86
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
87
|
-
import { createHermesTaskRouter, adapterRegistry, ClaudeCodeA2AAdapter, CodexA2AAdapter, CursorA2AAdapter, GeminiA2AAdapter, OpenCodeA2AAdapter, QwenA2AAdapter, createPreviewProxyRouter, createOrchestrationTaskRouter, createHermesRouter, createWorkflowRouter, } from './modules/orchestration/index.js';
|
|
87
|
+
import { createHermesTaskRouter, adapterRegistry, ClaudeCodeA2AAdapter, CodexA2AAdapter, CursorA2AAdapter, GeminiA2AAdapter, OpenCodeA2AAdapter, QwenA2AAdapter, JsonEventA2AAdapter, createPreviewProxyRouter, createOrchestrationTaskRouter, createHermesRouter, createWorkflowRouter, } from './modules/orchestration/index.js';
|
|
88
88
|
import networkRoutes from './routes/network.js';
|
|
89
89
|
import telegramRoutes from './routes/telegram.js';
|
|
90
90
|
import { restoreRequestedTunnel } from './services/external-access.js';
|
|
@@ -97,11 +97,38 @@ import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './util
|
|
|
97
97
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames, apiKeysDb } from './database/db.js';
|
|
98
98
|
import { setNotificationWebSocketServer } from './services/notification-orchestrator.js';
|
|
99
99
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
100
|
-
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
100
|
+
import { validateApiKey, authenticateToken, authenticateWebSocket, requireAdmin } from './middleware/auth.js';
|
|
101
|
+
import { filterProjectsForUser, userHasProjectAccess } from './services/platformization.js';
|
|
101
102
|
import { IS_PLATFORM } from './constants/config.js';
|
|
102
103
|
import { getConnectableHost } from '../shared/networkHosts.js';
|
|
103
104
|
import { buildDaemonCliCommand, handleDaemonCommand } from './daemon-manager.js';
|
|
104
105
|
const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode'];
|
|
106
|
+
function requireProjectAccess(capability = 'viewFiles') {
|
|
107
|
+
return (req, res, next) => {
|
|
108
|
+
const projectName = req.params.projectName || req.query.project || req.body?.project;
|
|
109
|
+
if (!projectName) {
|
|
110
|
+
return next();
|
|
111
|
+
}
|
|
112
|
+
if (!userHasProjectAccess(req.user, { name: String(projectName), projectName: String(projectName) }, capability)) {
|
|
113
|
+
return res.status(403).json({ error: 'Project access denied.' });
|
|
114
|
+
}
|
|
115
|
+
next();
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function requireProjectPathAccess(capability = 'viewFiles') {
|
|
119
|
+
return (req, res, next) => {
|
|
120
|
+
const projectPath = req.body?.projectPath || req.query.projectPath || os.homedir();
|
|
121
|
+
const resolvedProjectPath = path.resolve(String(projectPath));
|
|
122
|
+
if (!userHasProjectAccess(req.user, {
|
|
123
|
+
fullPath: resolvedProjectPath,
|
|
124
|
+
path: resolvedProjectPath,
|
|
125
|
+
projectPath: resolvedProjectPath,
|
|
126
|
+
}, capability)) {
|
|
127
|
+
return res.status(403).json({ error: 'Project access denied.' });
|
|
128
|
+
}
|
|
129
|
+
next();
|
|
130
|
+
};
|
|
131
|
+
}
|
|
105
132
|
// File system watchers for provider project/session folders
|
|
106
133
|
const PROVIDER_WATCH_PATHS = [
|
|
107
134
|
{ provider: 'claude', rootPath: path.join(os.homedir(), '.claude', 'projects') },
|
|
@@ -181,18 +208,20 @@ async function setupProjectsWatcher() {
|
|
|
181
208
|
clearProjectDirectoryCache();
|
|
182
209
|
// Get updated projects list
|
|
183
210
|
const updatedProjects = await getProjects(broadcastProgress);
|
|
184
|
-
|
|
185
|
-
const updateMessage = JSON.stringify({
|
|
211
|
+
const updatePayload = {
|
|
186
212
|
type: 'projects_updated',
|
|
187
|
-
projects: updatedProjects,
|
|
188
213
|
timestamp: new Date().toISOString(),
|
|
189
214
|
changeType: eventType,
|
|
190
215
|
changedFile: path.relative(rootPath, filePath),
|
|
191
216
|
watchProvider: provider
|
|
192
|
-
}
|
|
217
|
+
};
|
|
218
|
+
// Notify all connected clients about project changes, scoped to their access.
|
|
193
219
|
connectedClients.forEach(client => {
|
|
194
220
|
if (client.readyState === WebSocket.OPEN) {
|
|
195
|
-
client.send(
|
|
221
|
+
client.send(JSON.stringify({
|
|
222
|
+
...updatePayload,
|
|
223
|
+
projects: filterProjectsForUser(updatedProjects, client.user),
|
|
224
|
+
}));
|
|
196
225
|
}
|
|
197
226
|
});
|
|
198
227
|
}
|
|
@@ -249,6 +278,111 @@ async function setupProjectsWatcher() {
|
|
|
249
278
|
console.error('[ERROR] Failed to setup any provider watchers');
|
|
250
279
|
}
|
|
251
280
|
}
|
|
281
|
+
// ── Per-project workspace watchers (file explorer live refresh) ─────────────
|
|
282
|
+
// setupProjectsWatcher() above only watches provider metadata folders
|
|
283
|
+
// (~/.claude/projects etc.), so the file explorer never learned about changes
|
|
284
|
+
// inside the actual project working directory. These watchers are created on
|
|
285
|
+
// demand when a client sends `watch-project` over /ws and broadcast debounced
|
|
286
|
+
// `project_files_updated` events to subscribed clients only, letting the
|
|
287
|
+
// explorer refresh automatically without HTTP polling.
|
|
288
|
+
const WORKSPACE_WATCHER_DEBOUNCE_MS = 800;
|
|
289
|
+
const workspaceWatchers = new Map(); // projectName -> { watcher, subscribers, debounceTimer, pendingEvent, rootPath }
|
|
290
|
+
async function subscribeToWorkspace(ws, projectName) {
|
|
291
|
+
if (!projectName || typeof projectName !== 'string')
|
|
292
|
+
return;
|
|
293
|
+
if (!userHasProjectAccess(ws.user, { name: projectName, projectName }, 'viewFiles'))
|
|
294
|
+
return;
|
|
295
|
+
const existing = workspaceWatchers.get(projectName);
|
|
296
|
+
if (existing) {
|
|
297
|
+
existing.subscribers.add(ws);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
let rootPath;
|
|
301
|
+
try {
|
|
302
|
+
rootPath = await extractProjectDirectory(projectName);
|
|
303
|
+
await fsPromises.access(rootPath);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.warn(`[watcher] Cannot watch workspace for ${projectName}:`, error.message);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const entry = {
|
|
310
|
+
watcher: null,
|
|
311
|
+
subscribers: new Set([ws]),
|
|
312
|
+
debounceTimer: null,
|
|
313
|
+
pendingEvent: null,
|
|
314
|
+
rootPath,
|
|
315
|
+
};
|
|
316
|
+
workspaceWatchers.set(projectName, entry);
|
|
317
|
+
const broadcastFileUpdate = (eventType, filePath) => {
|
|
318
|
+
entry.pendingEvent = { eventType, filePath };
|
|
319
|
+
if (entry.debounceTimer) {
|
|
320
|
+
clearTimeout(entry.debounceTimer);
|
|
321
|
+
}
|
|
322
|
+
entry.debounceTimer = setTimeout(() => {
|
|
323
|
+
entry.debounceTimer = null;
|
|
324
|
+
const pending = entry.pendingEvent || {};
|
|
325
|
+
entry.pendingEvent = null;
|
|
326
|
+
const message = JSON.stringify({
|
|
327
|
+
type: 'project_files_updated',
|
|
328
|
+
projectName,
|
|
329
|
+
changeType: pending.eventType || 'change',
|
|
330
|
+
changedFile: pending.filePath ? path.relative(rootPath, pending.filePath) : null,
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
});
|
|
333
|
+
entry.subscribers.forEach((client) => {
|
|
334
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
335
|
+
client.send(message);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}, WORKSPACE_WATCHER_DEBOUNCE_MS);
|
|
339
|
+
};
|
|
340
|
+
try {
|
|
341
|
+
const chokidar = (await import('chokidar')).default;
|
|
342
|
+
const watcher = chokidar.watch(rootPath, {
|
|
343
|
+
ignored: WATCHER_IGNORED_PATTERNS,
|
|
344
|
+
persistent: true,
|
|
345
|
+
ignoreInitial: true,
|
|
346
|
+
followSymlinks: false,
|
|
347
|
+
depth: 10,
|
|
348
|
+
awaitWriteFinish: {
|
|
349
|
+
stabilityThreshold: 500,
|
|
350
|
+
pollInterval: 250
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
watcher
|
|
354
|
+
.on('add', (filePath) => broadcastFileUpdate('add', filePath))
|
|
355
|
+
.on('change', (filePath) => broadcastFileUpdate('change', filePath))
|
|
356
|
+
.on('unlink', (filePath) => broadcastFileUpdate('unlink', filePath))
|
|
357
|
+
.on('addDir', (dirPath) => broadcastFileUpdate('addDir', dirPath))
|
|
358
|
+
.on('unlinkDir', (dirPath) => broadcastFileUpdate('unlinkDir', dirPath))
|
|
359
|
+
.on('error', (error) => {
|
|
360
|
+
console.error(`[ERROR] Workspace watcher error for ${projectName}:`, error);
|
|
361
|
+
});
|
|
362
|
+
entry.watcher = watcher;
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
workspaceWatchers.delete(projectName);
|
|
366
|
+
console.error(`[ERROR] Failed to watch workspace for ${projectName}:`, error);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function unsubscribeFromWorkspace(ws, projectName = null) {
|
|
370
|
+
const entries = projectName
|
|
371
|
+
? (workspaceWatchers.has(projectName) ? [[projectName, workspaceWatchers.get(projectName)]] : [])
|
|
372
|
+
: Array.from(workspaceWatchers.entries());
|
|
373
|
+
for (const [name, entry] of entries) {
|
|
374
|
+
entry.subscribers.delete(ws);
|
|
375
|
+
if (entry.subscribers.size === 0) {
|
|
376
|
+
if (entry.debounceTimer) {
|
|
377
|
+
clearTimeout(entry.debounceTimer);
|
|
378
|
+
}
|
|
379
|
+
workspaceWatchers.delete(name);
|
|
380
|
+
entry.watcher?.close().catch((error) => {
|
|
381
|
+
console.warn(`[watcher] Failed to close workspace watcher for ${name}:`, error?.message || error);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
252
386
|
const app = express();
|
|
253
387
|
const server = http.createServer(app);
|
|
254
388
|
const ptySessionsMap = new Map();
|
|
@@ -661,7 +795,7 @@ app.get('/health', (req, res) => {
|
|
|
661
795
|
});
|
|
662
796
|
// Optional API key validation (if configured)
|
|
663
797
|
app.use('/api', validateApiKey);
|
|
664
|
-
app.post('/api/shell/sessions/terminate', authenticateToken, (req, res) => {
|
|
798
|
+
app.post('/api/shell/sessions/terminate', authenticateToken, requireProjectPathAccess('useShell'), (req, res) => {
|
|
665
799
|
const provider = req.body?.provider || 'claude';
|
|
666
800
|
const projectPath = req.body?.projectPath || os.homedir();
|
|
667
801
|
if (!SHELL_CLI_PROVIDERS.has(provider)) {
|
|
@@ -670,7 +804,7 @@ app.post('/api/shell/sessions/terminate', authenticateToken, (req, res) => {
|
|
|
670
804
|
const killedSessions = killProviderPtySessions(projectPath, provider);
|
|
671
805
|
res.json({ success: true, killedSessions });
|
|
672
806
|
});
|
|
673
|
-
app.get('/api/shell/sessions/provider-output', authenticateToken, (req, res) => {
|
|
807
|
+
app.get('/api/shell/sessions/provider-output', authenticateToken, requireProjectPathAccess('useShell'), (req, res) => {
|
|
674
808
|
const provider = String(req.query.provider || 'claude');
|
|
675
809
|
const projectPath = typeof req.query.projectPath === 'string' && req.query.projectPath.trim()
|
|
676
810
|
? req.query.projectPath.trim()
|
|
@@ -717,7 +851,7 @@ app.get('/api/shell/sessions/provider-output', authenticateToken, (req, res) =>
|
|
|
717
851
|
output,
|
|
718
852
|
});
|
|
719
853
|
});
|
|
720
|
-
app.post('/api/shell/sessions/provider-input', authenticateToken, (req, res) => {
|
|
854
|
+
app.post('/api/shell/sessions/provider-input', authenticateToken, requireProjectPathAccess('useShell'), (req, res) => {
|
|
721
855
|
const provider = String(req.body?.provider || 'claude');
|
|
722
856
|
const projectPath = typeof req.body?.projectPath === 'string' && req.body.projectPath.trim()
|
|
723
857
|
? req.body.projectPath.trim()
|
|
@@ -813,8 +947,8 @@ app.use('/api/public', authenticateToken, publicApiRoutes);
|
|
|
813
947
|
app.use('/api/webhooks', authenticateToken, webhooksRoutes);
|
|
814
948
|
// Production agent loop APIs (protected)
|
|
815
949
|
app.use('/api/production-agent-loop', authenticateToken, productionAgentLoopRoutes);
|
|
816
|
-
// Platform control plane APIs (
|
|
817
|
-
app.use('/api/platformization', authenticateToken, platformizationRoutes);
|
|
950
|
+
// Platform control plane APIs (admin-only)
|
|
951
|
+
app.use('/api/platformization', authenticateToken, requireAdmin, platformizationRoutes);
|
|
818
952
|
// Project Live View (protected control API + public share proxy)
|
|
819
953
|
app.use('/api/live-view', authenticateToken, liveViewRoutes);
|
|
820
954
|
// Unified provider MCP routes (protected)
|
|
@@ -826,6 +960,7 @@ adapterRegistry.register(new CursorA2AAdapter());
|
|
|
826
960
|
adapterRegistry.register(new GeminiA2AAdapter());
|
|
827
961
|
adapterRegistry.register(new QwenA2AAdapter());
|
|
828
962
|
adapterRegistry.register(new OpenCodeA2AAdapter());
|
|
963
|
+
adapterRegistry.register(new JsonEventA2AAdapter());
|
|
829
964
|
app.use('/hermes', createHermesTaskRouter());
|
|
830
965
|
app.use('/preview', authenticateToken, createPreviewProxyRouter());
|
|
831
966
|
app.use('/api/orchestration', authenticateToken, createOrchestrationTaskRouter());
|
|
@@ -1256,13 +1391,13 @@ app.post('/api/system/restart', authenticateToken, (req, res) => {
|
|
|
1256
1391
|
app.get('/api/projects', authenticateToken, async (req, res) => {
|
|
1257
1392
|
try {
|
|
1258
1393
|
const projects = await getProjects(broadcastProgress);
|
|
1259
|
-
res.json(projects);
|
|
1394
|
+
res.json(filterProjectsForUser(projects, req.user));
|
|
1260
1395
|
}
|
|
1261
1396
|
catch (error) {
|
|
1262
1397
|
res.status(500).json({ error: error.message });
|
|
1263
1398
|
}
|
|
1264
1399
|
});
|
|
1265
|
-
app.get('/api/projects/:projectName/sessions', authenticateToken, async (req, res) => {
|
|
1400
|
+
app.get('/api/projects/:projectName/sessions', authenticateToken, requireProjectAccess('viewFiles'), async (req, res) => {
|
|
1266
1401
|
try {
|
|
1267
1402
|
const { limit = 5, offset = 0 } = req.query;
|
|
1268
1403
|
const result = await getSessions(req.params.projectName, parseInt(limit), parseInt(offset));
|
|
@@ -1274,7 +1409,7 @@ app.get('/api/projects/:projectName/sessions', authenticateToken, async (req, re
|
|
|
1274
1409
|
}
|
|
1275
1410
|
});
|
|
1276
1411
|
// Rename project endpoint
|
|
1277
|
-
app.put('/api/projects/:projectName/rename', authenticateToken, async (req, res) => {
|
|
1412
|
+
app.put('/api/projects/:projectName/rename', authenticateToken, requireProjectAccess('manageProjectSettings'), async (req, res) => {
|
|
1278
1413
|
try {
|
|
1279
1414
|
const { displayName } = req.body;
|
|
1280
1415
|
await renameProject(req.params.projectName, displayName);
|
|
@@ -1285,7 +1420,7 @@ app.put('/api/projects/:projectName/rename', authenticateToken, async (req, res)
|
|
|
1285
1420
|
}
|
|
1286
1421
|
});
|
|
1287
1422
|
// Delete session endpoint
|
|
1288
|
-
app.delete('/api/projects/:projectName/sessions/:sessionId', authenticateToken, async (req, res) => {
|
|
1423
|
+
app.delete('/api/projects/:projectName/sessions/:sessionId', authenticateToken, requireProjectAccess('editFiles'), async (req, res) => {
|
|
1289
1424
|
try {
|
|
1290
1425
|
const { projectName, sessionId } = req.params;
|
|
1291
1426
|
console.log(`[API] Deleting session: ${sessionId} from project: ${projectName}`);
|
|
@@ -1328,7 +1463,7 @@ app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res) =
|
|
|
1328
1463
|
// Delete project endpoint
|
|
1329
1464
|
// force=true to allow removal even when sessions exist
|
|
1330
1465
|
// deleteData=true to also delete session/memory files on disk (destructive)
|
|
1331
|
-
app.delete('/api/projects/:projectName', authenticateToken, async (req, res) => {
|
|
1466
|
+
app.delete('/api/projects/:projectName', authenticateToken, requireProjectAccess('manageProjectSettings'), async (req, res) => {
|
|
1332
1467
|
try {
|
|
1333
1468
|
const { projectName } = req.params;
|
|
1334
1469
|
const force = req.query.force === 'true';
|
|
@@ -1346,7 +1481,7 @@ app.delete('/api/projects/:projectName', authenticateToken, async (req, res) =>
|
|
|
1346
1481
|
}
|
|
1347
1482
|
});
|
|
1348
1483
|
// Search conversations content (SSE streaming)
|
|
1349
|
-
app.get('/api/search/conversations', authenticateToken, async (req, res) => {
|
|
1484
|
+
app.get('/api/search/conversations', authenticateToken, requireAdmin, async (req, res) => {
|
|
1350
1485
|
const query = typeof req.query.q === 'string' ? req.query.q.trim() : '';
|
|
1351
1486
|
const parsedLimit = Number.parseInt(String(req.query.limit), 10);
|
|
1352
1487
|
const limit = Number.isNaN(parsedLimit) ? 50 : Math.max(1, Math.min(parsedLimit, 100));
|
|
@@ -1539,7 +1674,7 @@ app.post('/api/create-folder', authenticateToken, async (req, res) => {
|
|
|
1539
1674
|
}
|
|
1540
1675
|
});
|
|
1541
1676
|
// Read file content endpoint
|
|
1542
|
-
app.get('/api/projects/:projectName/file', authenticateToken, async (req, res) => {
|
|
1677
|
+
app.get('/api/projects/:projectName/file', authenticateToken, requireProjectAccess('viewFiles'), async (req, res) => {
|
|
1543
1678
|
try {
|
|
1544
1679
|
const { projectName } = req.params;
|
|
1545
1680
|
const { filePath } = req.query;
|
|
@@ -1576,7 +1711,7 @@ app.get('/api/projects/:projectName/file', authenticateToken, async (req, res) =
|
|
|
1576
1711
|
}
|
|
1577
1712
|
});
|
|
1578
1713
|
// Serve raw file bytes for previews and downloads.
|
|
1579
|
-
app.get('/api/projects/:projectName/files/content', authenticateToken, async (req, res) => {
|
|
1714
|
+
app.get('/api/projects/:projectName/files/content', authenticateToken, requireProjectAccess('viewFiles'), async (req, res) => {
|
|
1580
1715
|
try {
|
|
1581
1716
|
const { projectName } = req.params;
|
|
1582
1717
|
const { path: filePath } = req.query;
|
|
@@ -1625,7 +1760,7 @@ app.get('/api/projects/:projectName/files/content', authenticateToken, async (re
|
|
|
1625
1760
|
}
|
|
1626
1761
|
});
|
|
1627
1762
|
// Save file content endpoint
|
|
1628
|
-
app.put('/api/projects/:projectName/file', authenticateToken, async (req, res) => {
|
|
1763
|
+
app.put('/api/projects/:projectName/file', authenticateToken, requireProjectAccess('editFiles'), async (req, res) => {
|
|
1629
1764
|
try {
|
|
1630
1765
|
const { projectName } = req.params;
|
|
1631
1766
|
const { filePath, content } = req.body;
|
|
@@ -1669,7 +1804,7 @@ app.put('/api/projects/:projectName/file', authenticateToken, async (req, res) =
|
|
|
1669
1804
|
}
|
|
1670
1805
|
}
|
|
1671
1806
|
});
|
|
1672
|
-
app.get('/api/projects/:projectName/files', authenticateToken, async (req, res) => {
|
|
1807
|
+
app.get('/api/projects/:projectName/files', authenticateToken, requireProjectAccess('viewFiles'), async (req, res) => {
|
|
1673
1808
|
try {
|
|
1674
1809
|
// Using fsPromises from import
|
|
1675
1810
|
// Use extractProjectDirectory to get the actual project path
|
|
@@ -1679,8 +1814,12 @@ app.get('/api/projects/:projectName/files', authenticateToken, async (req, res)
|
|
|
1679
1814
|
}
|
|
1680
1815
|
catch (error) {
|
|
1681
1816
|
console.error('Error extracting project directory:', error);
|
|
1682
|
-
//
|
|
1683
|
-
|
|
1817
|
+
// Do NOT fall back to projectName.replace(/-/g, '/') here: on Windows the
|
|
1818
|
+
// dash-encoded name ("C--Users-...") decodes to a garbage path ("C//Users/...")
|
|
1819
|
+
// that can never exist, so the old fallback just produced a confusing 404
|
|
1820
|
+
// with a fabricated path. extractProjectDirectory already handles the
|
|
1821
|
+
// POSIX-style decode internally; if it throws, the project is unknown.
|
|
1822
|
+
return res.status(404).json({ error: `Project not found: ${req.params.projectName}` });
|
|
1684
1823
|
}
|
|
1685
1824
|
// Check if path exists
|
|
1686
1825
|
try {
|
|
@@ -1742,7 +1881,7 @@ function validateFilename(name) {
|
|
|
1742
1881
|
return { valid: true };
|
|
1743
1882
|
}
|
|
1744
1883
|
// POST /api/projects/:projectName/files/create - Create new file or directory
|
|
1745
|
-
app.post('/api/projects/:projectName/files/create', authenticateToken, async (req, res) => {
|
|
1884
|
+
app.post('/api/projects/:projectName/files/create', authenticateToken, requireProjectAccess('editFiles'), async (req, res) => {
|
|
1746
1885
|
try {
|
|
1747
1886
|
const { projectName } = req.params;
|
|
1748
1887
|
const { path: parentPath, type, name } = req.body;
|
|
@@ -1815,7 +1954,7 @@ app.post('/api/projects/:projectName/files/create', authenticateToken, async (re
|
|
|
1815
1954
|
}
|
|
1816
1955
|
});
|
|
1817
1956
|
// PUT /api/projects/:projectName/files/rename - Rename file or directory
|
|
1818
|
-
app.put('/api/projects/:projectName/files/rename', authenticateToken, async (req, res) => {
|
|
1957
|
+
app.put('/api/projects/:projectName/files/rename', authenticateToken, requireProjectAccess('editFiles'), async (req, res) => {
|
|
1819
1958
|
try {
|
|
1820
1959
|
const { projectName } = req.params;
|
|
1821
1960
|
const { oldPath, newName } = req.body;
|
|
@@ -1887,7 +2026,7 @@ app.put('/api/projects/:projectName/files/rename', authenticateToken, async (req
|
|
|
1887
2026
|
}
|
|
1888
2027
|
});
|
|
1889
2028
|
// DELETE /api/projects/:projectName/files - Delete file or directory
|
|
1890
|
-
app.delete('/api/projects/:projectName/files', authenticateToken, async (req, res) => {
|
|
2029
|
+
app.delete('/api/projects/:projectName/files', authenticateToken, requireProjectAccess('editFiles'), async (req, res) => {
|
|
1891
2030
|
try {
|
|
1892
2031
|
const { projectName } = req.params;
|
|
1893
2032
|
const { path: targetPath, type } = req.body;
|
|
@@ -2097,7 +2236,7 @@ const uploadFilesHandler = async (req, res) => {
|
|
|
2097
2236
|
}
|
|
2098
2237
|
});
|
|
2099
2238
|
};
|
|
2100
|
-
app.post('/api/projects/:projectName/files/upload', authenticateToken, uploadFilesHandler);
|
|
2239
|
+
app.post('/api/projects/:projectName/files/upload', authenticateToken, requireProjectAccess('editFiles'), uploadFilesHandler);
|
|
2101
2240
|
/**
|
|
2102
2241
|
* Proxy an authenticated client WebSocket to a plugin's internal WS server.
|
|
2103
2242
|
* Auth is enforced by verifyClient before this function is reached.
|
|
@@ -2196,6 +2335,7 @@ function handleChatConnection(ws, request) {
|
|
|
2196
2335
|
console.log('[INFO] Chat WebSocket connected');
|
|
2197
2336
|
// Add to connected clients for project updates
|
|
2198
2337
|
ws.userId = request?.user?.id ?? request?.user?.userId ?? null;
|
|
2338
|
+
ws.user = request?.user ?? null;
|
|
2199
2339
|
connectedClients.add(ws);
|
|
2200
2340
|
// Wrap WebSocket with writer for consistent interface with SSEStreamWriter
|
|
2201
2341
|
const writer = new WebSocketWriter(ws, request?.user?.id ?? request?.user?.userId ?? null);
|
|
@@ -2333,6 +2473,15 @@ function handleChatConnection(ws, request) {
|
|
|
2333
2473
|
});
|
|
2334
2474
|
}
|
|
2335
2475
|
}
|
|
2476
|
+
else if (data.type === 'watch-project') {
|
|
2477
|
+
// Subscribe this client to live file-tree updates for a project
|
|
2478
|
+
// workspace. The server pushes debounced `project_files_updated`
|
|
2479
|
+
// events so the explorer refreshes without HTTP polling.
|
|
2480
|
+
await subscribeToWorkspace(ws, data.projectName);
|
|
2481
|
+
}
|
|
2482
|
+
else if (data.type === 'unwatch-project') {
|
|
2483
|
+
unsubscribeFromWorkspace(ws, data.projectName || null);
|
|
2484
|
+
}
|
|
2336
2485
|
else if (data.type === 'get-active-sessions') {
|
|
2337
2486
|
// Get all currently active sessions
|
|
2338
2487
|
const activeSessions = {
|
|
@@ -2361,6 +2510,8 @@ function handleChatConnection(ws, request) {
|
|
|
2361
2510
|
console.log('🔌 Chat client disconnected');
|
|
2362
2511
|
// Remove from connected clients
|
|
2363
2512
|
connectedClients.delete(ws);
|
|
2513
|
+
// Drop any workspace watcher subscriptions held by this socket
|
|
2514
|
+
unsubscribeFromWorkspace(ws);
|
|
2364
2515
|
});
|
|
2365
2516
|
}
|
|
2366
2517
|
// Handle shell WebSocket connections
|
|
@@ -2389,6 +2540,15 @@ function handleShellConnection(ws, request) {
|
|
|
2389
2540
|
// is writable, has a git-friendly cwd, and matches where
|
|
2390
2541
|
// every provider already stores its config (~/.codex etc.).
|
|
2391
2542
|
const projectPath = data.projectPath || os.homedir();
|
|
2543
|
+
const requestedProjectPath = path.resolve(projectPath);
|
|
2544
|
+
if (!userHasProjectAccess(request.user, {
|
|
2545
|
+
fullPath: requestedProjectPath,
|
|
2546
|
+
path: requestedProjectPath,
|
|
2547
|
+
projectPath: requestedProjectPath,
|
|
2548
|
+
}, 'useShell')) {
|
|
2549
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Shell access denied for this project' }));
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2392
2552
|
const sessionId = data.sessionId;
|
|
2393
2553
|
const hasSession = data.hasSession;
|
|
2394
2554
|
const provider = data.provider || 'claude';
|
|
@@ -2515,7 +2675,7 @@ function handleShellConnection(ws, request) {
|
|
|
2515
2675
|
}));
|
|
2516
2676
|
try {
|
|
2517
2677
|
// Validate projectPath — resolve to absolute and verify it exists
|
|
2518
|
-
const resolvedProjectPath =
|
|
2678
|
+
const resolvedProjectPath = requestedProjectPath;
|
|
2519
2679
|
try {
|
|
2520
2680
|
const stats = fs.statSync(resolvedProjectPath);
|
|
2521
2681
|
if (!stats.isDirectory()) {
|
|
@@ -2861,7 +3021,7 @@ function handleShellConnection(ws, request) {
|
|
|
2861
3021
|
});
|
|
2862
3022
|
}
|
|
2863
3023
|
// Image upload endpoint
|
|
2864
|
-
app.post('/api/projects/:projectName/upload-images', authenticateToken, async (req, res) => {
|
|
3024
|
+
app.post('/api/projects/:projectName/upload-images', authenticateToken, requireProjectAccess('editFiles'), async (req, res) => {
|
|
2865
3025
|
try {
|
|
2866
3026
|
const multer = (await import('multer')).default;
|
|
2867
3027
|
const path = (await import('path')).default;
|
|
@@ -2937,7 +3097,7 @@ app.post('/api/projects/:projectName/upload-images', authenticateToken, async (r
|
|
|
2937
3097
|
}
|
|
2938
3098
|
});
|
|
2939
3099
|
// Get token usage for a specific session
|
|
2940
|
-
app.get('/api/projects/:projectName/sessions/:sessionId/token-usage', authenticateToken, async (req, res) => {
|
|
3100
|
+
app.get('/api/projects/:projectName/sessions/:sessionId/token-usage', authenticateToken, requireProjectAccess('viewFiles'), async (req, res) => {
|
|
2941
3101
|
try {
|
|
2942
3102
|
const { projectName, sessionId } = req.params;
|
|
2943
3103
|
const { provider = 'claude' } = req.query;
|