@neuralis/brain-core 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +88 -1417
  2. package/app/README.md +4 -4
  3. package/app/cards/FilesystemCardShell.tsx +42 -4
  4. package/app/cards/FilesystemChangeCard.tsx +34 -11
  5. package/app/cards/README.md +6 -5
  6. package/app/cards/diffUtils.ts +99 -1
  7. package/app/filesystem/FilesystemHost.tsx +24 -2
  8. package/app/filesystem/README.md +1 -1
  9. package/app/filesystem/api/README.md +3 -3
  10. package/app/filesystem/api/index.ts +38 -14
  11. package/app/filesystem/api/projectPackages.ts +13 -102
  12. package/app/filesystem/domain/types.ts +30 -10
  13. package/app/filesystem/hooks/README.md +4 -4
  14. package/app/filesystem/hooks/useDiscoverableSources.ts +19 -30
  15. package/app/filesystem/hooks/useFilesystemSSE.ts +42 -2
  16. package/app/filesystem/host/README.md +6 -4
  17. package/app/filesystem/host/buildFilesystemHostValue.ts +28 -2
  18. package/app/filesystem/host/installFilesystemHostComponents.tsx +1 -1
  19. package/app/filesystem/lib/sourceKindForm.ts +210 -27
  20. package/app/filesystem/lib/treeVisualState.tsx +98 -4
  21. package/app/filesystem/store/README.md +1 -0
  22. package/app/filesystem/store/filesystemStore.ts +99 -8
  23. package/app/filesystem/store/gitSlice.ts +5 -0
  24. package/app/filesystem/store/helpers.ts +17 -0
  25. package/app/filesystem/store/listSlice.ts +8 -0
  26. package/app/filesystem/ui/CodeEditor.tsx +18 -3
  27. package/app/filesystem/ui/DiscoverDock.tsx +5 -1
  28. package/app/filesystem/ui/FileEditor.tsx +15 -10
  29. package/app/filesystem/ui/FileTree.tsx +75 -24
  30. package/app/filesystem/ui/FilesystemWidget.tsx +10 -2
  31. package/app/filesystem/ui/FolderPicker.tsx +251 -0
  32. package/app/filesystem/ui/GitControlPanel.tsx +7 -2
  33. package/app/filesystem/ui/PackagesPanel.tsx +52 -374
  34. package/app/filesystem/ui/PendingChangesPanel.tsx +1 -1
  35. package/app/filesystem/ui/PolicyEditor.tsx +109 -24
  36. package/app/filesystem/ui/README.md +30 -23
  37. package/app/filesystem/ui/RawMediaPreview.tsx +58 -0
  38. package/app/filesystem/ui/SourceBranchHeader.tsx +4 -4
  39. package/app/filesystem/ui/SourcesPanel.tsx +502 -342
  40. package/app/filesystem/ui/Toolbar.tsx +1 -2
  41. package/app/filesystem/ui/VectorHealthPanel.tsx +2 -2
  42. package/app/shared/filesystem.png +0 -0
  43. package/dist/app/host.js +58 -46
  44. package/dist/scripts/sourceRootPairV1.d.ts +47 -0
  45. package/dist/scripts/sourceRootPairV1.d.ts.map +1 -0
  46. package/dist/scripts/sourceRootPairV1.js +147 -0
  47. package/dist/scripts/sourceRootPairV1.js.map +1 -0
  48. package/dist/scripts/sourceToFolderV1.d.ts +56 -0
  49. package/dist/scripts/sourceToFolderV1.d.ts.map +1 -0
  50. package/dist/scripts/sourceToFolderV1.js +152 -0
  51. package/dist/scripts/sourceToFolderV1.js.map +1 -0
  52. package/dist/scripts/sourceToHostV1.d.ts +56 -0
  53. package/dist/scripts/sourceToHostV1.d.ts.map +1 -0
  54. package/dist/scripts/sourceToHostV1.js +132 -0
  55. package/dist/scripts/sourceToHostV1.js.map +1 -0
  56. package/dist/src/channels/filesystem.d.ts +1 -1
  57. package/dist/src/channels/filesystem.js +1 -1
  58. package/dist/src/connectors/AppSourceConfigStore.d.ts +17 -3
  59. package/dist/src/connectors/AppSourceConfigStore.d.ts.map +1 -1
  60. package/dist/src/connectors/AppSourceConfigStore.js +46 -6
  61. package/dist/src/connectors/AppSourceConfigStore.js.map +1 -1
  62. package/dist/src/connectors/HostConnector.d.ts +201 -0
  63. package/dist/src/connectors/HostConnector.d.ts.map +1 -0
  64. package/dist/src/connectors/HostConnector.js +903 -0
  65. package/dist/src/connectors/HostConnector.js.map +1 -0
  66. package/dist/src/connectors/inProcessTransport.d.ts +131 -0
  67. package/dist/src/connectors/inProcessTransport.d.ts.map +1 -0
  68. package/dist/src/connectors/inProcessTransport.js +447 -0
  69. package/dist/src/connectors/inProcessTransport.js.map +1 -0
  70. package/dist/src/domain/access/callerPermissions.d.ts +29 -0
  71. package/dist/src/domain/access/callerPermissions.d.ts.map +1 -1
  72. package/dist/src/domain/access/callerPermissions.js +85 -5
  73. package/dist/src/domain/access/callerPermissions.js.map +1 -1
  74. package/dist/src/domain/access/nativeLocalFence.d.ts +65 -0
  75. package/dist/src/domain/access/nativeLocalFence.d.ts.map +1 -0
  76. package/dist/src/domain/access/nativeLocalFence.js +171 -0
  77. package/dist/src/domain/access/nativeLocalFence.js.map +1 -0
  78. package/dist/src/domain/access/uriPermission.js +3 -3
  79. package/dist/src/domain/access/uriPermission.js.map +1 -1
  80. package/dist/src/domain/byteRange.d.ts +15 -0
  81. package/dist/src/domain/byteRange.d.ts.map +1 -0
  82. package/dist/src/domain/byteRange.js +30 -0
  83. package/dist/src/domain/byteRange.js.map +1 -0
  84. package/dist/src/domain/defaultPermissions.d.ts.map +1 -1
  85. package/dist/src/domain/defaultPermissions.js +29 -0
  86. package/dist/src/domain/defaultPermissions.js.map +1 -1
  87. package/dist/src/domain/mime.d.ts +11 -19
  88. package/dist/src/domain/mime.d.ts.map +1 -1
  89. package/dist/src/domain/mime.js +13 -102
  90. package/dist/src/domain/mime.js.map +1 -1
  91. package/dist/src/domain/paths.d.ts.map +1 -1
  92. package/dist/src/domain/paths.js +5 -3
  93. package/dist/src/domain/paths.js.map +1 -1
  94. package/dist/src/domain/scopeAccessCtx.d.ts +2 -2
  95. package/dist/src/domain/scopeAccessCtx.js +2 -2
  96. package/dist/src/domain/sourceConfig.d.ts +52 -19
  97. package/dist/src/domain/sourceConfig.d.ts.map +1 -1
  98. package/dist/src/domain/sourceConfig.js +103 -19
  99. package/dist/src/domain/sourceConfig.js.map +1 -1
  100. package/dist/src/domain/sourcePackages.d.ts +8 -8
  101. package/dist/src/domain/sourcePackages.d.ts.map +1 -1
  102. package/dist/src/domain/sourceRootPair.d.ts +88 -0
  103. package/dist/src/domain/sourceRootPair.d.ts.map +1 -0
  104. package/dist/src/domain/sourceRootPair.js +108 -0
  105. package/dist/src/domain/sourceRootPair.js.map +1 -0
  106. package/dist/src/domain/syncTypes.d.ts +8 -1
  107. package/dist/src/domain/syncTypes.d.ts.map +1 -1
  108. package/dist/src/domain/syncTypes.js +1 -0
  109. package/dist/src/domain/syncTypes.js.map +1 -1
  110. package/dist/src/domain/types.d.ts +32 -2
  111. package/dist/src/domain/types.d.ts.map +1 -1
  112. package/dist/src/domain/uri.d.ts.map +1 -1
  113. package/dist/src/domain/uri.js +3 -2
  114. package/dist/src/domain/uri.js.map +1 -1
  115. package/dist/src/embeddings/clients/GeminiEmbedder.d.ts.map +1 -1
  116. package/dist/src/embeddings/clients/GeminiEmbedder.js +2 -5
  117. package/dist/src/embeddings/clients/GeminiEmbedder.js.map +1 -1
  118. package/dist/src/embeddings/clients/OpenAIEmbedder.d.ts.map +1 -1
  119. package/dist/src/embeddings/clients/OpenAIEmbedder.js +2 -3
  120. package/dist/src/embeddings/clients/OpenAIEmbedder.js.map +1 -1
  121. package/dist/src/embeddings/clients/QwenEmbedder.d.ts +2 -0
  122. package/dist/src/embeddings/clients/QwenEmbedder.d.ts.map +1 -1
  123. package/dist/src/embeddings/clients/QwenEmbedder.js +10 -2
  124. package/dist/src/embeddings/clients/QwenEmbedder.js.map +1 -1
  125. package/dist/src/embeddings/clients/VoyageEmbedder.d.ts +2 -0
  126. package/dist/src/embeddings/clients/VoyageEmbedder.d.ts.map +1 -1
  127. package/dist/src/embeddings/clients/VoyageEmbedder.js +8 -1
  128. package/dist/src/embeddings/clients/VoyageEmbedder.js.map +1 -1
  129. package/dist/src/embeddings/factory.d.ts +5 -3
  130. package/dist/src/embeddings/factory.d.ts.map +1 -1
  131. package/dist/src/embeddings/factory.js +7 -3
  132. package/dist/src/embeddings/factory.js.map +1 -1
  133. package/dist/src/embeddings/models.d.ts +1 -1
  134. package/dist/src/embeddings/models.d.ts.map +1 -1
  135. package/dist/src/embeddings/retry.js +1 -1
  136. package/dist/src/embeddings/retry.js.map +1 -1
  137. package/dist/src/embeddings/types.d.ts +6 -0
  138. package/dist/src/embeddings/types.d.ts.map +1 -1
  139. package/dist/src/embeddings/types.js +11 -0
  140. package/dist/src/embeddings/types.js.map +1 -1
  141. package/dist/src/events/pending.d.ts +1 -1
  142. package/dist/src/events/pending.js +1 -1
  143. package/dist/src/git/GitRunner.d.ts +110 -30
  144. package/dist/src/git/GitRunner.d.ts.map +1 -1
  145. package/dist/src/git/GitRunner.js +511 -199
  146. package/dist/src/git/GitRunner.js.map +1 -1
  147. package/dist/src/git/GitWatchRegistry.d.ts +41 -35
  148. package/dist/src/git/GitWatchRegistry.d.ts.map +1 -1
  149. package/dist/src/git/GitWatchRegistry.js +61 -79
  150. package/dist/src/git/GitWatchRegistry.js.map +1 -1
  151. package/dist/src/git/gitPlace.d.ts +61 -0
  152. package/dist/src/git/gitPlace.d.ts.map +1 -0
  153. package/dist/src/git/gitPlace.js +74 -0
  154. package/dist/src/git/gitPlace.js.map +1 -0
  155. package/dist/src/git/sourceGitControl.d.ts +33 -21
  156. package/dist/src/git/sourceGitControl.d.ts.map +1 -1
  157. package/dist/src/git/sourceGitControl.js +211 -95
  158. package/dist/src/git/sourceGitControl.js.map +1 -1
  159. package/dist/src/git/sourceGitStatus.js +1 -1
  160. package/dist/src/git/sourceGitStatus.js.map +1 -1
  161. package/dist/src/git/untrackedStats.js +1 -1
  162. package/dist/src/git/untrackedStats.js.map +1 -1
  163. package/dist/src/index.d.ts +2 -1
  164. package/dist/src/index.d.ts.map +1 -1
  165. package/dist/src/index.js +2 -1
  166. package/dist/src/index.js.map +1 -1
  167. package/dist/src/lifecycle.d.ts +9 -12
  168. package/dist/src/lifecycle.d.ts.map +1 -1
  169. package/dist/src/lifecycle.js +2 -1
  170. package/dist/src/lifecycle.js.map +1 -1
  171. package/dist/src/routes/approve.d.ts +1 -1
  172. package/dist/src/routes/approve.js +1 -1
  173. package/dist/src/routes/events.d.ts +1 -1
  174. package/dist/src/routes/events.js +1 -1
  175. package/dist/src/routes/git.d.ts +7 -7
  176. package/dist/src/routes/git.d.ts.map +1 -1
  177. package/dist/src/routes/git.js +201 -72
  178. package/dist/src/routes/git.js.map +1 -1
  179. package/dist/src/routes/health.d.ts +6 -6
  180. package/dist/src/routes/health.js +12 -12
  181. package/dist/src/routes/helpers/sourceNotConfigured.d.ts +1 -1
  182. package/dist/src/routes/helpers/sourceNotConfigured.js +1 -1
  183. package/dist/src/routes/helpers/uriGuard.d.ts +1 -1
  184. package/dist/src/routes/helpers/uriGuard.d.ts.map +1 -1
  185. package/dist/src/routes/helpers/uriGuard.js +3 -2
  186. package/dist/src/routes/helpers/uriGuard.js.map +1 -1
  187. package/dist/src/routes/helpers.d.ts +11 -4
  188. package/dist/src/routes/helpers.d.ts.map +1 -1
  189. package/dist/src/routes/helpers.js +13 -6
  190. package/dist/src/routes/helpers.js.map +1 -1
  191. package/dist/src/routes/list.d.ts +1 -1
  192. package/dist/src/routes/list.d.ts.map +1 -1
  193. package/dist/src/routes/list.js +3 -4
  194. package/dist/src/routes/list.js.map +1 -1
  195. package/dist/src/routes/pending.d.ts +4 -4
  196. package/dist/src/routes/pending.js +9 -9
  197. package/dist/src/routes/policies.d.ts +10 -8
  198. package/dist/src/routes/policies.d.ts.map +1 -1
  199. package/dist/src/routes/policies.js +31 -25
  200. package/dist/src/routes/policies.js.map +1 -1
  201. package/dist/src/routes/raw.d.ts +1 -1
  202. package/dist/src/routes/raw.d.ts.map +1 -1
  203. package/dist/src/routes/raw.js +66 -12
  204. package/dist/src/routes/raw.js.map +1 -1
  205. package/dist/src/routes/read.d.ts +1 -1
  206. package/dist/src/routes/read.js +1 -1
  207. package/dist/src/routes/search.d.ts +1 -1
  208. package/dist/src/routes/search.js +1 -1
  209. package/dist/src/routes/sources.d.ts +63 -6
  210. package/dist/src/routes/sources.d.ts.map +1 -1
  211. package/dist/src/routes/sources.js +494 -382
  212. package/dist/src/routes/sources.js.map +1 -1
  213. package/dist/src/routes/upload.d.ts +1 -1
  214. package/dist/src/routes/upload.js +1 -1
  215. package/dist/src/routes/write.d.ts +2 -2
  216. package/dist/src/routes/write.js +2 -2
  217. package/dist/src/services/BridgeService.d.ts +21 -35
  218. package/dist/src/services/BridgeService.d.ts.map +1 -1
  219. package/dist/src/services/BridgeService.js +42 -88
  220. package/dist/src/services/BridgeService.js.map +1 -1
  221. package/dist/src/services/FilesystemService.d.ts +4 -3
  222. package/dist/src/services/FilesystemService.d.ts.map +1 -1
  223. package/dist/src/services/FilesystemService.js +112 -34
  224. package/dist/src/services/FilesystemService.js.map +1 -1
  225. package/dist/src/services/FilesystemUiService.d.ts +4 -1
  226. package/dist/src/services/FilesystemUiService.d.ts.map +1 -1
  227. package/dist/src/services/FilesystemUiService.js +66 -25
  228. package/dist/src/services/FilesystemUiService.js.map +1 -1
  229. package/dist/src/services/SyncScheduler.d.ts +12 -4
  230. package/dist/src/services/SyncScheduler.d.ts.map +1 -1
  231. package/dist/src/services/SyncScheduler.js +37 -8
  232. package/dist/src/services/SyncScheduler.js.map +1 -1
  233. package/dist/src/services/baselineRepair.d.ts +13 -3
  234. package/dist/src/services/baselineRepair.d.ts.map +1 -1
  235. package/dist/src/services/baselineRepair.js +13 -3
  236. package/dist/src/services/baselineRepair.js.map +1 -1
  237. package/dist/src/services/declaredSources.d.ts +132 -43
  238. package/dist/src/services/declaredSources.d.ts.map +1 -1
  239. package/dist/src/services/declaredSources.js +118 -45
  240. package/dist/src/services/declaredSources.js.map +1 -1
  241. package/dist/src/store/dataFormats.d.ts +17 -2
  242. package/dist/src/store/dataFormats.d.ts.map +1 -1
  243. package/dist/src/store/dataFormats.js +18 -3
  244. package/dist/src/store/dataFormats.js.map +1 -1
  245. package/dist/src/sync/DeltaSyncRunner.d.ts.map +1 -1
  246. package/dist/src/sync/DeltaSyncRunner.js +144 -4
  247. package/dist/src/sync/DeltaSyncRunner.js.map +1 -1
  248. package/dist/src/sync/SyncJobQueue.d.ts +7 -2
  249. package/dist/src/sync/SyncJobQueue.d.ts.map +1 -1
  250. package/dist/src/sync/SyncJobQueue.js +16 -10
  251. package/dist/src/sync/SyncJobQueue.js.map +1 -1
  252. package/dist/src/sync/SyncWatchService.d.ts +21 -4
  253. package/dist/src/sync/SyncWatchService.d.ts.map +1 -1
  254. package/dist/src/sync/SyncWatchService.js +115 -10
  255. package/dist/src/sync/SyncWatchService.js.map +1 -1
  256. package/dist/src/sync/categoryInference.d.ts +1 -1
  257. package/dist/src/sync/categoryInference.js +1 -1
  258. package/dist/src/tools/execCtx.d.ts +2 -2
  259. package/dist/src/tools/execCtx.js +2 -2
  260. package/dist/src/tools/fs_read.d.ts.map +1 -1
  261. package/dist/src/tools/fs_read.js +5 -0
  262. package/dist/src/tools/fs_read.js.map +1 -1
  263. package/dist/src/usecases/admitSourceRoot.d.ts +65 -0
  264. package/dist/src/usecases/admitSourceRoot.d.ts.map +1 -0
  265. package/dist/src/usecases/admitSourceRoot.js +85 -0
  266. package/dist/src/usecases/admitSourceRoot.js.map +1 -0
  267. package/dist/src/usecases/browseSourceFolders.d.ts +85 -0
  268. package/dist/src/usecases/browseSourceFolders.d.ts.map +1 -0
  269. package/dist/src/usecases/browseSourceFolders.js +210 -0
  270. package/dist/src/usecases/browseSourceFolders.js.map +1 -0
  271. package/dist/src/usecases/createDeclaredSources.d.ts +13 -2
  272. package/dist/src/usecases/createDeclaredSources.d.ts.map +1 -1
  273. package/dist/src/usecases/createDeclaredSources.js +39 -0
  274. package/dist/src/usecases/createDeclaredSources.js.map +1 -1
  275. package/dist/src/usecases/embeddingBacklog.js +1 -1
  276. package/dist/src/usecases/embeddingBacklog.js.map +1 -1
  277. package/dist/src/usecases/list.d.ts.map +1 -1
  278. package/dist/src/usecases/list.js +83 -26
  279. package/dist/src/usecases/list.js.map +1 -1
  280. package/dist/src/usecases/listAttachableDevices.d.ts +13 -0
  281. package/dist/src/usecases/listAttachableDevices.d.ts.map +1 -0
  282. package/dist/src/usecases/listAttachableDevices.js +10 -0
  283. package/dist/src/usecases/listAttachableDevices.js.map +1 -0
  284. package/dist/src/usecases/listSourcePackages.d.ts.map +1 -1
  285. package/dist/src/usecases/listSourcePackages.js +2 -5
  286. package/dist/src/usecases/listSourcePackages.js.map +1 -1
  287. package/dist/src/usecases/purgeExcludedPoints.d.ts.map +1 -1
  288. package/dist/src/usecases/purgeExcludedPoints.js +3 -2
  289. package/dist/src/usecases/purgeExcludedPoints.js.map +1 -1
  290. package/dist/src/usecases/readFiles.d.ts.map +1 -1
  291. package/dist/src/usecases/readFiles.js +99 -1
  292. package/dist/src/usecases/readFiles.js.map +1 -1
  293. package/dist/src/usecases/reconcileSourcePackageRoots.d.ts +1 -1
  294. package/dist/src/usecases/reconcileSourcePackageRoots.d.ts.map +1 -1
  295. package/dist/src/usecases/reconcileSourcePackageRoots.js +2 -1
  296. package/dist/src/usecases/reconcileSourcePackageRoots.js.map +1 -1
  297. package/dist/src/usecases/revokePrincipal.d.ts +7 -5
  298. package/dist/src/usecases/revokePrincipal.d.ts.map +1 -1
  299. package/dist/src/usecases/revokePrincipal.js +29 -7
  300. package/dist/src/usecases/revokePrincipal.js.map +1 -1
  301. package/dist/src/usecases/scheduleSourceSync.d.ts +7 -0
  302. package/dist/src/usecases/scheduleSourceSync.d.ts.map +1 -1
  303. package/dist/src/usecases/scheduleSourceSync.js +6 -5
  304. package/dist/src/usecases/scheduleSourceSync.js.map +1 -1
  305. package/dist/src/usecases/syncPointWrites.d.ts +2 -0
  306. package/dist/src/usecases/syncPointWrites.d.ts.map +1 -1
  307. package/dist/src/usecases/syncPointWrites.js +5 -1
  308. package/dist/src/usecases/syncPointWrites.js.map +1 -1
  309. package/dist/src/usecases/triggerSourceSync.d.ts +46 -0
  310. package/dist/src/usecases/triggerSourceSync.d.ts.map +1 -0
  311. package/dist/src/usecases/triggerSourceSync.js +75 -0
  312. package/dist/src/usecases/triggerSourceSync.js.map +1 -0
  313. package/dist/src/usecases/uploadBinary.d.ts +2 -1
  314. package/dist/src/usecases/uploadBinary.d.ts.map +1 -1
  315. package/dist/src/usecases/uploadBinary.js +2 -2
  316. package/dist/src/usecases/uploadBinary.js.map +1 -1
  317. package/dist/src/usecases/vector/endpointGuard.d.ts +1 -1
  318. package/dist/src/usecases/vector/endpointGuard.js +1 -1
  319. package/dist/src/usecases/vectorMaintenance.js +1 -1
  320. package/dist/src/usecases/vectorMaintenance.js.map +1 -1
  321. package/dist/src/usecases/write/content.js +1 -1
  322. package/dist/src/vector/qdrantClient.d.ts +1 -1
  323. package/dist/src/vector/qdrantClient.js +1 -1
  324. package/dist/src/wiring.d.ts +3 -0
  325. package/dist/src/wiring.d.ts.map +1 -1
  326. package/dist/src/wiring.js +72 -6
  327. package/dist/src/wiring.js.map +1 -1
  328. package/dist/tools/fs_list.json +1 -1
  329. package/dist/tools/fs_read.json +1 -1
  330. package/dist/tools/fs_search.json +1 -1
  331. package/dist/tools/fs_write.json +1 -1
  332. package/instructions/brain-model.md +1 -1
  333. package/package.json +212 -114
  334. package/rules/brain-rules.md +2 -2
  335. package/skills/brain-start/SKILL.md +1 -1
  336. package/skills/connect-business-data/SKILL.md +3 -3
  337. package/skills/create-package/SKILL.md +26 -26
  338. package/skills/create-package/references/config-and-credentials.md +16 -13
  339. package/skills/create-package/references/connectors-and-uri-policies.md +39 -25
  340. package/skills/create-package/references/manifest-and-discovery.md +12 -12
  341. package/skills/create-package/references/runtime-and-build.md +19 -19
  342. package/skills/create-package/references/skills-and-context.md +36 -20
  343. package/skills/create-package/references/surfaces-and-design.md +16 -16
  344. package/skills/create-package/references/team-and-workflows.md +2 -2
  345. package/skills/create-package/references/test-and-ship.md +21 -21
  346. package/skills/create-package/references/tools-and-schemas.md +5 -5
  347. package/skills/create-package/scripts/{check-package.sh → check-package.mjs} +79 -65
  348. package/skills/create-package/scripts/{verify-loaded.sh → verify-loaded.mjs} +156 -162
  349. package/skills/git/SKILL.md +53 -51
  350. package/skills/manage-files/SKILL.md +22 -25
  351. package/skills/manage-sources/SKILL.md +128 -87
  352. package/skills/manage-uri-policy/SKILL.md +18 -15
  353. package/skills/organize/SKILL.md +1 -1
  354. package/skills/sync/SKILL.md +3 -3
  355. package/tools/fs_list.json +1 -1
  356. package/tools/fs_read.json +1 -1
  357. package/tools/fs_search.json +1 -1
  358. package/tools/fs_write.json +1 -1
  359. package/workflows/sources-digest.json +1 -1
  360. package/dist/src/connectors/LocalDiskConnector.d.ts +0 -171
  361. package/dist/src/connectors/LocalDiskConnector.d.ts.map +0 -1
  362. package/dist/src/connectors/LocalDiskConnector.js +0 -1233
  363. package/dist/src/connectors/LocalDiskConnector.js.map +0 -1
  364. package/docs/drive-overview.md +0 -59
  365. package/skills/manage-files/scripts/copy.sh +0 -7
  366. package/skills/manage-files/scripts/create.sh +0 -7
  367. package/skills/manage-files/scripts/mkdir.sh +0 -7
  368. package/skills/manage-files/scripts/modify.sh +0 -7
  369. package/skills/manage-files/scripts/upload.sh +0 -16
  370. package/skills/manage-files/scripts/write.sh +0 -29
  371. package/skills/manage-sources/scripts/reembed.sh +0 -23
