@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.
Files changed (331) hide show
  1. package/CODE_OF_CONDUCT.md +41 -41
  2. package/CONTRIBUTING.md +155 -155
  3. package/LICENSE +718 -718
  4. package/README.de.md +169 -169
  5. package/README.ja.md +167 -167
  6. package/README.ko.md +167 -167
  7. package/README.md +419 -419
  8. package/README.ru.md +169 -169
  9. package/README.tr.md +298 -298
  10. package/README.zh-CN.md +167 -167
  11. package/SECURITY.md +46 -46
  12. package/dist/api-automation.html +110 -110
  13. package/dist/api-docs.html +548 -548
  14. package/dist/assets/index-B9N-gfOQ.css +32 -0
  15. package/dist/assets/{index-EN9ngyxf.js → index-HfGHXhD6.js} +175 -175
  16. package/dist/clear-cache.html +85 -85
  17. package/dist/convert-icons.md +52 -52
  18. package/dist/docs.html +308 -308
  19. package/dist/favicon.svg +8 -8
  20. package/dist/features.html +133 -133
  21. package/dist/generate-icons.js +48 -48
  22. package/dist/humans.txt +15 -15
  23. package/dist/icons/codex-white.svg +3 -3
  24. package/dist/icons/codex.svg +3 -3
  25. package/dist/icons/cursor-white.svg +11 -11
  26. package/dist/icons/icon-128x128.svg +9 -9
  27. package/dist/icons/icon-144x144.svg +9 -9
  28. package/dist/icons/icon-152x152.svg +9 -9
  29. package/dist/icons/icon-192x192.svg +9 -9
  30. package/dist/icons/icon-384x384.svg +9 -9
  31. package/dist/icons/icon-512x512.svg +9 -9
  32. package/dist/icons/icon-72x72.svg +9 -9
  33. package/dist/icons/icon-96x96.svg +9 -9
  34. package/dist/icons/icon-template.svg +9 -9
  35. package/dist/icons/qwen-logo.svg +14 -14
  36. package/dist/index.html +59 -59
  37. package/dist/landing.html +268 -268
  38. package/dist/llms-full.txt +119 -119
  39. package/dist/llms.txt +53 -53
  40. package/dist/logo.svg +12 -12
  41. package/dist/manifest.json +60 -60
  42. package/dist/openapi.yaml +1696 -1696
  43. package/dist/orchestration.html +125 -125
  44. package/dist/robots.txt +4 -4
  45. package/dist/site.css +692 -692
  46. package/dist/sitemap.xml +51 -51
  47. package/dist/sw.js +132 -132
  48. package/dist-server/server/cli.js +96 -96
  49. package/dist-server/server/daemon/manager.js +33 -33
  50. package/dist-server/server/daemon-manager.js +64 -64
  51. package/dist-server/server/database/db.js +14 -2
  52. package/dist-server/server/database/db.js.map +1 -1
  53. package/dist-server/server/index.js +191 -31
  54. package/dist-server/server/index.js.map +1 -1
  55. package/dist-server/server/middleware/auth.js +16 -5
  56. package/dist-server/server/middleware/auth.js.map +1 -1
  57. package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js +84 -0
  58. package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js.map +1 -0
  59. package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js +43 -0
  60. package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js.map +1 -0
  61. package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +55 -1
  62. package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
  63. package/dist-server/server/modules/orchestration/index.js +1 -0
  64. package/dist-server/server/modules/orchestration/index.js.map +1 -1
  65. package/dist-server/server/routes/auth.js +12 -5
  66. package/dist-server/server/routes/auth.js.map +1 -1
  67. package/dist-server/server/routes/commands.js +25 -25
  68. package/dist-server/server/routes/git.js +29 -17
  69. package/dist-server/server/routes/git.js.map +1 -1
  70. package/dist-server/server/routes/live-view.js +46 -46
  71. package/dist-server/server/routes/platformization.js +7 -6
  72. package/dist-server/server/routes/platformization.js.map +1 -1
  73. package/dist-server/server/services/hermes-gateway.js +310 -0
  74. package/dist-server/server/services/hermes-gateway.js.map +1 -1
  75. package/dist-server/server/services/platformization.js +58 -2
  76. package/dist-server/server/services/platformization.js.map +1 -1
  77. package/dist-server/server/services/public-api-manifest.js +59 -51
  78. package/dist-server/server/services/public-api-manifest.js.map +1 -1
  79. package/package.json +222 -222
  80. package/scripts/fix-node-pty.js +67 -67
  81. package/scripts/github/create-v1.38-issues.mjs +351 -351
  82. package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
  83. package/scripts/hermes/configure-pixcode-mcp.mjs +165 -163
  84. package/scripts/hermes/pixcode-mcp-server.mjs +1009 -958
  85. package/scripts/smoke/changes-panel-layout.mjs +48 -48
  86. package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
  87. package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
  88. package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
  89. package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
  90. package/scripts/smoke/chat-session-state.mjs +19 -19
  91. package/scripts/smoke/code-editor-theme.mjs +55 -55
  92. package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
  93. package/scripts/smoke/command-center-agent-writes.mjs +79 -79
  94. package/scripts/smoke/command-center-non-git.mjs +46 -46
  95. package/scripts/smoke/context-packet.mjs +43 -43
  96. package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
  97. package/scripts/smoke/daemon-entrypoint.mjs +20 -20
  98. package/scripts/smoke/default-landing-routing.mjs +33 -33
  99. package/scripts/smoke/desktop-native-notifications.mjs +30 -30
  100. package/scripts/smoke/desktop-tray-icon.mjs +33 -33
  101. package/scripts/smoke/discord-release-workflow.mjs +24 -24
  102. package/scripts/smoke/git-install-update.mjs +255 -255
  103. package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
  104. package/scripts/smoke/hermes-api-install.mjs +56 -56
  105. package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
  106. package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -367
  107. package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
  108. package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
  109. package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
  110. package/scripts/smoke/hermes-rest-gateway.mjs +79 -70
  111. package/scripts/smoke/hermes-rest-live.mjs +42 -42
  112. package/scripts/smoke/hermes-roundtrip.mjs +167 -167
  113. package/scripts/smoke/hermes-settings-commands.mjs +349 -346
  114. package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
  115. package/scripts/smoke/live-view-diagnostics.mjs +53 -53
  116. package/scripts/smoke/live-view-environment.mjs +92 -92
  117. package/scripts/smoke/live-view-integration.mjs +450 -450
  118. package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
  119. package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
  120. package/scripts/smoke/model-registry.mjs +36 -36
  121. package/scripts/smoke/multi-project-ui.mjs +45 -45
  122. package/scripts/smoke/multi-worker-slots.mjs +42 -42
  123. package/scripts/smoke/notification-center.mjs +87 -87
  124. package/scripts/smoke/notification-inapp-preference.mjs +23 -23
  125. package/scripts/smoke/notification-taxonomy.mjs +58 -58
  126. package/scripts/smoke/orchestration-api.mjs +172 -172
  127. package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
  128. package/scripts/smoke/orchestration-live-run.mjs +176 -176
  129. package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
  130. package/scripts/smoke/orchestration-model-sync.mjs +30 -30
  131. package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
  132. package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
  133. package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
  134. package/scripts/smoke/permission-policy.mjs +50 -50
  135. package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
  136. package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
  137. package/scripts/smoke/provider-rest-api.mjs +124 -124
  138. package/scripts/smoke/provider-selection-status.mjs +52 -52
  139. package/scripts/smoke/run-state-refresh.mjs +52 -52
  140. package/scripts/smoke/runtime-manager.mjs +99 -99
  141. package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
  142. package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
  143. package/scripts/smoke/static-root-routing.mjs +21 -21
  144. package/scripts/smoke/strict-handoff-compact.mjs +60 -60
  145. package/scripts/smoke/taskmaster-config.mjs +24 -24
  146. package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
  147. package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
  148. package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
  149. package/scripts/smoke/telegram-control.mjs +242 -242
  150. package/scripts/smoke/tunnel-persistence.mjs +56 -56
  151. package/scripts/smoke/update-issue-progress.mjs +69 -69
  152. package/scripts/smoke/update-ux.mjs +55 -55
  153. package/scripts/smoke/v138-completion.mjs +132 -132
  154. package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
  155. package/scripts/smoke/v138-diagnostics.mjs +63 -63
  156. package/scripts/smoke/v138-issue-planner.mjs +33 -33
  157. package/scripts/smoke/v143-remote-control.mjs +76 -76
  158. package/scripts/smoke/v144-production-loop.mjs +47 -47
  159. package/scripts/smoke/v145-platformization.mjs +46 -46
  160. package/scripts/smoke/v146-control-room-ui.mjs +150 -150
  161. package/scripts/smoke/version-modal-autoshow.mjs +29 -29
  162. package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
  163. package/scripts/smoke/vscode-workbench-polish.mjs +461 -436
  164. package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
  165. package/scripts/smoke/workflow-templates.mjs +43 -43
  166. package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
  167. package/scripts/update-git-install.mjs +293 -293
  168. package/server/claude-sdk.js +920 -920
  169. package/server/cli.js +1039 -1039
  170. package/server/constants/config.js +4 -4
  171. package/server/cursor-cli.js +344 -344
  172. package/server/daemon/manager.js +563 -563
  173. package/server/daemon-manager.js +964 -964
  174. package/server/database/db.js +908 -895
  175. package/server/database/json-store.js +197 -197
  176. package/server/gemini-cli.js +550 -550
  177. package/server/gemini-response-handler.js +79 -79
  178. package/server/index.js +201 -30
  179. package/server/load-env.js +35 -35
  180. package/server/middleware/auth.js +171 -156
  181. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
  182. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
  183. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
  184. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
  185. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
  186. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
  187. package/server/modules/orchestration/a2a/adapters/json-event.adapter.test.ts +60 -0
  188. package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +101 -0
  189. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
  190. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
  191. package/server/modules/orchestration/a2a/agent-card.ts +55 -55
  192. package/server/modules/orchestration/a2a/routes.ts +590 -590
  193. package/server/modules/orchestration/a2a/task-store.ts +178 -178
  194. package/server/modules/orchestration/a2a/types.ts +126 -126
  195. package/server/modules/orchestration/a2a/validator.ts +113 -113
  196. package/server/modules/orchestration/hermes/hermes.routes.ts +642 -583
  197. package/server/modules/orchestration/index.ts +101 -100
  198. package/server/modules/orchestration/preview/port-watcher.ts +112 -112
  199. package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
  200. package/server/modules/orchestration/preview/types.ts +19 -19
  201. package/server/modules/orchestration/security/permission-policy.ts +401 -401
  202. package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
  203. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
  204. package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
  205. package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
  206. package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
  207. package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
  208. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
  209. package/server/modules/orchestration/workflows/context-packet.ts +186 -186
  210. package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
  211. package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
  212. package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
  213. package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
  214. package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
  215. package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
  216. package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
  217. package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
  218. package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
  219. package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
  220. package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
  221. package/server/modules/orchestration/workspace/path-safety.ts +55 -55
  222. package/server/modules/orchestration/workspace/types.ts +52 -52
  223. package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
  224. package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
  225. package/server/modules/providers/index.ts +2 -2
  226. package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
  227. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  228. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
  229. package/server/modules/providers/list/claude/claude.provider.ts +15 -15
  230. package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
  231. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  232. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
  233. package/server/modules/providers/list/codex/codex.provider.ts +15 -15
  234. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
  235. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  236. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
  237. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
  238. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
  239. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
  240. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
  241. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
  242. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
  243. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  244. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
  245. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  246. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
  247. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  248. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
  249. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  250. package/server/modules/providers/provider.registry.ts +40 -40
  251. package/server/modules/providers/provider.routes.ts +944 -944
  252. package/server/modules/providers/services/mcp.service.ts +86 -86
  253. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  254. package/server/modules/providers/services/sessions.service.ts +45 -45
  255. package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
  256. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  257. package/server/modules/providers/shared/provider-configs.ts +142 -142
  258. package/server/modules/providers/tests/mcp.test.ts +293 -293
  259. package/server/openai-codex.js +462 -462
  260. package/server/opencode-cli.js +491 -491
  261. package/server/opencode-response-handler.js +111 -111
  262. package/server/projects.js +3008 -3008
  263. package/server/qwen-code-cli.js +410 -410
  264. package/server/qwen-response-handler.js +73 -73
  265. package/server/routes/agent.js +1435 -1435
  266. package/server/routes/auth.js +154 -146
  267. package/server/routes/codex.js +20 -20
  268. package/server/routes/commands.js +570 -570
  269. package/server/routes/cursor.js +61 -61
  270. package/server/routes/diagnostics.js +41 -41
  271. package/server/routes/gemini.js +25 -25
  272. package/server/routes/git.js +1650 -1635
  273. package/server/routes/live-view.js +411 -411
  274. package/server/routes/mcp-utils.js +13 -13
  275. package/server/routes/messages.js +62 -62
  276. package/server/routes/network.js +125 -125
  277. package/server/routes/platformization.js +198 -197
  278. package/server/routes/plugins.js +320 -320
  279. package/server/routes/production-agent-loop.js +90 -90
  280. package/server/routes/projects.js +917 -917
  281. package/server/routes/public-api.js +34 -34
  282. package/server/routes/qwen.js +27 -27
  283. package/server/routes/remote.js +55 -55
  284. package/server/routes/settings.js +321 -321
  285. package/server/routes/telegram.js +140 -140
  286. package/server/routes/user.js +125 -125
  287. package/server/routes/webhooks.js +63 -63
  288. package/server/services/control-room.js +102 -102
  289. package/server/services/diagnostics.js +165 -165
  290. package/server/services/external-access.js +375 -375
  291. package/server/services/hermes-gateway.js +1562 -1247
  292. package/server/services/hermes-install-jobs.js +729 -729
  293. package/server/services/install-jobs.js +715 -715
  294. package/server/services/live-view.js +956 -956
  295. package/server/services/managed-runtimes.js +493 -493
  296. package/server/services/model-registry.js +144 -144
  297. package/server/services/notification-orchestrator.js +365 -365
  298. package/server/services/notification-taxonomy.js +204 -204
  299. package/server/services/platformization.js +844 -779
  300. package/server/services/production-agent-loop.js +248 -248
  301. package/server/services/provider-cli-versions.js +149 -149
  302. package/server/services/provider-credentials.js +189 -189
  303. package/server/services/provider-models.js +396 -396
  304. package/server/services/public-api-manifest.js +190 -182
  305. package/server/services/remote-connection.js +127 -127
  306. package/server/services/runtime-manager.js +323 -323
  307. package/server/services/startup-update.js +234 -234
  308. package/server/services/telegram/bot.js +331 -331
  309. package/server/services/telegram/control-center.js +979 -979
  310. package/server/services/telegram/telegram-http-client.js +151 -151
  311. package/server/services/telegram/translations.js +340 -340
  312. package/server/services/vapid-keys.js +36 -36
  313. package/server/services/webhooks.js +216 -216
  314. package/server/sessionManager.js +225 -225
  315. package/server/shared/interfaces.ts +54 -54
  316. package/server/shared/types.ts +172 -172
  317. package/server/shared/utils.ts +193 -193
  318. package/server/tsconfig.json +36 -36
  319. package/server/utils/colors.js +21 -21
  320. package/server/utils/commandParser.js +305 -305
  321. package/server/utils/frontmatter.js +18 -18
  322. package/server/utils/gitConfig.js +34 -34
  323. package/server/utils/plugin-loader.js +457 -457
  324. package/server/utils/plugin-process-manager.js +185 -185
  325. package/server/utils/port-access.js +209 -209
  326. package/server/utils/runtime-paths.js +37 -37
  327. package/server/utils/url-detection.js +71 -71
  328. package/server/vite-daemon.js +79 -79
  329. package/shared/modelConstants.js +161 -161
  330. package/shared/networkHosts.js +22 -22
  331. 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
- // Notify all connected clients about the project changes
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(updateMessage);
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 (protected)
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
- // Fallback to simple dash replacement
1683
- actualPath = req.params.projectName.replace(/-/g, '/');
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 = path.resolve(projectPath);
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;