@@ -3,10 +3,12 @@ import { db } from '@sim/db'
33import {
44 chat as chatTable ,
55 copilotChats ,
6+ customTools as customToolsTable ,
67 document ,
78 jobExecutionLogs ,
89 knowledgeConnector ,
910 mcpServers as mcpServersTable ,
11+ skill as skillTable ,
1012 workflowDeploymentVersion ,
1113 workflowExecutionLogs ,
1214 workflowFolder ,
@@ -16,7 +18,7 @@ import {
1618} from '@sim/db/schema'
1719import { createLogger } from '@sim/logger'
1820import { toError } from '@sim/utils/errors'
19- import { and , desc , eq , isNotNull , isNull , ne , sql } from 'drizzle-orm'
21+ import { and , desc , eq , isNotNull , isNull , ne , or , sql } from 'drizzle-orm'
2022import { listApiKeys } from '@/lib/api-key/service'
2123import {
2224 buildWorkspaceContextMd ,
@@ -109,13 +111,13 @@ import {
109111 type WorkspaceFileRecord ,
110112} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
111113import { listCustomBlocksWithInputsForWorkspace } from '@/lib/workflows/custom-blocks/operations'
112- import { listCustomTools } from '@/lib/workflows/custom-tools/operations'
114+ import { getCustomToolById } from '@/lib/workflows/custom-tools/operations'
113115import {
114116 loadWorkflowDeploymentSnapshot ,
115117 loadWorkflowFromNormalizedTables ,
116118} from '@/lib/workflows/persistence/utils'
117119import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer'
118- import { listSkills } from '@/lib/workflows/skills/operations'
120+ import { getSkillById } from '@/lib/workflows/skills/operations'
119121import { listFolders , listWorkflows } from '@/lib/workflows/utils'
120122import {
121123 assertActiveWorkspaceAccess ,
@@ -1878,25 +1880,47 @@ export class WorkspaceVFS {
18781880 }
18791881
18801882 /**
1881- * Materialize custom tools using the shared listCustomTools function.
1883+ * Advertise custom tools in the VFS without eagerly loading their code.
1884+ * Paths are registered as lazy so glob/WORKSPACE.md see them, but full
1885+ * schema+code is fetched only when read (or a grep whose scope touches them).
18821886 */
18831887 private async materializeCustomTools (
18841888 workspaceId : string ,
18851889 userId : string
18861890 ) : Promise < NonNullable < WorkspaceMdData [ 'customTools' ] > > {
18871891 try {
1888- const toolRows = await listCustomTools ( { userId, workspaceId } )
1892+ // Metadata only — tool code can be large; keep it out of the eager map.
1893+ // Visibility matches listCustomTools: workspace tools + legacy user-owned.
1894+ const toolRows = await db
1895+ . select ( {
1896+ id : customToolsTable . id ,
1897+ title : customToolsTable . title ,
1898+ } )
1899+ . from ( customToolsTable )
1900+ . where (
1901+ or (
1902+ eq ( customToolsTable . workspaceId , workspaceId ) ,
1903+ and ( isNull ( customToolsTable . workspaceId ) , eq ( customToolsTable . userId , userId ) )
1904+ )
1905+ )
1906+ . orderBy ( desc ( customToolsTable . createdAt ) )
18891907
18901908 for ( const tool of toolRows ) {
18911909 const safeName = sanitizeName ( tool . title )
1892- const serialized = serializeCustomTool ( {
1893- id : tool . id ,
1894- title : tool . title ,
1895- schema : tool . schema ,
1896- code : tool . code ,
1897- } )
1898- this . files . set ( `custom-tools/${ safeName } .json` , serialized )
1899- this . files . set ( `agent/custom-tools/${ safeName } .json` , serialized )
1910+ const toolId = tool . id
1911+ const load = async ( ) => {
1912+ const full = await getCustomToolById ( { toolId, userId, workspaceId } )
1913+ if ( ! full ) return null
1914+ return serializeCustomTool ( {
1915+ id : full . id ,
1916+ title : full . title ,
1917+ schema : full . schema ,
1918+ code : full . code ,
1919+ } )
1920+ }
1921+ // Legacy alias + canonical agent/ path — each resolves independently on read.
1922+ this . registerLazy ( `custom-tools/${ safeName } .json` , load )
1923+ this . registerLazy ( `agent/custom-tools/${ safeName } .json` , load )
19001924 }
19011925
19021926 return toolRows . map ( ( t ) => ( { id : t . id , name : t . title } ) )
@@ -1995,26 +2019,39 @@ export class WorkspaceVFS {
19952019 }
19962020
19972021 /**
1998- * Materialize workspace skills using the shared listSkills function.
2022+ * Advertise workspace skills in the VFS without eagerly loading their bodies.
2023+ * Paths are registered as lazy so glob/WORKSPACE.md see them, but full content
2024+ * is fetched only when read (or a grep whose scope touches the path) resolves them.
19992025 */
20002026 private async materializeSkills (
20012027 workspaceId : string
20022028 ) : Promise < NonNullable < WorkspaceMdData [ 'skills' ] > > {
20032029 try {
2004- const skillRows = await listSkills ( { workspaceId, includeBuiltins : false } )
2030+ // Metadata only — skill bodies can be large; keep them out of the eager map.
2031+ const skillRows = await db
2032+ . select ( {
2033+ id : skillTable . id ,
2034+ name : skillTable . name ,
2035+ description : skillTable . description ,
2036+ } )
2037+ . from ( skillTable )
2038+ . where ( eq ( skillTable . workspaceId , workspaceId ) )
2039+ . orderBy ( desc ( skillTable . createdAt ) )
20052040
20062041 for ( const s of skillRows ) {
20072042 const safeName = sanitizeName ( s . name )
2008- this . files . set (
2009- `agent/skills/${ safeName } .json` ,
2010- serializeSkill ( {
2011- id : s . id ,
2012- name : s . name ,
2013- description : s . description ,
2014- content : s . content ,
2015- createdAt : s . createdAt ,
2043+ const skillId = s . id
2044+ this . registerLazy ( `agent/skills/${ safeName } .json` , async ( ) => {
2045+ const full = await getSkillById ( { skillId, workspaceId } )
2046+ if ( ! full ) return null
2047+ return serializeSkill ( {
2048+ id : full . id ,
2049+ name : full . name ,
2050+ description : full . description ,
2051+ content : full . content ,
2052+ createdAt : full . createdAt ,
20162053 } )
2017- )
2054+ } )
20182055 }
20192056
20202057 return skillRows . map ( ( s ) => ( { id : s . id , name : s . name , description : s . description } ) )
0 commit comments