@@ -1,4 +1,5 @@
1
1
  import type { FileNode, FilePrev } from '../domain/types';
2
+ import { deviceRefusalLabel, sourceLiveReasonLabel } from '../lib/treeVisualState';
2
3
 
3
4
  // ---------------------------------------------------------------------------
4
5
  // URI helpers (lightweight, client-side — no dependency on brain-core/src/)
@@ -213,3 +214,19 @@ export function contextKey(
213
214
  ): string {
214
215
  return `${userId ?? ''}::${projectId ?? ''}::${agentId}`;
215
216
  }
217
+
218
+ /**
219
+ * One file's read failure as the Files UI says it: a device folder that could
220
+ * not serve names its state in the tree's own words (`source_unavailable` →
221
+ * the live-reason label, `device_refused` → the refusal code); any other
222
+ * failure keeps the server's text. `undefined` when the file read.
223
+ */
224
+ export function readFailureMessage(file: { error?: unknown; code?: unknown; reason?: unknown; not_ready?: unknown }): string | undefined {
225
+ if (typeof file.error !== 'string') return undefined;
226
+ // `source_unavailable` is a device folder's answer only (the `host` connector's device transport).
227
+ if (file.code === 'source_unavailable' && typeof file.reason === 'string') {
228
+ return sourceLiveReasonLabel(file.reason, { kind: 'host', notReady: Array.isArray(file.not_ready) ? file.not_ready : null });
229
+ }
230
+ if (file.code === 'device_refused') return deviceRefusalLabel(file.reason) ?? file.error;
231
+ return file.error;
232
+ }
@@ -436,9 +436,17 @@ export function createListSlice(
436
436
  }
