@neuralis/brain-core 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -1410
- package/app/README.md +1 -1
- package/app/cards/FilesystemCardShell.tsx +42 -4
- package/app/cards/FilesystemChangeCard.tsx +34 -11
- package/app/cards/README.md +6 -5
- package/app/cards/diffUtils.ts +99 -1
- package/app/filesystem/FilesystemHost.tsx +17 -2
- package/app/filesystem/api/README.md +3 -3
- package/app/filesystem/api/index.ts +35 -11
- package/app/filesystem/api/projectPackages.ts +13 -102
- package/app/filesystem/domain/types.ts +30 -10
- package/app/filesystem/hooks/README.md +3 -3
- package/app/filesystem/hooks/useDiscoverableSources.ts +19 -30
- package/app/filesystem/hooks/useFilesystemSSE.ts +1 -1
- package/app/filesystem/host/README.md +5 -3
- package/app/filesystem/host/buildFilesystemHostValue.ts +19 -0
- package/app/filesystem/host/installFilesystemHostComponents.tsx +1 -1
- package/app/filesystem/lib/sourceKindForm.ts +118 -39
- package/app/filesystem/lib/treeVisualState.tsx +98 -4
- package/app/filesystem/store/README.md +1 -0
- package/app/filesystem/store/filesystemStore.ts +86 -5
- package/app/filesystem/store/gitSlice.ts +5 -0
- package/app/filesystem/store/helpers.ts +17 -0
- package/app/filesystem/store/listSlice.ts +8 -0
- package/app/filesystem/ui/CodeEditor.tsx +18 -3
- package/app/filesystem/ui/FileEditor.tsx +15 -10
- package/app/filesystem/ui/FileTree.tsx +41 -16
- package/app/filesystem/ui/FolderPicker.tsx +251 -0
- package/app/filesystem/ui/GitControlPanel.tsx +5 -0
- package/app/filesystem/ui/PackagesPanel.tsx +52 -374
- package/app/filesystem/ui/PolicyEditor.tsx +109 -24
- package/app/filesystem/ui/README.md +17 -11
- package/app/filesystem/ui/RawMediaPreview.tsx +58 -0
- package/app/filesystem/ui/SourceBranchHeader.tsx +4 -4
- package/app/filesystem/ui/SourcesPanel.tsx +420 -360
- package/app/filesystem/ui/Toolbar.tsx +1 -2
- package/app/shared/filesystem.png +0 -0
- package/dist/app/host.js +53 -48
- package/dist/scripts/sourceRootPairV1.d.ts +47 -0
- package/dist/scripts/sourceRootPairV1.d.ts.map +1 -0
- package/dist/scripts/sourceRootPairV1.js +147 -0
- package/dist/scripts/sourceRootPairV1.js.map +1 -0
- package/dist/scripts/sourceToFolderV1.d.ts +56 -0
- package/dist/scripts/sourceToFolderV1.d.ts.map +1 -0
- package/dist/scripts/sourceToFolderV1.js +152 -0
- package/dist/scripts/sourceToFolderV1.js.map +1 -0
- package/dist/scripts/sourceToHostV1.d.ts +56 -0
- package/dist/scripts/sourceToHostV1.d.ts.map +1 -0
- package/dist/scripts/sourceToHostV1.js +132 -0
- package/dist/scripts/sourceToHostV1.js.map +1 -0
- package/dist/src/config/env.d.ts +8 -0
- package/dist/src/config/env.d.ts.map +1 -1
- package/dist/src/config/env.js +17 -6
- package/dist/src/config/env.js.map +1 -1
- package/dist/src/connectors/AppSourceConfigStore.d.ts +17 -3
- package/dist/src/connectors/AppSourceConfigStore.d.ts.map +1 -1
- package/dist/src/connectors/AppSourceConfigStore.js +46 -6
- package/dist/src/connectors/AppSourceConfigStore.js.map +1 -1
- package/dist/src/connectors/HostConnector.d.ts +201 -0
- package/dist/src/connectors/HostConnector.d.ts.map +1 -0
- package/dist/src/connectors/HostConnector.js +903 -0
- package/dist/src/connectors/HostConnector.js.map +1 -0
- package/dist/src/connectors/inProcessTransport.d.ts +131 -0
- package/dist/src/connectors/inProcessTransport.d.ts.map +1 -0
- package/dist/src/connectors/inProcessTransport.js +447 -0
- package/dist/src/connectors/inProcessTransport.js.map +1 -0
- package/dist/src/domain/BrainError.d.ts +14 -0
- package/dist/src/domain/BrainError.d.ts.map +1 -1
- package/dist/src/domain/BrainError.js +21 -0
- package/dist/src/domain/BrainError.js.map +1 -1
- package/dist/src/domain/access/callerPermissions.d.ts +29 -0
- package/dist/src/domain/access/callerPermissions.d.ts.map +1 -1
- package/dist/src/domain/access/callerPermissions.js +85 -5
- package/dist/src/domain/access/callerPermissions.js.map +1 -1
- package/dist/src/domain/access/nativeLocalFence.d.ts +65 -0
- package/dist/src/domain/access/nativeLocalFence.d.ts.map +1 -0
- package/dist/src/domain/access/nativeLocalFence.js +171 -0
- package/dist/src/domain/access/nativeLocalFence.js.map +1 -0
- package/dist/src/domain/access/uriPermission.js +3 -3
- package/dist/src/domain/access/uriPermission.js.map +1 -1
- package/dist/src/domain/byteRange.d.ts +15 -0
- package/dist/src/domain/byteRange.d.ts.map +1 -0
- package/dist/src/domain/byteRange.js +30 -0
- package/dist/src/domain/byteRange.js.map +1 -0
- package/dist/src/domain/defaultPermissions.d.ts.map +1 -1
- package/dist/src/domain/defaultPermissions.js +29 -0
- package/dist/src/domain/defaultPermissions.js.map +1 -1
- package/dist/src/domain/mime.d.ts +8 -16
- package/dist/src/domain/mime.d.ts.map +1 -1
- package/dist/src/domain/mime.js +10 -99
- package/dist/src/domain/mime.js.map +1 -1
- package/dist/src/domain/paths.d.ts.map +1 -1
- package/dist/src/domain/paths.js +5 -3
- package/dist/src/domain/paths.js.map +1 -1
- package/dist/src/domain/sourceConfig.d.ts +52 -19
- package/dist/src/domain/sourceConfig.d.ts.map +1 -1
- package/dist/src/domain/sourceConfig.js +103 -19
- package/dist/src/domain/sourceConfig.js.map +1 -1
- package/dist/src/domain/sourcePackages.d.ts +8 -8
- package/dist/src/domain/sourcePackages.d.ts.map +1 -1
- package/dist/src/domain/sourceRootPair.d.ts +88 -0
- package/dist/src/domain/sourceRootPair.d.ts.map +1 -0
- package/dist/src/domain/sourceRootPair.js +108 -0
- package/dist/src/domain/sourceRootPair.js.map +1 -0
- package/dist/src/domain/syncTypes.d.ts +8 -1
- package/dist/src/domain/syncTypes.d.ts.map +1 -1
- package/dist/src/domain/syncTypes.js +1 -0
- package/dist/src/domain/syncTypes.js.map +1 -1
- package/dist/src/domain/types.d.ts +29 -1
- package/dist/src/domain/types.d.ts.map +1 -1
- package/dist/src/domain/uri.d.ts.map +1 -1
- package/dist/src/domain/uri.js +3 -2
- package/dist/src/domain/uri.js.map +1 -1
- package/dist/src/embeddings/clients/GeminiEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/GeminiEmbedder.js +2 -5
- package/dist/src/embeddings/clients/GeminiEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/OpenAIEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/OpenAIEmbedder.js +2 -3
- package/dist/src/embeddings/clients/OpenAIEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/QwenEmbedder.d.ts +2 -0
- package/dist/src/embeddings/clients/QwenEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/QwenEmbedder.js +10 -2
- package/dist/src/embeddings/clients/QwenEmbedder.js.map +1 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.d.ts +2 -0
- package/dist/src/embeddings/clients/VoyageEmbedder.d.ts.map +1 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.js +8 -1
- package/dist/src/embeddings/clients/VoyageEmbedder.js.map +1 -1
- package/dist/src/embeddings/factory.d.ts +5 -3
- package/dist/src/embeddings/factory.d.ts.map +1 -1
- package/dist/src/embeddings/factory.js +7 -3
- package/dist/src/embeddings/factory.js.map +1 -1
- package/dist/src/embeddings/models.d.ts +2 -3
- package/dist/src/embeddings/models.d.ts.map +1 -1
- package/dist/src/embeddings/models.js +1 -2
- package/dist/src/embeddings/models.js.map +1 -1
- package/dist/src/embeddings/retry.js +1 -1
- package/dist/src/embeddings/retry.js.map +1 -1
- package/dist/src/embeddings/types.d.ts +6 -0
- package/dist/src/embeddings/types.d.ts.map +1 -1
- package/dist/src/embeddings/types.js +11 -0
- package/dist/src/embeddings/types.js.map +1 -1
- package/dist/src/git/GitRunner.d.ts +110 -30
- package/dist/src/git/GitRunner.d.ts.map +1 -1
- package/dist/src/git/GitRunner.js +511 -199
- package/dist/src/git/GitRunner.js.map +1 -1
- package/dist/src/git/GitWatchRegistry.d.ts +41 -35
- package/dist/src/git/GitWatchRegistry.d.ts.map +1 -1
- package/dist/src/git/GitWatchRegistry.js +61 -79
- package/dist/src/git/GitWatchRegistry.js.map +1 -1
- package/dist/src/git/gitPlace.d.ts +61 -0
- package/dist/src/git/gitPlace.d.ts.map +1 -0
- package/dist/src/git/gitPlace.js +74 -0
- package/dist/src/git/gitPlace.js.map +1 -0
- package/dist/src/git/sourceGitControl.d.ts +31 -19
- package/dist/src/git/sourceGitControl.d.ts.map +1 -1
- package/dist/src/git/sourceGitControl.js +208 -92
- package/dist/src/git/sourceGitControl.js.map +1 -1
- package/dist/src/git/sourceGitStatus.js +1 -1
- package/dist/src/git/sourceGitStatus.js.map +1 -1
- package/dist/src/git/untrackedStats.js +1 -1
- package/dist/src/git/untrackedStats.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lifecycle.d.ts +12 -16
- package/dist/src/lifecycle.d.ts.map +1 -1
- package/dist/src/lifecycle.js +2 -1
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/routes/approve.d.ts.map +1 -1
- package/dist/src/routes/approve.js +3 -3
- package/dist/src/routes/approve.js.map +1 -1
- package/dist/src/routes/git.d.ts.map +1 -1
- package/dist/src/routes/git.js +183 -54
- package/dist/src/routes/git.js.map +1 -1
- package/dist/src/routes/health.d.ts.map +1 -1
- package/dist/src/routes/health.js +8 -5
- package/dist/src/routes/health.js.map +1 -1
- package/dist/src/routes/helpers/uriGuard.d.ts +1 -1
- package/dist/src/routes/helpers/uriGuard.d.ts.map +1 -1
- package/dist/src/routes/helpers/uriGuard.js +3 -2
- package/dist/src/routes/helpers/uriGuard.js.map +1 -1
- package/dist/src/routes/helpers.d.ts +20 -4
- package/dist/src/routes/helpers.d.ts.map +1 -1
- package/dist/src/routes/helpers.js +38 -5
- package/dist/src/routes/helpers.js.map +1 -1
- package/dist/src/routes/list.d.ts.map +1 -1
- package/dist/src/routes/list.js +5 -6
- package/dist/src/routes/list.js.map +1 -1
- package/dist/src/routes/pending.d.ts.map +1 -1
- package/dist/src/routes/pending.js +5 -5
- package/dist/src/routes/pending.js.map +1 -1
- package/dist/src/routes/policies.d.ts +6 -4
- package/dist/src/routes/policies.d.ts.map +1 -1
- package/dist/src/routes/policies.js +22 -16
- package/dist/src/routes/policies.js.map +1 -1
- package/dist/src/routes/raw.d.ts.map +1 -1
- package/dist/src/routes/raw.js +68 -14
- package/dist/src/routes/raw.js.map +1 -1
- package/dist/src/routes/read.d.ts.map +1 -1
- package/dist/src/routes/read.js +3 -3
- package/dist/src/routes/read.js.map +1 -1
- package/dist/src/routes/search.d.ts.map +1 -1
- package/dist/src/routes/search.js +3 -3
- package/dist/src/routes/search.js.map +1 -1
- package/dist/src/routes/sources.d.ts +60 -18
- package/dist/src/routes/sources.d.ts.map +1 -1
- package/dist/src/routes/sources.js +420 -308
- package/dist/src/routes/sources.js.map +1 -1
- package/dist/src/routes/upload.d.ts.map +1 -1
- package/dist/src/routes/upload.js +3 -3
- package/dist/src/routes/upload.js.map +1 -1
- package/dist/src/services/BridgeService.d.ts +15 -35
- package/dist/src/services/BridgeService.d.ts.map +1 -1
- package/dist/src/services/BridgeService.js +30 -88
- package/dist/src/services/BridgeService.js.map +1 -1
- package/dist/src/services/FilesystemService.d.ts +3 -2
- package/dist/src/services/FilesystemService.d.ts.map +1 -1
- package/dist/src/services/FilesystemService.js +116 -32
- package/dist/src/services/FilesystemService.js.map +1 -1
- package/dist/src/services/FilesystemUiService.d.ts +4 -1
- package/dist/src/services/FilesystemUiService.d.ts.map +1 -1
- package/dist/src/services/FilesystemUiService.js +71 -27
- package/dist/src/services/FilesystemUiService.js.map +1 -1
- package/dist/src/services/SyncScheduler.d.ts +12 -4
- package/dist/src/services/SyncScheduler.d.ts.map +1 -1
- package/dist/src/services/SyncScheduler.js +45 -10
- package/dist/src/services/SyncScheduler.js.map +1 -1
- package/dist/src/services/baselineRepair.d.ts +13 -3
- package/dist/src/services/baselineRepair.d.ts.map +1 -1
- package/dist/src/services/baselineRepair.js +13 -3
- package/dist/src/services/baselineRepair.js.map +1 -1
- package/dist/src/services/declaredSources.d.ts +125 -42
- package/dist/src/services/declaredSources.d.ts.map +1 -1
- package/dist/src/services/declaredSources.js +117 -45
- package/dist/src/services/declaredSources.js.map +1 -1
- package/dist/src/store/dataFormats.d.ts +17 -2
- package/dist/src/store/dataFormats.d.ts.map +1 -1
- package/dist/src/store/dataFormats.js +18 -3
- package/dist/src/store/dataFormats.js.map +1 -1
- package/dist/src/sync/DeltaSyncRunner.d.ts.map +1 -1
- package/dist/src/sync/DeltaSyncRunner.js +144 -4
- package/dist/src/sync/DeltaSyncRunner.js.map +1 -1
- package/dist/src/sync/SyncJobQueue.d.ts +14 -2
- package/dist/src/sync/SyncJobQueue.d.ts.map +1 -1
- package/dist/src/sync/SyncJobQueue.js +56 -10
- package/dist/src/sync/SyncJobQueue.js.map +1 -1
- package/dist/src/sync/SyncWatchService.d.ts +21 -4
- package/dist/src/sync/SyncWatchService.d.ts.map +1 -1
- package/dist/src/sync/SyncWatchService.js +115 -10
- package/dist/src/sync/SyncWatchService.js.map +1 -1
- package/dist/src/sync/categoryInference.d.ts +1 -1
- package/dist/src/sync/categoryInference.js +1 -1
- package/dist/src/tools/execCtx.d.ts +6 -0
- package/dist/src/tools/execCtx.d.ts.map +1 -1
- package/dist/src/tools/execCtx.js +11 -0
- package/dist/src/tools/execCtx.js.map +1 -1
- package/dist/src/tools/fs_list.js +2 -2
- package/dist/src/tools/fs_list.js.map +1 -1
- package/dist/src/tools/fs_read.js +2 -2
- package/dist/src/tools/fs_read.js.map +1 -1
- package/dist/src/tools/fs_search.js +2 -2
- package/dist/src/tools/fs_search.js.map +1 -1
- package/dist/src/tools/fs_write.js +2 -2
- package/dist/src/tools/fs_write.js.map +1 -1
- package/dist/src/usecases/admitSourceRoot.d.ts +65 -0
- package/dist/src/usecases/admitSourceRoot.d.ts.map +1 -0
- package/dist/src/usecases/admitSourceRoot.js +85 -0
- package/dist/src/usecases/admitSourceRoot.js.map +1 -0
- package/dist/src/usecases/browseSourceFolders.d.ts +85 -0
- package/dist/src/usecases/browseSourceFolders.d.ts.map +1 -0
- package/dist/src/usecases/browseSourceFolders.js +210 -0
- package/dist/src/usecases/browseSourceFolders.js.map +1 -0
- package/dist/src/usecases/createDeclaredSources.d.ts +13 -2
- package/dist/src/usecases/createDeclaredSources.d.ts.map +1 -1
- package/dist/src/usecases/createDeclaredSources.js +39 -0
- package/dist/src/usecases/createDeclaredSources.js.map +1 -1
- package/dist/src/usecases/deprovisionProject.d.ts +4 -5
- package/dist/src/usecases/deprovisionProject.d.ts.map +1 -1
- package/dist/src/usecases/deprovisionProject.js +3 -8
- package/dist/src/usecases/deprovisionProject.js.map +1 -1
- package/dist/src/usecases/list.d.ts.map +1 -1
- package/dist/src/usecases/list.js +83 -26
- package/dist/src/usecases/list.js.map +1 -1
- package/dist/src/usecases/listAttachableDevices.d.ts +13 -0
- package/dist/src/usecases/listAttachableDevices.d.ts.map +1 -0
- package/dist/src/usecases/listAttachableDevices.js +10 -0
- package/dist/src/usecases/listAttachableDevices.js.map +1 -0
- package/dist/src/usecases/listSourcePackages.d.ts.map +1 -1
- package/dist/src/usecases/listSourcePackages.js +95 -79
- package/dist/src/usecases/listSourcePackages.js.map +1 -1
- package/dist/src/usecases/purgeExcludedPoints.d.ts.map +1 -1
- package/dist/src/usecases/purgeExcludedPoints.js +3 -2
- package/dist/src/usecases/purgeExcludedPoints.js.map +1 -1
- package/dist/src/usecases/readFiles.d.ts.map +1 -1
- package/dist/src/usecases/readFiles.js +17 -1
- package/dist/src/usecases/readFiles.js.map +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.d.ts +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.d.ts.map +1 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.js +2 -1
- package/dist/src/usecases/reconcileSourcePackageRoots.js.map +1 -1
- package/dist/src/usecases/revokePrincipal.d.ts +7 -5
- package/dist/src/usecases/revokePrincipal.d.ts.map +1 -1
- package/dist/src/usecases/revokePrincipal.js +29 -7
- package/dist/src/usecases/revokePrincipal.js.map +1 -1
- package/dist/src/usecases/scheduleSourceSync.d.ts +7 -0
- package/dist/src/usecases/scheduleSourceSync.d.ts.map +1 -1
- package/dist/src/usecases/scheduleSourceSync.js +6 -5
- package/dist/src/usecases/scheduleSourceSync.js.map +1 -1
- package/dist/src/usecases/searchFiles.d.ts.map +1 -1
- package/dist/src/usecases/searchFiles.js +13 -3
- package/dist/src/usecases/searchFiles.js.map +1 -1
- package/dist/src/usecases/sourcePackageInventory.d.ts +10 -2
- package/dist/src/usecases/sourcePackageInventory.d.ts.map +1 -1
- package/dist/src/usecases/sourcePackageInventory.js +24 -7
- package/dist/src/usecases/sourcePackageInventory.js.map +1 -1
- package/dist/src/usecases/triggerSourceSync.d.ts +46 -0
- package/dist/src/usecases/triggerSourceSync.d.ts.map +1 -0
- package/dist/src/usecases/triggerSourceSync.js +75 -0
- package/dist/src/usecases/triggerSourceSync.js.map +1 -0
- package/dist/src/usecases/uploadBinary.d.ts +2 -1
- package/dist/src/usecases/uploadBinary.d.ts.map +1 -1
- package/dist/src/usecases/uploadBinary.js +2 -2
- package/dist/src/usecases/uploadBinary.js.map +1 -1
- package/dist/src/usecases/vector/endpointGuard.d.ts +1 -1
- package/dist/src/usecases/vector/endpointGuard.js +1 -1
- package/dist/src/usecases/vectorActivityWatch.d.ts +20 -13
- package/dist/src/usecases/vectorActivityWatch.d.ts.map +1 -1
- package/dist/src/usecases/vectorActivityWatch.js +61 -36
- package/dist/src/usecases/vectorActivityWatch.js.map +1 -1
- package/dist/src/usecases/vectorMaintenance.d.ts.map +1 -1
- package/dist/src/usecases/vectorMaintenance.js +16 -1
- package/dist/src/usecases/vectorMaintenance.js.map +1 -1
- package/dist/src/usecases/write/index.d.ts.map +1 -1
- package/dist/src/usecases/write/index.js +17 -2
- package/dist/src/usecases/write/index.js.map +1 -1
- package/dist/src/usecases/write/occupancy.d.ts +5 -1
- package/dist/src/usecases/write/occupancy.d.ts.map +1 -1
- package/dist/src/usecases/write/occupancy.js +13 -4
- package/dist/src/usecases/write/occupancy.js.map +1 -1
- package/dist/src/usecases/write/structure.d.ts.map +1 -1
- package/dist/src/usecases/write/structure.js +20 -5
- package/dist/src/usecases/write/structure.js.map +1 -1
- package/dist/src/vector/FilesystemVectorIndex.d.ts +72 -2
- package/dist/src/vector/FilesystemVectorIndex.d.ts.map +1 -1
- package/dist/src/vector/FilesystemVectorIndex.js +211 -27
- package/dist/src/vector/FilesystemVectorIndex.js.map +1 -1
- package/dist/src/vector/InMemoryVectorIndex.d.ts.map +1 -1
- package/dist/src/vector/InMemoryVectorIndex.js +55 -7
- package/dist/src/vector/InMemoryVectorIndex.js.map +1 -1
- package/dist/src/vector/qdrantClient.d.ts +18 -1
- package/dist/src/vector/qdrantClient.d.ts.map +1 -1
- package/dist/src/vector/qdrantClient.js +48 -6
- package/dist/src/vector/qdrantClient.js.map +1 -1
- package/dist/src/wiring.d.ts +10 -12
- package/dist/src/wiring.d.ts.map +1 -1
- package/dist/src/wiring.js +101 -61
- package/dist/src/wiring.js.map +1 -1
- package/package.json +166 -54
- package/skills/connect-business-data/SKILL.md +2 -2
- package/skills/create-package/SKILL.md +18 -18
- package/skills/create-package/references/config-and-credentials.md +16 -13
- package/skills/create-package/references/connectors-and-uri-policies.md +36 -27
- package/skills/create-package/references/manifest-and-discovery.md +12 -12
- package/skills/create-package/references/runtime-and-build.md +18 -18
- package/skills/create-package/references/skills-and-context.md +35 -19
- package/skills/create-package/references/surfaces-and-design.md +16 -16
- package/skills/create-package/references/team-and-workflows.md +2 -2
- package/skills/create-package/references/test-and-ship.md +17 -17
- package/skills/create-package/references/tools-and-schemas.md +5 -5
- package/skills/create-package/scripts/{check-package.sh → check-package.mjs} +78 -64
- package/skills/create-package/scripts/{verify-loaded.sh → verify-loaded.mjs} +156 -162
- package/skills/git/SKILL.md +31 -29
- package/skills/manage-files/SKILL.md +11 -14
- package/skills/manage-sources/SKILL.md +85 -45
- package/skills/manage-uri-policy/SKILL.md +9 -6
- package/skills/sync/SKILL.md +2 -2
- package/dist/src/connectors/LocalDiskConnector.d.ts +0 -171
- package/dist/src/connectors/LocalDiskConnector.d.ts.map +0 -1
- package/dist/src/connectors/LocalDiskConnector.js +0 -1233
- package/dist/src/connectors/LocalDiskConnector.js.map +0 -1
- package/skills/manage-files/scripts/copy.sh +0 -7
- package/skills/manage-files/scripts/create.sh +0 -7
- package/skills/manage-files/scripts/mkdir.sh +0 -7
- package/skills/manage-files/scripts/modify.sh +0 -7
- package/skills/manage-files/scripts/upload.sh +0 -16
- package/skills/manage-files/scripts/write.sh +0 -29
- package/skills/manage-sources/scripts/reembed.sh +0 -23
|
@@ -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
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
988
|
-
|
|
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
|
-
<
|
|
1003
|
+
<RawMediaPreview
|
|
1004
|
+
kind="video"
|
|
1000
1005
|
src={rawUrl}
|
|
1001
|
-
|
|
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>
|
|
@@ -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
|
-
|
|
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 });
|
|
@@ -972,21 +976,11 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
|
|
|
972
976
|
compact
|
|
973
977
|
/>
|
|
974
978
|
) : item.kind === 'empty' ? (
|
|
975
|
-
<
|
|
976
|
-
|
|
979
|
+
<SourceEmptyRow
|
|
980
|
+
sourceRoot={item.sourceRoot}
|
|
981
|
+
unavailable={item.unavailable}
|
|
977
982
|
onContextMenu={(e) => handleSourceContextMenu(item.sourceRoot, e)}
|
|
978
|
-
|
|
979
|
-
<span className="text-xs text-white/35">
|
|
980
|
-
No files for {sourceFromUri(item.sourceRoot)}
|
|
981
|
-
</span>
|
|
982
|
-
<SkillLauncher
|
|
983
|
-
variant="label"
|
|
984
|
-
skill="manage-files"
|
|
985
|
-
packageId="@neuralis/brain-core"
|
|
986
|
-
scripts={["scripts/upload.sh", "scripts/modify.sh"]}
|
|
987
|
-
label="Populate this source with the agent"
|
|
988
|
-
/>
|
|
989
|
-
</div>
|
|
983
|
+
/>
|
|
990
984
|
) : item.kind === 'creating' ? (
|
|
991
985
|
<CreateRow
|
|
992
986
|
kind={item.createKind}
|
|
@@ -1052,6 +1046,37 @@ export function FileTree(props: { agentId: string; sidebarView?: SidebarView })
|
|
|
1052
1046
|
);
|
|
1053
1047
|
}
|
|
1054
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
|
+
|
|
1055
1080
|
/**
|
|
1056
1081
|
* Inline "New File / New Folder" input row spliced into the tree at the create
|
|
1057
1082
|
* position. Enter commits, Escape / empty-blur cancels — the `doneRef` guard
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { CheckCircle2, ChevronUp, Folder, FolderOpen, Loader2, Send } from 'lucide-react';
|
|
5
|
+
import type { PackageApi } from '@neuralis/package-system/client';
|
|
6
|
+
import { browseDirectories, type BrowseFoldersResult } from '../api';
|
|
7
|
+
import { useFilesystemHost } from '../FilesystemHost';
|
|
8
|
+
|
|
9
|
+
/** A folder the picker offers without a request — the discoverable mount roots on this server. */
|
|
10
|
+
export type FolderSuggestion = { name: string; path: string };
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
agentId: string;
|
|
14
|
+
projectId: string | null;
|
|
15
|
+
client: PackageApi;
|
|
16
|
+
/** The paired device the folder lives on (`dev_…`), or `null` for this server. */
|
|
17
|
+
device: string | null;
|
|
18
|
+
/** This server runs in a container: the browsable zone is `/mounts` (an empty path opens it). */
|
|
19
|
+
isDocker: boolean;
|
|
20
|
+
/** The folder the picker opens at (the typed path); empty = the plane's start. */
|
|
21
|
+
startPath: string;
|
|
22
|
+
/** This server only: discoverable folders shown first, picked with no listing. */
|
|
23
|
+
suggestions: readonly FolderSuggestion[];
|
|
24
|
+
/** The access the form asks for — an "ask on that device" requests the write list when set. */
|
|
25
|
+
wantWrite: boolean;
|
|
26
|
+
onSelect(path: string): void;
|
|
27
|
+
onClose(): void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The parent of a server or device path, in that path's own separators (`C:\x` → `C:\`). */
|
|
31
|
+
function parentOf(path: string): string {
|
|
32
|
+
const up = path.replace(/[/\\][^/\\]+[/\\]?$/, '');
|
|
33
|
+
if (/^[A-Za-z]:$/.test(up)) return `${up}\\`;
|
|
34
|
+
return up || '/';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** What a device's fixed state code tells a person — never a path. */
|
|
38
|
+
function deviceStateLine(state: string): string {
|
|
39
|
+
switch (state) {
|
|
40
|
+
case 'ceiling_denied': return 'This folder is not allowed on that device yet.';
|
|
41
|
+
case 'device_offline': return 'The device is offline. Type the folder — it attaches now and serves when the device is back.';
|
|
42
|
+
case 'broker_outdated': return 'That device runs an older Neuralis CLI. Update it there (neuralis device install) to browse; a typed folder still attaches.';
|
|
43
|
+
case 'devices_disabled': return 'Paired devices are switched off on this server.';
|
|
44
|
+
case 'not_found': return 'That folder does not exist on the device.';
|
|
45
|
+
case 'not_a_directory': return 'That path is not a folder.';
|
|
46
|
+
case 'os_denied': return 'The device cannot read that folder.';
|
|
47
|
+
default: return 'The device did not answer. Type the folder, or try again.';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The line a device folder outside its allowed ones reads — the picker's state and the Create guard say it the same way. */
|
|
52
|
+
export const DEVICE_FOLDER_NOT_ALLOWED = deviceStateLine('ceiling_denied');
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ask on the caller's OWN device for a folder it does not allow yet: the ask
|
|
56
|
+
* button, the answer, the pending count and the grant command to run there.
|
|
57
|
+
* ONE owner for the picker's `ceiling_denied` state and the Create guard. Key
|
|
58
|
+
* it by `path` so a new folder starts a new ask. Cost: one ask + one count per
|
|
59
|
+
* click, nothing on render.
|
|
60
|
+
*/
|
|
61
|
+
export function DeviceFolderAsk({ device, projectId, agentId, path, wantWrite }: {
|
|
62
|
+
device: string; projectId: string | null; agentId: string; path: string; wantWrite: boolean;
|
|
63
|
+
}) {
|
|
64
|
+
const host = useFilesystemHost();
|
|
65
|
+
const [ask, setAsk] = useState<{ busy: boolean; line: string | null; pending: number | null }>({ busy: false, line: null, pending: null });
|
|
66
|
+
const askOnDevice = async () => {
|
|
67
|
+
if (!projectId || !host.requestDeviceFolder) return;
|
|
68
|
+
setAsk((a) => ({ ...a, busy: true }));
|
|
69
|
+
try {
|
|
70
|
+
const answer = await host.requestDeviceFolder({ deviceId: device, path, list: wantWrite ? 'write' : 'read', projectId, agentId });
|
|
71
|
+
const pending = host.countDeviceFolderRequests ? await host.countDeviceFolderRequests({ deviceId: device, projectId, agentId }) : null;
|
|
72
|
+
setAsk({
|
|
73
|
+
busy: false,
|
|
74
|
+
pending,
|
|
75
|
+
line: answer.recorded
|
|
76
|
+
? 'Asked. On that device: tray → Allow a folder…, or run the command below.'
|
|
77
|
+
: answer.reason === 'inside_ceiling'
|
|
78
|
+
? 'That folder is already allowed — open it again.'
|
|
79
|
+
: 'The device did not record the ask. Run the command below on it.',
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
setAsk({ busy: false, pending: null, line: err instanceof Error && err.message ? err.message : 'The ask could not be sent.' });
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
{host.requestDeviceFolder && projectId && (
|
|
88
|
+
<button type="button" disabled={ask.busy} onClick={() => void askOnDevice()}
|
|
89
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-[8px] font-semibold text-cyan-300/80 bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-400/20 transition disabled:opacity-40">
|
|
90
|
+
{ask.busy ? <Loader2 className="h-2.5 w-2.5 animate-spin" /> : <Send className="h-2.5 w-2.5" />}
|
|
91
|
+
Ask on that device
|
|
92
|
+
</button>
|
|
93
|
+
)}
|
|
94
|
+
{ask.line && <div className="text-[9px] text-white/55">{ask.line}</div>}
|
|
95
|
+
{ask.pending !== null && <div className="text-[9px] text-white/40">{ask.pending} pending on that device for this project</div>}
|
|
96
|
+
<code className="block text-[9px] text-white/45 font-mono break-all">neuralis device grant {path}{wantWrite ? ' --write' : ''}</code>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The Add-Source folder picker: one level at a time, folders only, "Select
|
|
103
|
+
* this folder". On this server it reads `sources/browse` (the gates are the
|
|
104
|
+
* route's: you browse what you could attach; in Docker the `/mounts` zone and
|
|
105
|
+
* below) and shows the discoverable suggestions first. On the caller's OWN
|
|
106
|
+
* device it starts at the typed folder, or else from the folders that device
|
|
107
|
+
* allows this host, lists through the device's broker, and for a folder
|
|
108
|
+
* outside them offers to ASK on that device — the grant stays the owner's act on the machine (the tray's
|
|
109
|
+
* "Allow a folder…", `neuralis device grant`). UX only: the create re-checks
|
|
110
|
+
* every gate. Cost: one request per opened level, one ask per click; nothing
|
|
111
|
+
* on open of the form, no timer, no listener.
|
|
112
|
+
*/
|
|
113
|
+
export function FolderPicker({ agentId, projectId, client, device, isDocker, startPath, suggestions, wantWrite, onSelect, onClose }: Props) {
|
|
114
|
+
const [listing, setListing] = useState<BrowseFoldersResult | null>(null);
|
|
115
|
+
const [path, setPath] = useState('');
|
|
116
|
+
const [loading, setLoading] = useState(false);
|
|
117
|
+
const [error, setError] = useState<string | null>(null);
|
|
118
|
+
// The device's allowed folders: "up" from one of them returns to that list.
|
|
119
|
+
const rootsRef = useRef<Set<string>>(new Set());
|
|
120
|
+
// Liveness: a reply that lands after the picker closed (or a newer open) is dropped.
|
|
121
|
+
const seqRef = useRef(0);
|
|
122
|
+
// Where the picker first opens — read once per open, never a reason to re-list as the field changes.
|
|
123
|
+
const startRef = useRef(startPath);
|
|
124
|
+
startRef.current = startPath;
|
|
125
|
+
|
|
126
|
+
const open = useCallback(async (target: string) => {
|
|
127
|
+
const seq = ++seqRef.current;
|
|
128
|
+
// Natively an empty path is the suggestions alone: there is no "start" folder to list.
|
|
129
|
+
if (!device && !isDocker && target === '') {
|
|
130
|
+
setListing(null);
|
|
131
|
+
setPath('');
|
|
132
|
+
setError(null);
|
|
133
|
+
setLoading(false);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setLoading(true);
|
|
137
|
+
setError(null);
|
|
138
|
+
try {
|
|
139
|
+
const result = await browseDirectories({ path: target, agentId, ...(projectId ? { projectId } : {}), ...(device ? { device } : {}) }, client);
|
|
140
|
+
if (seq !== seqRef.current) return;
|
|
141
|
+
if (result.roots) rootsRef.current = new Set(result.entries.map((e) => e.path));
|
|
142
|
+
setListing(result);
|
|
143
|
+
setPath(target);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (seq !== seqRef.current) return;
|
|
146
|
+
// One stable sentence per plane — the server never returns a path or an errno.
|
|
147
|
+
setError(device && err instanceof Error && err.message ? err.message : 'That path is not available.');
|
|
148
|
+
setListing(null);
|
|
149
|
+
setPath(target);
|
|
150
|
+
} finally {
|
|
151
|
+
if (seq === seqRef.current) setLoading(false);
|
|
152
|
+
}
|
|
153
|
+
}, [agentId, projectId, client, device, isDocker]);
|
|
154
|
+
|
|
155
|
+
const hasSuggestions = suggestions.length > 0;
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
// A typed folder opens where it is — on a device too, where a folder outside
|
|
158
|
+
// the allowed ones answers `ceiling_denied` (existence-independent) and offers the ask.
|
|
159
|
+
void open(startRef.current || (device || hasSuggestions || isDocker ? '' : '/'));
|
|
160
|
+
return () => { seqRef.current += 1; };
|
|
161
|
+
}, [open, device, isDocker, hasSuggestions]);
|
|
162
|
+
|
|
163
|
+
const state = listing?.state ?? 'ok';
|
|
164
|
+
const inRoots = Boolean(device && listing?.roots);
|
|
165
|
+
// On this server the suggestions are the start (in Docker above the zone's own listing).
|
|
166
|
+
const listed = new Set(listing?.entries.map((e) => e.path) ?? []);
|
|
167
|
+
const shownSuggestions = !device && path === '' ? suggestions.filter((s) => !listed.has(s.path)) : [];
|
|
168
|
+
const canSelectHere = Boolean(path) && !inRoots && state === 'ok' && !error;
|
|
169
|
+
const canGoUp = Boolean(path) && !loading && (device ? true : listing ? listing.atRoot !== true : path !== '/');
|
|
170
|
+
|
|
171
|
+
const goUp = () => {
|
|
172
|
+
if (device && (rootsRef.current.has(path) || listing?.atRoot || parentOf(path) === path)) { void open(''); return; }
|
|
173
|
+
void open(parentOf(path));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div className="rounded-md bg-white/[0.03] border border-white/[0.1] overflow-hidden">
|
|
178
|
+
<div className="flex items-center gap-1.5 px-2 py-1.5 bg-white/[0.02] border-b border-white/[0.06]">
|
|
179
|
+
<FolderOpen className="h-3 w-3 text-cyan-400 shrink-0" />
|
|
180
|
+
<span className="text-[9px] font-mono text-white/60 truncate flex-1">
|
|
181
|
+
{inRoots ? 'Folders this device allows' : path || 'Suggested'}
|
|
182
|
+
</span>
|
|
183
|
+
<button type="button" onClick={onClose} className="text-[9px] text-white/40 hover:text-white/70 px-1">Close</button>
|
|
184
|
+
</div>
|
|
185
|
+
{error && <div className="px-2 py-1 text-[9px] text-red-300/80">{error}</div>}
|
|
186
|
+
{loading ? (
|
|
187
|
+
<div className="flex items-center justify-center py-3">
|
|
188
|
+
<Loader2 className="h-3 w-3 animate-spin text-white/30" />
|
|
189
|
+
</div>
|
|
190
|
+
) : (
|
|
191
|
+
<div className="max-h-[180px] overflow-y-auto">
|
|
192
|
+
{shownSuggestions.map((s) => (
|
|
193
|
+
<div key={`s:${s.path}`} className="flex items-center gap-0.5 hover:bg-white/[0.04] transition">
|
|
194
|
+
<button type="button" onClick={() => void open(s.path)} className="flex items-center gap-1.5 px-2 py-1 flex-1 text-left min-w-0">
|
|
195
|
+
<Folder className="h-3 w-3 text-emerald-300/70 shrink-0" />
|
|
196
|
+
<span className="text-[10px] text-white/75 truncate">{s.name}</span>
|
|
197
|
+
<span className="text-[8px] text-white/30 font-mono truncate">{s.path}</span>
|
|
198
|
+
</button>
|
|
199
|
+
<button type="button" onClick={() => onSelect(s.path)} className="px-2 py-1 text-[8px] font-medium text-cyan-300/70 hover:text-cyan-200 shrink-0">Select</button>
|
|
200
|
+
</div>
|
|
201
|
+
))}
|
|
202
|
+
{canGoUp && (
|
|
203
|
+
<button type="button" onClick={goUp} className="w-full flex items-center gap-1.5 px-2 py-1 text-left hover:bg-white/[0.04] transition">
|
|
204
|
+
<ChevronUp className="h-3 w-3 text-white/30" />
|
|
205
|
+
<span className="text-[10px] text-white/50">..</span>
|
|
206
|
+
</button>
|
|
207
|
+
)}
|
|
208
|
+
{device && state !== 'ok' && (
|
|
209
|
+
<div className="px-2 py-1.5 space-y-1">
|
|
210
|
+
<div className="text-[9px] text-amber-300/80">{deviceStateLine(state)}</div>
|
|
211
|
+
{state === 'ceiling_denied' && path && (
|
|
212
|
+
<DeviceFolderAsk key={path} device={device} projectId={projectId} agentId={agentId} path={path} wantWrite={wantWrite} />
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
{state === 'ok' && !error && listing && listing.entries.length === 0 && (
|
|
217
|
+
<div className="px-2 py-2 text-[9px] text-white/30 italic">
|
|
218
|
+
{inRoots ? 'This device allows no folder to this server yet — type one and ask, or run neuralis device grant <dir> there.' : 'No subfolders'}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
{state === 'ok' && listing?.entries.map((entry) => (
|
|
222
|
+
<div key={entry.path} className="flex items-center gap-0.5 hover:bg-white/[0.04] transition">
|
|
223
|
+
<button type="button" onClick={() => void open(entry.path)} className="flex items-center gap-1.5 px-2 py-1 flex-1 text-left min-w-0">
|
|
224
|
+
<Folder className="h-3 w-3 text-cyan-400/70 shrink-0" />
|
|
225
|
+
<span className="text-[10px] text-white/75 truncate">{entry.name}</span>
|
|
226
|
+
{entry.access && <span className="text-[8px] text-white/30 shrink-0">{entry.access === 'write' ? 'read · write' : 'read'}</span>}
|
|
227
|
+
</button>
|
|
228
|
+
<button type="button" onClick={() => onSelect(entry.path)} className="px-2 py-1 text-[8px] font-medium text-cyan-300/70 hover:text-cyan-200 shrink-0">Select</button>
|
|
229
|
+
</div>
|
|
230
|
+
))}
|
|
231
|
+
{state === 'ok' && (listing?.symlinks ?? 0) > 0 && (
|
|
232
|
+
<div className="px-2 py-1 text-[8px] text-white/30 italic">
|
|
233
|
+
{listing!.symlinks} linked {listing!.symlinks === 1 ? 'folder is' : 'folders are'} not shown — links are not followed; type the path to attach one.
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
{state === 'ok' && listing?.truncated && (
|
|
237
|
+
<div className="px-2 py-1 text-[8px] text-white/30 italic">Only the first entries are listed — type the path for one further down.</div>
|
|
238
|
+
)}
|
|
239
|
+
{canSelectHere && (
|
|
240
|
+
<button type="button" onClick={() => onSelect(path)}
|
|
241
|
+
className="w-full flex items-center gap-1.5 px-2 py-1.5 text-left bg-cyan-500/5 hover:bg-cyan-500/10 border-t border-white/[0.06] transition">
|
|
242
|
+
<CheckCircle2 className="h-3 w-3 text-cyan-400 shrink-0" />
|
|
243
|
+
<span className="text-[10px] font-medium text-cyan-300/80">Select this folder</span>
|
|
244
|
+
<span className="text-[9px] text-white/30 font-mono truncate ml-auto">{path}</span>
|
|
245
|
+
</button>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
@@ -682,6 +682,11 @@ export function GitControlPanel({ agentId }: { agentId: string }) {
|
|
|
682
682
|
// shape symmetric with `source`).
|
|
683
683
|
useEffect(() => {
|
|
684
684
|
if (!selectedSlug) return;
|
|
685
|
+
// A folder that only CONTAINS checkouts has no change-set at its root: the
|
|
686
|
+
// effect above picks its first repo, and that pick loads the status. Asking
|
|
687
|
+
// the root would answer "unavailable" for a moment on every switch.
|
|
688
|
+
const git = selectedSource?.git;
|
|
689
|
+
if (!activeRepoRel && git && !git.isRepo && git.containsRepos) return;
|
|
685
690
|
void loadGitStatus({ agentId, projectId: projectId ?? null, source: selectedSlug, repo: activeRepoRel });
|
|
686
691
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
687
692
|
}, [agentId, projectId, selectedSlug, activeRepoRel, repoKey]);
|