437
437
  }
438
438
 
439
+ // An expanded folder of a source this listing no longer names (deleted, or no
440
+ // longer visible) would be listed on every load and refused: one pass drops it,
441
+ // and the window's persisted projection follows.
442
+ const listedSources = new Set(sources.map((s) => sourceFromUri(s.uri_root)));
443
+ const expanded = state.expandedFolders as Set<string>;
444
+ const keptExpanded = sources.length > 0 ? [...expanded].filter((uri) => listedSources.has(sourceFromUri(uri))) : [];
445
+
439
446
  return {
440
447
  files: next,
441
448
  sources,
449
+ ...(sources.length > 0 && keptExpanded.length !== expanded.size ? { expandedFolders: new Set(keptExpanded) } : {}),
442
450
  sourcesLoading: false,
443
451
  // A listing that answered clears the previous failure's banner.
444
452
  snapshotError: undefined,
@@ -13,7 +13,7 @@
13
13
  * diff surfaces exactly, so the editor ↔ diff transition never jumps.
14
14
  */
15
15
  import { useEffect, useRef } from 'react';
16
- import { EditorState, Compartment } from '@codemirror/state';
16
+ import { EditorState, Compartment, type Extension } from '@codemirror/state';
17
17
  import {
18
18
  EditorView,
19
19
  lineNumbers,
@@ -67,6 +67,7 @@ export function CodeEditor({
67
67
  const readOnlyComp = useRef(new Compartment());
68
68
  const wrapComp = useRef(new Compartment());
69
69
  const gitGutterComp = useRef(new Compartment());
70
+ const labelComp = useRef(new Compartment());
70
71
 
71
72
  onChangeRef.current = onChange;
72
73
 
@@ -93,13 +94,12 @@ export function CodeEditor({
93
94
  ]),
94
95
  wrapComp.current.of(wrap ? EditorView.lineWrapping : []),
95
96
  gitGutterComp.current.of([]),
96
- placeholder ? cmPlaceholder(placeholder) : [],
97
+ labelComp.current.of(labelExtensions(placeholder, ariaLabel)),
97
98
  EditorView.updateListener.of((u) => {
98
99
  if (u.docChanged && !applyingExternal.current) {
99
100
  onChangeRef.current?.(u.state.doc.toString());
100
101
  }
101
102
  }),
102
- EditorView.contentAttributes.of(ariaLabel ? { 'aria-label': ariaLabel } : {}),
103
103
  ],
104
104
  });
105
105
 
@@ -155,6 +155,14 @@ export function CodeEditor({
155
155
  });
156
156
  }, [wrap]);
157
157
 
158
+ // Empty-state line + accessible name reconfigure — the view outlives the file
159
+ // it shows, so a read that FAILS after mount replaces "Loading…" here.
160
+ useEffect(() => {
161
+ const view = viewRef.current;
162
+ if (!view) return;
163
+ view.dispatch({ effects: labelComp.current.reconfigure(labelExtensions(placeholder, ariaLabel)) });
164
+ }, [placeholder, ariaLabel]);
165
+
158
166
  // Git left-gutter reconfigure — present only when there's a non-empty status.
159
167
  useEffect(() => {
160
168
  const view = viewRef.current;
@@ -165,3 +173,10 @@ export function CodeEditor({
165
173
 
166
174
  return <div ref={hostRef} className={className} />;
167
175
  }
176
+
177
+ function labelExtensions(placeholder: string | undefined, ariaLabel: string | undefined): Extension[] {
178
+ return [
179
+ placeholder ? cmPlaceholder(placeholder) : [],
180
+ EditorView.contentAttributes.of(ariaLabel ? { 'aria-label': ariaLabel } : {}),
181
+ ];
182
+ }
@@ -56,9 +56,13 @@ export function DiscoverDock({ suggestions, expanded, onToggleExpanded, onPick,
56
56
  return (
57
57
  <div
58
58
  className={cn(
59
- 'group/discover shrink-0 border-t border-white/[0.06] bg-[#0b0b0f]/95',
59
+ 'group/discover shrink-0 border-t border-white/[0.06]',
60
60
  overlay ? 'absolute inset-x-0 bottom-0 z-20 shadow-[0_-8px_24px_rgba(0,0,0,0.45)]' : '',
61
61
  )}
62
+ // Collapsed it carries no surface of its own (the panel chrome owns it);
63
+ // only the overlay form needs a scrim, derived from the theme so it
64
+ // matches every workspace theme.
65
+ style={overlay ? { backgroundColor: 'rgba(var(--w-bg-rgb), 0.96)' } : undefined}
62
66
  data-testid="discover-dock"
63
67
  >
64
68
  <div className="flex items-center justify-between px-2 pt-1.5 pb-1">
@@ -18,6 +18,7 @@ import { GitDiffView } from './GitDiffView';
18
18
  import { GitCommitChangesView } from './GitCommitChangesView';
19
19
  import { gitDiffTargetModeLabel, repoQualifiedLabel, shortSha } from './gitFileDiffMode';
20
20
  import { getRawFileUrl, approveFileReview, writeFile, writeRefusalMessage, getGitDiff } from '../api';
21
+ import { RawMediaPreview } from './RawMediaPreview';
21
22
  import { InlineUploader } from './InlineUploader';
22
23
  import { SourcesPanel } from './SourcesPanel';
23
24
  import { PackagesPanel } from './PackagesPanel';
@@ -172,6 +173,9 @@ export function FileEditor() {
172
173
  if (!activeNode?.uri || !rawAgentId) return null;
173
174
  return getRawFileUrl(rawAgentId, activeNode.uri, projectId ?? undefined);
174
175
  }, [activeNode?.uri, rawAgentId, projectId]);
176
+ // An image or video renders from the byte lane, which says itself why it
177
+ // cannot (`RawMediaPreview`): the text read's failure is not this file's.
178
+ const mediaPreview = (isImage || isVideo) && !!rawUrl && !editing;
175
179
  const previousActiveKeyRef = useRef<string | null>(null);
176
180
  const paneRef = useRef<HTMLDivElement | null>(null);
177
181
  const isWidePane = useContainerWidth(paneRef);
@@ -404,12 +408,12 @@ export function FileEditor() {
404
408
  }, [hasMetaDetails]);
405
409
 
406
410
  const handleStartEdit = useCallback(() => {
407
- if (!activeNode || isDeleted) return;
411
+ if (!activeNode || isDeleted || errorMessage) return;
408
412
  setDraftUri(activeNode.uri);
409
413
  setDraftContent(activeContent);
410
414
  setEditError(null);
411
415
  setEditing(true);
412
- }, [activeNode, activeContent, isDeleted]);
416
+ }, [activeNode, activeContent, isDeleted, errorMessage]);
413
417
 
414
418
  const handleCancelEdit = useCallback(() => {
415
419
  if (!activeNode) return;
@@ -703,7 +707,7 @@ export function FileEditor() {
703
707
  <span className="truncate font-mono text-white/42">{displayFolderPath}</span>
704
708
  </div>
705
709
  )}
706
- {errorMessage ? (
710
+ {errorMessage && !mediaPreview ? (
707
711
  <div className="text-[10px] text-red-300/80 truncate" title={errorMessage}>
708
712
  Read failed
709
713
  </div>
@@ -741,8 +745,8 @@ export function FileEditor() {
741
745
  type="button"
742
746
  className="inline-flex items-center justify-center rounded-md bg-white/5 p-1.5 text-white/60 hover:text-white/80 hover:bg-white/10 transition disabled:opacity-40 disabled:cursor-not-allowed"
743
747
  onClick={handleStartEdit}
744
- disabled={!targetAgentId || isDeleted || isBinary}
745
- title={isBinary ? 'Binary files cannot be edited in-place' : 'Edit in-place'}
748
+ disabled={!targetAgentId || isDeleted || isBinary || !!errorMessage}
749
+ title={isBinary ? 'Binary files cannot be edited in-place' : errorMessage ? 'The file could not be read — reload it to edit' : 'Edit in-place'}
746
750
  aria-label="Edit in-place"
747
751
  >
748
752
  <Pencil className="w-3 h-3" />
@@ -916,7 +920,7 @@ export function FileEditor() {
916
920
  ) : null}
917
921
  {showPackagesTab && projectId ? (
918
922
  <div className="w-full h-full overflow-y-auto">
919
- <PackagesPanel projectId={projectId} />
923
+ <PackagesPanel projectId={projectId} agentId={agentId ?? undefined} />
920
924
  </div>
921
925
  ) : null}
922
926
  {showPendingTab && agentId ? (
@@ -984,8 +988,8 @@ export function FileEditor() {
984
988
  <div className="w-full h-full min-h-0 overflow-auto px-2 py-1.5">
985
989
  <div className={cn(isDeleted && 'blur-[2px] opacity-65')}>
986
990
  <div className="text-[11px] text-white/45 mb-3">Image preview</div>
987
- {/* eslint-disable-next-line @next/next/no-img-element */}
988
- <img
991
+ <RawMediaPreview
992
+ kind="image"
989
993
  src={rawUrl}
990
994
  alt={activeNode.name}
991
995
  className="max-w-full max-h-[calc(100vh-220px)] rounded-xl bg-black/20 shadow-[0_18px_46px_-26px_rgba(0,0,0,0.65),inset_0_1px_0_rgba(255,255,255,0.06)]"
@@ -996,9 +1000,10 @@ export function FileEditor() {
996
1000
  <div className="w-full h-full min-h-0 overflow-auto px-2 py-1.5">
997
1001
  <div className={cn(isDeleted && 'blur-[2px] opacity-65')}>
998
1002
  <div className="text-[11px] text-white/45 mb-3">Video preview</div>
999
- <video
1003
+ <RawMediaPreview
1004
+ kind="video"
1000
1005
  src={rawUrl}
1001
- controls
1006
+ alt={activeNode.name}
1002
1007
  className="w-full max-w-[1100px] rounded-xl bg-black/20 shadow-[0_18px_46px_-26px_rgba(0,0,0,0.65),inset_0_1px_0_rgba(255,255,255,0.06)]"
1003
1008
  />
1004
1009
  </div>
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useFilesystemStore, useFilesystemStoreApi, type SidebarView } from '../store/filesystemStore';
4
- import React, { useMemo, useState, useCallback, useRef, useEffect } from 'react';
4
+ import React, { useMemo, useState, useCallback, useRef, useEffect, useLayoutEffect } from 'react';
5
5
  import { SkillLauncher } from '@neuralis/package-system/client';
6
6
  import { meetsRequires } from '@neuralis/package-system/access';
7
7
  import { useVirtualizer } from '@tanstack/react-virtual';
@@ -17,6 +17,7 @@ import { FileTreeContextMenu, type TreeMenuAction } from './FileTreeContextMenu'
17
17
  import { TreeDeleteConfirm } from './TreeDeleteConfirm';
18
18
  import { ReembedDialog, type ReembedTarget } from './ReembedDialog';
19
19
  import { SourceBranchHeader } from './SourceBranchHeader';
20
+ import { sourceUnavailableLabel } from '../lib/treeVisualState';
20
21
  import { FileTreeSearch } from './FileTreeSearch';
21
22
  import { GitControlPanel } from './GitControlPanel';
22
23
  import { fileKeyFromNode, folderUri, sourceFromUri, sourceRootUri, uriAncestors } from '../store/helpers';
@@ -109,7 +110,7 @@ export function isMeasuredRow(kind: FlatItemKind): boolean {
109
110
  type FlatItem =
110
111
  | { kind: 'header'; key: string; info: SourceInfo; expanded: boolean }
111
112
  | { kind: 'node'; key: string; node: FileNode; depth: number }
112
- | { kind: 'empty'; key: string; sourceRoot: string }
113
+ | { kind: 'empty'; key: string; sourceRoot: string; unavailable?: string }
113
114
  | { kind: 'creating'; key: string; parentUri: string; createKind: 'file' | 'folder'; depth: number };
114
115
 
115
116
  function sortNodes(a: FileNode, b: FileNode): number {
@@ -332,7 +333,10 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
332
333
  if (!branchExpanded) continue;
333
334
  const visible = flattenSource(treeIndex, info.uri_root, expanded);
334
335
  if (visible.length === 0) {
335
- items.push({ kind: 'empty', key: `e:${info.uri_root}`, sourceRoot: info.uri_root });
336
+ // A source that cannot be reached is not an empty one: it says why, and
337
+ // offers nothing to populate.
338
+ const unavailable = sourceUnavailableLabel(info);
339
+ items.push({ kind: 'empty', key: `e:${info.uri_root}`, sourceRoot: info.uri_root, ...(unavailable ? { unavailable } : {}) });
336
340
  } else {
337
341
  for (const entry of visible) {
338
342
  items.push({ kind: 'node', key: `n:${entry.node.uri}`, node: entry.node, depth: entry.depth });
@@ -439,9 +443,9 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
439
443
  return uris.map((u) => files[u]).filter(Boolean) as FileNode[];
440
444
  }, [contextMenu, selection, files]);
441
445
 
442
- // HIDE-ONLY mirror of the reindex route's `drive.sync` gate — the route decides.
446
+ // HIDE-ONLY mirror of the reindex route's `sources.sync` gate — the route decides.
443
447
  const canSync = useMemo(
444
- () => meetsRequires({ grantedFeatures: [...(host.grantedFeatures ?? [])] }, { features: ['drive.sync'] }),
448
+ () => meetsRequires({ grantedFeatures: [...(host.grantedFeatures ?? [])] }, { features: ['sources.sync'] }),
445
449
  [host.grantedFeatures],
446
450
  );
447
451
  const [reembedTarget, setReembedTarget] = useState<ReembedTarget | null>(null);
@@ -700,7 +704,7 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
700
704
  () => sourceInfos.every((info) => !sourceBranchExpanded[info.uri_root]),
701
705
  [sourceInfos, sourceBranchExpanded],
702
706
  );
703
- // HIDE-ONLY feature gate. The server list is already `drive.read`-gated and
707
+ // HIDE-ONLY feature gate. The server list is already `files.read`-gated and
704
708
  // `seeRequires`-filtered, so this hides an affordance the caller could not use
705
709
  // and authorizes NOTHING — the attach route stays the boundary. Hidden
706
710
  // entirely, never a locked row (the S2 discipline). Evaluated with the ONE
@@ -709,7 +713,7 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
709
713
  // The host value keeps the list `readonly` so no UI file can mutate the
710
714
  // caller's grants; the kernel predicate takes a mutable array, so copy at
711
715
  // the boundary rather than widening the contract.
712
- () => meetsRequires({ grantedFeatures: [...(host.grantedFeatures ?? [])] }, { features: ['drive.mount'] }),
716
+ () => meetsRequires({ grantedFeatures: [...(host.grantedFeatures ?? [])] }, { features: ['sources.mount'] }),
713
717
  [host.grantedFeatures],
714
718
  );
715
719
  // FETCH: lazily, only once the dock could actually render. An eager fetch on
@@ -779,6 +783,32 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
779
783
  getItemKey,
780
784
  });
781
785
 
786
+ // The scroller UNMOUNTS whenever the git panel or a loading/empty state replaces
787
+ // the tree, but the virtualizer instance survives with its old scroll offset. On
788
+ // re-attach virtual-core writes that offset onto the NEW element; when the new
789
+ // content is still too short the browser clamps `scrollTop` to 0 without firing
790
+ // a scroll event, and the virtualizer keeps windowing from the stale offset — the
791
+ // first rows stay unrendered above a blank gap until the user scrolls. One read
792
+ // per attach (declared after `useVirtualizer`, so it runs after its `_willUpdate`)
793
+ // re-feeds the real offset through the virtualizer's own scroll listener — in a
794
+ // microtask, because that listener re-renders with `flushSync`, which React
795
+ // refuses inside a commit; the microtask still lands before the frame paints.
796
+ const scrollerAttachedRef = useRef(false);
797
+ const attachScroller = useCallback((el: HTMLDivElement | null) => {
798
+ parentRef.current = el;
799
+ if (el) scrollerAttachedRef.current = true;
800
+ }, []);
801
+ useLayoutEffect(() => {
802
+ if (!scrollerAttachedRef.current) return;
803
+ scrollerAttachedRef.current = false;
804
+ const el = parentRef.current;
805
+ if (el && el.scrollTop !== (virtualizer.scrollOffset ?? 0)) {
806
+ queueMicrotask(() => {
807
+ if (parentRef.current === el) el.dispatchEvent(new Event('scroll'));
808
+ });
809
+ }
810
+ });
811
+
782
812
  // Git Control view (G2.1, Q1) — the panel REPLACES the tree + search IN PLACE,
783
813
  // exactly like search does. Early-return AFTER all hooks (so none are skipped)
784
814
  // and BEFORE the loading/empty tree short-circuits (the git panel owns its own
@@ -806,7 +836,7 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
806
836
 
807
837
  // Root-listing failure (the SERVICE answered nothing — a single source's
808
838
  // failure is its own header's `error` state). Without this surface the tree
809
- // was silently "Empty Drive": the store wrote `snapshotError` and nothing read
839
+ // was silently "No files yet": the store wrote `snapshotError` and nothing read
810
840
  // it. Retry re-runs the same root listing; a listing that answers clears it.
811
841
  const snapshotBanner = snapshotError && !showSearch ? (
812
842
  <div
@@ -835,7 +865,7 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
835
865
  ) : null;
836
866
 
837
867
  if (!allNodes.length && !showSearch && !hasSources) {
838
- return snapshotBanner ?? <div className="p-4 text-center text-xs text-white/30">Empty Drive</div>;
868
+ return snapshotBanner ?? <div className="p-4 text-center text-xs text-white/30">No files yet</div>;
839
869
  }
840
870
 
841
871
  const renderNode = (node: FileNode, depth: number) => {
@@ -881,7 +911,7 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
881
911
  // `relative` anchors the dock's EXPANDED overlay form.
882
912
  <div className="relative flex min-h-0 flex-1 flex-col">
883
913
  <div
884
- ref={parentRef}
914
+ ref={attachScroller}
885
915
  tabIndex={0}
886
916
  onKeyDown={handleTreeKeyDown}
887
917
  onContextMenu={handleBackgroundContextMenu}
@@ -946,21 +976,11 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
946
976
  compact
947
977
  />
948
978
  ) : item.kind === 'empty' ? (
949
- <div
950
- className="flex flex-col items-start gap-1.5 px-4 py-2"
979
+ <SourceEmptyRow
980
+ sourceRoot={item.sourceRoot}
981
+ unavailable={item.unavailable}
951
982
  onContextMenu={(e) => handleSourceContextMenu(item.sourceRoot, e)}
952
- >
953
- <span className="text-xs text-white/35">
954
- No files for {sourceFromUri(item.sourceRoot)}
955
- </span>
956
- <SkillLauncher
957
- variant="label"
958
- skill="manage-files"
959
- packageId="@neuralis/brain-core"
960
- scripts={["scripts/upload.sh", "scripts/modify.sh"]}
961
- label="Populate this source with the agent"
962
- />
963
- </div>
983
+ />
964
984
  ) : item.kind === 'creating' ? (
965
985
  <CreateRow
966
986
  kind={item.createKind}
@@ -1026,6 +1046,37 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
1026
1046
  );
1027
1047
  }
1028
1048
 
1049
+ /**
1050
+ * The placeholder under an expanded source with no rows: "No files" and the
1051
+ * populate launcher — or, for a source that cannot be reached, its live state
1052
+ * and nothing to populate (an unreachable folder is not an empty one).
1053
+ */
1054
+ export function SourceEmptyRow(props: {
1055
+ sourceRoot: string;
1056
+ unavailable?: string;
1057
+ onContextMenu?: (e: React.MouseEvent) => void;
1058
+ }) {
1059
+ return (
1060
+ <div className="flex flex-col items-start gap-1.5 px-4 py-2" onContextMenu={props.onContextMenu}>
1061
+ {props.unavailable ? (
1062
+ <span role="status" className="text-xs text-white/45">{props.unavailable}</span>
1063
+ ) : (
1064
+ <>
1065
+ <span className="text-xs text-white/35">
1066
+ No files for {sourceFromUri(props.sourceRoot)}
1067
+ </span>
1068
+ <SkillLauncher
1069
+ variant="label"
1070
+ skill="manage-files"
1071
+ packageId="@neuralis/brain-core"
1072
+ label="Populate this source with the agent"
1073
+ />
1074
+ </>
1075
+ )}
1076
+ </div>
1077
+ );
1078
+ }
1079
+
1029
1080
  /**
1030
1081
  * Inline "New File / New Folder" input row spliced into the tree at the create
1031
1082
  * position. Enter commits, Escape / empty-blur cancels — the `doneRef` guard
@@ -28,6 +28,7 @@ export function FilesystemWidget(props: { agentId: string; projectId?: string |
28
28
  const setSidebarView = useFilesystemStore((s) => s.setSidebarView);
29
29
  const lastAppliedNavRef = useRef<string | null>(null);
30
30
  const splitRef = useRef<HTMLDivElement | null>(null);
31
+ const sidebarRef = useRef<HTMLDivElement | null>(null);
31
32
  const dragRef = useRef<{ startX: number; startWidth: number } | null>(null);
32
33
  const [dragging, setDragging] = useState(false);
33
34
 
@@ -103,11 +104,16 @@ export function FilesystemWidget(props: { agentId: string; projectId?: string |
103
104
  // the move keeps tracking even when the cursor leaves the thin handle. The
104
105
  // upper bound is the live container width (minus the handle), so the tree can
105
106
  // be dragged all the way over the editor, leaving only tree + chat visible.
107
+ // The RENDERED width is capped by CSS at the same bound: a narrowing widget
108
+ // eats the editor first, then the tree, and widening grows the tree back to
109
+ // the stored width — so the drag starts from the rendered width, never the
110
+ // stored one, or a capped tree would not follow the pointer at first.
106
111
  const HANDLE_WIDTH = 8;
107
112
  const onHandlePointerDown = useCallback(
108
113
  (e: React.PointerEvent<HTMLDivElement>) => {
109
114
  e.preventDefault();
110
- dragRef.current = { startX: e.clientX, startWidth: sidebarWidth };
115
+ const rendered = sidebarRef.current?.offsetWidth;
116
+ dragRef.current = { startX: e.clientX, startWidth: rendered && rendered < sidebarWidth ? rendered : sidebarWidth };
111
117
  setDragging(true);
112
118
  e.currentTarget.setPointerCapture(e.pointerId);
113
119
  },
@@ -150,9 +156,11 @@ export function FilesystemWidget(props: { agentId: string; projectId?: string |
150
156
  className={`flex-1 min-h-0 flex flex-row ${dragging ? 'select-none cursor-col-resize' : ''}`}
151
157
  >
152
158
  <div
159
+ ref={sidebarRef}
153
160
  className="h-full flex flex-col min-h-0 shrink-0 overflow-hidden"
154
161
  style={{
155
162
  width: sidebarOpen ? sidebarWidth : 0,
163
+ maxWidth: `calc(100% - ${HANDLE_WIDTH}px)`,
156
164
  backgroundColor: 'transparent',
157
165
  borderRight: sidebarOpen ? '1px solid rgba(255,255,255,0.05)' : 'none',
158
166
  }}
@@ -238,7 +246,7 @@ export function FilesystemWidget(props: { agentId: string; projectId?: string |
238
246
  </div>
239
247
  )}
240
248
 
241
- <div className="flex-1 min-w-0 min-h-0">
249
+ <div className="flex-1 min-w-0 min-h-0 overflow-hidden">
242
250
  <FileEditor />
243
251
  </div>
244
252
  </div>